Merge "Auto hide IllustrationPreference when lottie file is empty" into main
diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java
index 7724c23..92543b1 100644
--- a/core/java/android/app/AppCompatTaskInfo.java
+++ b/core/java/android/app/AppCompatTaskInfo.java
@@ -32,6 +32,11 @@
public boolean topActivityEligibleForLetterboxEducation;
/**
+ * Whether the letterbox education is enabled
+ */
+ public boolean isLetterboxEducationEnabled;
+
+ /**
* Whether the direct top activity is in size compat mode on foreground.
*/
public boolean topActivityInSizeCompat;
@@ -178,6 +183,7 @@
== that.topActivityEligibleForUserAspectRatioButton
&& topActivityEligibleForLetterboxEducation
== that.topActivityEligibleForLetterboxEducation
+ && isLetterboxEducationEnabled == that.isLetterboxEducationEnabled
&& topActivityLetterboxVerticalPosition == that.topActivityLetterboxVerticalPosition
&& topActivityLetterboxHorizontalPosition
== that.topActivityLetterboxHorizontalPosition
@@ -192,6 +198,7 @@
* Reads the AppCompatTaskInfo from a parcel.
*/
void readFromParcel(Parcel source) {
+ isLetterboxEducationEnabled = source.readBoolean();
topActivityInSizeCompat = source.readBoolean();
topActivityEligibleForLetterboxEducation = source.readBoolean();
isLetterboxDoubleTapEnabled = source.readBoolean();
@@ -212,6 +219,7 @@
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(isLetterboxEducationEnabled);
dest.writeBoolean(topActivityInSizeCompat);
dest.writeBoolean(topActivityEligibleForLetterboxEducation);
dest.writeBoolean(isLetterboxDoubleTapEnabled);
@@ -232,6 +240,7 @@
return "AppCompatTaskInfo { topActivityInSizeCompat=" + topActivityInSizeCompat
+ " topActivityEligibleForLetterboxEducation= "
+ topActivityEligibleForLetterboxEducation
+ + "isLetterboxEducationEnabled= " + isLetterboxEducationEnabled
+ " isLetterboxDoubleTapEnabled= " + isLetterboxDoubleTapEnabled
+ " topActivityEligibleForUserAspectRatioButton= "
+ topActivityEligibleForUserAspectRatioButton
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 2d0f6fc..54f6909 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -482,7 +482,8 @@
UID_STATE_FOREGROUND_SERVICE,
UID_STATE_FOREGROUND,
UID_STATE_BACKGROUND,
- UID_STATE_CACHED
+ UID_STATE_CACHED,
+ UID_STATE_NONEXISTENT
})
public @interface UidState {}
@@ -566,6 +567,12 @@
public static final int MIN_PRIORITY_UID_STATE = UID_STATE_CACHED;
/**
+ * Special uid state: The UID is not running
+ * @hide
+ */
+ public static final int UID_STATE_NONEXISTENT = Integer.MAX_VALUE;
+
+ /**
* Resolves the first unrestricted state given an app op.
* @param op The op to resolve.
* @return The last restricted UID state.
@@ -596,6 +603,7 @@
UID_STATE_FOREGROUND,
UID_STATE_BACKGROUND,
UID_STATE_CACHED
+ // UID_STATE_NONEXISTENT isn't a real UID state, so it is excluded
};
/** @hide */
@@ -615,6 +623,8 @@
return "bg";
case UID_STATE_CACHED:
return "cch";
+ case UID_STATE_NONEXISTENT:
+ return "gone";
default:
return "unknown";
}
diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig
index 8458857..36d0e08 100644
--- a/core/java/android/companion/flags.aconfig
+++ b/core/java/android/companion/flags.aconfig
@@ -39,3 +39,11 @@
description: "Expose perm sync user consent API"
bug: "309528663"
}
+
+flag {
+ name: "ongoing_perm_sync"
+ is_exported: true
+ namespace: "companion"
+ description: "Enable ongoing perm sync"
+ bug: "338469649"
+}
\ No newline at end of file
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index cee8d96..061e7f7 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -254,6 +254,14 @@
}
flag {
+ name: "wait_application_killed"
+ namespace: "package_manager_service"
+ description: "Feature flag to control whether to wait until the application is killed when clear application data"
+ bug: "31009094"
+ is_fixed_read_only: true
+}
+
+flag {
name: "component_state_changed_metrics"
namespace: "package_manager_service"
description: "Feature flag to log the metrics when the component state is changed."
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 0e28560..2ca58d1 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -166,6 +166,16 @@
}
flag {
+ name: "finish_running_ops_for_killed_packages"
+ namespace: "permissions"
+ description: "Finish all appops for a dead app process"
+ bug: "234630570"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "runtime_permission_appops_mapping_enabled"
is_fixed_read_only: true
namespace: "permissions"
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
index 29a6db6..8237b20 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
@@ -105,6 +105,21 @@
public static final String SERVICE_INTERFACE =
"android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService";
+ // TODO(339594686): make API
+ /**
+ * @hide
+ */
+ public static final String REGISTER_MODEL_UPDATE_CALLBACK_BUNDLE_KEY =
+ "register_model_update_callback";
+ /**
+ * @hide
+ */
+ public static final String MODEL_LOADED_BUNDLE_KEY = "model_loaded";
+ /**
+ * @hide
+ */
+ public static final String MODEL_UNLOADED_BUNDLE_KEY = "model_unloaded";
+
private IRemoteStorageService mRemoteStorageService;
/**
diff --git a/core/java/android/window/TransitionFilter.java b/core/java/android/window/TransitionFilter.java
index 64fe66e..ec4e3e9 100644
--- a/core/java/android/window/TransitionFilter.java
+++ b/core/java/android/window/TransitionFilter.java
@@ -25,6 +25,7 @@
import android.app.ActivityManager;
import android.app.WindowConfiguration;
import android.content.ComponentName;
+import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.WindowManager;
@@ -180,6 +181,7 @@
public @ContainerOrder int mOrder = CONTAINER_ORDER_ANY;
public ComponentName mTopActivity;
+ public IBinder mLaunchCookie;
public Requirement() {
}
@@ -193,6 +195,7 @@
mMustBeTask = in.readBoolean();
mOrder = in.readInt();
mTopActivity = in.readTypedObject(ComponentName.CREATOR);
+ mLaunchCookie = in.readStrongBinder();
}
/** Go through changes and find if at-least one change matches this filter */
@@ -231,6 +234,9 @@
if (mMustBeTask && change.getTaskInfo() == null) {
continue;
}
+ if (!matchesCookie(change.getTaskInfo())) {
+ continue;
+ }
return true;
}
return false;
@@ -247,13 +253,25 @@
return false;
}
+ private boolean matchesCookie(ActivityManager.RunningTaskInfo info) {
+ if (mLaunchCookie == null) return true;
+ if (info == null) return false;
+ for (IBinder cookie : info.launchCookies) {
+ if (mLaunchCookie.equals(cookie)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/** Check if the request matches this filter. It may generate false positives */
boolean matches(@NonNull TransitionRequestInfo request) {
// Can't check modes/order since the transition hasn't been built at this point.
if (mActivityType == ACTIVITY_TYPE_UNDEFINED) return true;
return request.getTriggerTask() != null
&& request.getTriggerTask().getActivityType() == mActivityType
- && matchesTopActivity(request.getTriggerTask(), null /* activityCmp */);
+ && matchesTopActivity(request.getTriggerTask(), null /* activityCmp */)
+ && matchesCookie(request.getTriggerTask());
}
@Override
@@ -267,6 +285,7 @@
dest.writeBoolean(mMustBeTask);
dest.writeInt(mOrder);
dest.writeTypedObject(mTopActivity, flags);
+ dest.writeStrongBinder(mLaunchCookie);
}
@NonNull
@@ -307,6 +326,7 @@
out.append(" mustBeTask=" + mMustBeTask);
out.append(" order=" + containerOrderToString(mOrder));
out.append(" topActivity=").append(mTopActivity);
+ out.append(" launchCookie=").append(mLaunchCookie);
out.append("}");
return out.toString();
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index f931a76..e29f256 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -47,7 +47,7 @@
void animateExpandNotificationsPanel();
void animateExpandSettingsPanel(String subPanel);
void animateCollapsePanels();
- void togglePanel();
+ void toggleNotificationsPanel();
void showWirelessChargingAnimation(int batteryLevel);
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1ef5c3f..5bd2033 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4652,6 +4652,19 @@
-->
<string-array name="config_companionDeviceCerts" translatable="false"></string-array>
+ <!-- A list of packages that auto-enable permissions sync feature.
+ Note that config_companionPermSyncEnabledPackages and config_companionPermSyncEnabledCerts
+ are parallel arrays.
+ -->
+ <string-array name="config_companionPermSyncEnabledPackages" translatable="false"></string-array>
+
+ <!-- A list of SHA256 Certificates corresponding to config_companionPermSyncEnabledPackages.
+ Note that config_companionPermSyncEnabledPackages and config_companionPermSyncEnabledCerts
+ are parallel arrays.
+ Example: "1A:2B:3C:4D"
+ -->
+ <string-array name="config_companionPermSyncEnabledCerts" translatable="false"></string-array>
+
<!-- The package name for the default wellbeing app.
This package must be trusted, as it has the permissions to control other applications
on the device.
@@ -4704,6 +4717,13 @@
<!-- The component name for the default system on-device sandboxed inference service. -->
<string name="config_defaultOnDeviceSandboxedInferenceService" translatable="false"></string>
+ <!-- The broadcast intent name for notifying when the on-device model is loading -->
+ <string name="config_onDeviceIntelligenceModelLoadedBroadcastKey" translatable="false"></string>
+
+ <!-- The broadcast intent name for notifying when the on-device model has been unloaded -->
+ <string name="config_onDeviceIntelligenceModelUnloadedBroadcastKey" translatable="false"></string>
+
+
<!-- Component name that accepts ACTION_SEND intents for requesting ambient context consent for
wearable sensing. -->
<string translatable="false" name="config_defaultWearableSensingConsentComponent"></string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ec63ea3..ae79a4c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -675,6 +675,8 @@
<java-symbol type="string" name="config_companionDeviceManagerPackage" />
<java-symbol type="array" name="config_companionDevicePackages" />
<java-symbol type="array" name="config_companionDeviceCerts" />
+ <java-symbol type="array" name="config_companionPermSyncEnabledPackages" />
+ <java-symbol type="array" name="config_companionPermSyncEnabledCerts" />
<java-symbol type="string" name="config_default_dns_server" />
<java-symbol type="string" name="config_ethernet_iface_regex" />
<java-symbol type="string" name="not_checked" />
@@ -3941,6 +3943,8 @@
<java-symbol type="string" name="config_defaultWearableSensingService" />
<java-symbol type="string" name="config_defaultOnDeviceIntelligenceService" />
<java-symbol type="string" name="config_defaultOnDeviceSandboxedInferenceService" />
+ <java-symbol type="string" name="config_onDeviceIntelligenceModelLoadedBroadcastKey" />
+ <java-symbol type="string" name="config_onDeviceIntelligenceModelUnloadedBroadcastKey" />
<java-symbol type="string" name="config_retailDemoPackage" />
<java-symbol type="string" name="config_retailDemoPackageSignature" />
diff --git a/core/tests/coretests/src/android/graphics/drawable/IconTest.java b/core/tests/coretests/src/android/graphics/drawable/IconTest.java
index 950925f..e0c3b04 100644
--- a/core/tests/coretests/src/android/graphics/drawable/IconTest.java
+++ b/core/tests/coretests/src/android/graphics/drawable/IconTest.java
@@ -475,8 +475,8 @@
final Icon ic = Icon.createWithBitmap(bm);
final Drawable drawable = ic.loadDrawable(mContext);
- assertThat(drawable.getIntrinsicWidth()).isEqualTo(maxWidth);
- assertThat(drawable.getIntrinsicHeight()).isEqualTo(maxHeight);
+ assertThat(Math.abs(drawable.getIntrinsicWidth() - maxWidth)).isLessThan(2);
+ assertThat(Math.abs(drawable.getIntrinsicHeight() - maxHeight)).isLessThan(2);
}
@Test
diff --git a/graphics/java/android/graphics/RecordingCanvas.java b/graphics/java/android/graphics/RecordingCanvas.java
index 635e78e..cc5b3b9 100644
--- a/graphics/java/android/graphics/RecordingCanvas.java
+++ b/graphics/java/android/graphics/RecordingCanvas.java
@@ -40,7 +40,7 @@
/** @hide */
private static int getPanelFrameSize() {
- final int DefaultSize = 100 * 1024 * 1024; // 100 MB;
+ final int DefaultSize = 150 * 1024 * 1024; // 150 MB;
return Math.max(SystemProperties.getInt("ro.hwui.max_texture_allocation_size", DefaultSize),
DefaultSize);
}
@@ -262,7 +262,7 @@
protected void throwIfCannotDraw(Bitmap bitmap) {
super.throwIfCannotDraw(bitmap);
int bitmapSize = bitmap.getByteCount();
- if (bitmapSize > MAX_BITMAP_SIZE) {
+ if (bitmap.getConfig() != Bitmap.Config.HARDWARE && bitmapSize > MAX_BITMAP_SIZE) {
throw new RuntimeException(
"Canvas: trying to draw too large(" + bitmapSize + "bytes) bitmap.");
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 5c292f1..bfac24b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -188,6 +188,11 @@
*/
private boolean mHasShownUserAspectRatioSettingsButton = false;
+ /**
+ * This is true when the rechability education is displayed for the first time.
+ */
+ private boolean mIsFirstReachabilityEducationRunning;
+
public CompatUIController(@NonNull Context context,
@NonNull ShellInit shellInit,
@NonNull ShellController shellController,
@@ -252,9 +257,35 @@
removeLayouts(taskInfo.taskId);
return;
}
-
+ // We're showing the first reachability education so we ignore incoming TaskInfo
+ // until the education flow has completed or we double tap.
+ if (mIsFirstReachabilityEducationRunning) {
+ return;
+ }
+ if (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed) {
+ if (taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled) {
+ createOrUpdateLetterboxEduLayout(taskInfo, taskListener);
+ } else if (!taskInfo.appCompatTaskInfo.isFromLetterboxDoubleTap) {
+ // In this case the app is letterboxed and the letterbox education
+ // is disabled. In this case we need to understand if it's the first
+ // time we show the reachability education. When this is happening
+ // we need to ignore all the incoming TaskInfo until the education
+ // completes. If we come from a double tap we follow the normal flow.
+ final boolean topActivityPillarboxed =
+ taskInfo.appCompatTaskInfo.isTopActivityPillarboxed();
+ final boolean isFirstTimeHorizontalReachabilityEdu = topActivityPillarboxed
+ && !mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(taskInfo);
+ final boolean isFirstTimeVerticalReachabilityEdu = !topActivityPillarboxed
+ && !mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(taskInfo);
+ if (isFirstTimeHorizontalReachabilityEdu || isFirstTimeVerticalReachabilityEdu) {
+ mIsFirstReachabilityEducationRunning = true;
+ mCompatUIConfiguration.setSeenLetterboxEducation(taskInfo.userId);
+ createOrUpdateReachabilityEduLayout(taskInfo, taskListener);
+ return;
+ }
+ }
+ }
createOrUpdateCompatLayout(taskInfo, taskListener);
- createOrUpdateLetterboxEduLayout(taskInfo, taskListener);
createOrUpdateRestartDialogLayout(taskInfo, taskListener);
if (mCompatUIConfiguration.getHasSeenLetterboxEducation(taskInfo.userId)) {
createOrUpdateReachabilityEduLayout(taskInfo, taskListener);
@@ -589,6 +620,7 @@
private void onInitialReachabilityEduDismissed(@NonNull TaskInfo taskInfo,
@NonNull ShellTaskOrganizer.TaskListener taskListener) {
// We need to update the UI otherwise it will not be shown until the user relaunches the app
+ mIsFirstReachabilityEducationRunning = false;
createOrUpdateUserAspectRatioSettingsLayout(taskInfo, taskListener);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
index b41454d..5af4c3b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -41,15 +41,6 @@
public static final boolean IS_DISPLAY_CHANGE_ENABLED = SystemProperties.getBoolean(
"persist.wm.debug.desktop_change_display", false);
-
- /**
- * Flag to indicate that desktop stashing is enabled.
- * When enabled, swiping home from desktop stashes the open apps. Next app that launches,
- * will be added to the desktop.
- */
- private static final boolean IS_STASHING_ENABLED = SystemProperties.getBoolean(
- "persist.wm.debug.desktop_stashing", false);
-
/**
* Flag to indicate whether to apply shadows to windows in desktop mode.
*/
@@ -109,14 +100,6 @@
}
/**
- * Return {@code true} if desktop task stashing is enabled when going home.
- * Allows users to use home screen to add tasks to desktop.
- */
- public static boolean isStashingEnabled() {
- return IS_STASHING_ENABLED;
- }
-
- /**
* Return whether to use window shadows.
*
* @param isFocusedWindow whether the window to apply shadows to is focused
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 2d508b2..6bbc8fe 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
@@ -48,7 +48,6 @@
val activeTasks: ArraySet<Int> = ArraySet(),
val visibleTasks: ArraySet<Int> = ArraySet(),
val minimizedTasks: ArraySet<Int> = ArraySet(),
- var stashed: Boolean = false
)
// Token of the current wallpaper activity, used to remove it when the last task is removed
@@ -95,10 +94,8 @@
visibleTasksListeners[visibleTasksListener] = executor
displayData.keyIterator().forEach { displayId ->
val visibleTasksCount = getVisibleTaskCount(displayId)
- val stashed = isStashed(displayId)
executor.execute {
visibleTasksListener.onTasksVisibilityChanged(displayId, visibleTasksCount)
- visibleTasksListener.onStashedChanged(displayId, stashed)
}
}
}
@@ -400,26 +397,6 @@
}
/**
- * Update stashed status on display with id [displayId]
- */
- fun setStashed(displayId: Int, stashed: Boolean) {
- val data = displayData.getOrCreate(displayId)
- val oldValue = data.stashed
- data.stashed = stashed
- if (oldValue != stashed) {
- KtProtoLog.d(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTaskRepo: mark stashed=%b displayId=%d",
- stashed,
- displayId
- )
- visibleTasksListeners.forEach { (listener, executor) ->
- executor.execute { listener.onStashedChanged(displayId, stashed) }
- }
- }
- }
-
- /**
* Removes and returns the bounds saved before maximizing the given task.
*/
fun removeBoundsBeforeMaximize(taskId: Int): Rect? {
@@ -433,13 +410,6 @@
boundsBeforeMaximizeByTaskId.set(taskId, Rect(bounds))
}
- /**
- * Check if display with id [displayId] has desktop tasks stashed
- */
- fun isStashed(displayId: Int): Boolean {
- return displayData[displayId]?.stashed ?: false
- }
-
internal fun dump(pw: PrintWriter, prefix: String) {
val innerPrefix = "$prefix "
pw.println("${prefix}DesktopModeTaskRepository")
@@ -455,7 +425,6 @@
pw.println("${prefix}Display $displayId:")
pw.println("${innerPrefix}activeTasks=${data.activeTasks.toDumpString()}")
pw.println("${innerPrefix}visibleTasks=${data.visibleTasks.toDumpString()}")
- pw.println("${innerPrefix}stashed=${data.stashed}")
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index b0d5923..b2bdbfe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -240,34 +240,6 @@
}
}
- /**
- * Stash desktop tasks on display with id [displayId].
- *
- * When desktop tasks are stashed, launcher home screen icons are fully visible. New apps
- * launched in this state will be added to the desktop. Existing desktop tasks will be brought
- * back to front during the launch.
- */
- fun stashDesktopApps(displayId: Int) {
- if (DesktopModeStatus.isStashingEnabled()) {
- KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: stashDesktopApps")
- desktopModeTaskRepository.setStashed(displayId, true)
- }
- }
-
- /**
- * Clear the stashed state for the given display
- */
- fun hideStashedDesktopApps(displayId: Int) {
- if (DesktopModeStatus.isStashingEnabled()) {
- KtProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: hideStashedApps displayId=%d",
- displayId
- )
- desktopModeTaskRepository.setStashed(displayId, false)
- }
- }
-
/** Get number of tasks that are marked as visible */
fun getVisibleTaskCount(displayId: Int): Int {
return desktopModeTaskRepository.getVisibleTaskCount(displayId)
@@ -871,8 +843,6 @@
val result = triggerTask?.let { task ->
when {
request.type == TRANSIT_TO_BACK -> handleBackNavigation(task)
- // If display has tasks stashed, handle as stashed launch
- task.isStashed -> handleStashedTaskLaunch(task, transition)
// Check if the task has a top transparent activity
shouldLaunchAsModal(task) -> handleTransparentTaskLaunch(task)
// Check if fullscreen task should be updated
@@ -911,12 +881,8 @@
.forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) }
}
- private val TaskInfo.isStashed: Boolean
- get() = desktopModeTaskRepository.isStashed(displayId)
-
- private fun shouldLaunchAsModal(task: TaskInfo): Boolean {
- return Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task)
- }
+ private fun shouldLaunchAsModal(task: TaskInfo) =
+ Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task)
private fun shouldRemoveWallpaper(request: TransitionRequestInfo): Boolean {
return Flags.enableDesktopWindowingWallpaperActivity() &&
@@ -976,24 +942,6 @@
return null
}
- private fun handleStashedTaskLaunch(
- task: RunningTaskInfo,
- transition: IBinder
- ): WindowContainerTransaction {
- KtProtoLog.d(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: launch apps with stashed on transition taskId=%d",
- task.taskId
- )
- val wct = WindowContainerTransaction()
- val taskToMinimize =
- bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId)
- addMoveToDesktopChanges(wct, task)
- desktopModeTaskRepository.setStashed(task.displayId, false)
- addPendingMinimizeTransition(transition, taskToMinimize)
- return wct
- }
-
// Always launch transparent tasks in fullscreen.
private fun handleTransparentTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? {
// Already fullscreen, no-op.
@@ -1467,20 +1415,6 @@
) { c -> c.showDesktopApps(displayId, remoteTransition) }
}
- override fun stashDesktopApps(displayId: Int) {
- ExecutorUtils.executeRemoteCallWithTaskPermission(
- controller,
- "stashDesktopApps"
- ) { c -> c.stashDesktopApps(displayId) }
- }
-
- override fun hideStashedDesktopApps(displayId: Int) {
- ExecutorUtils.executeRemoteCallWithTaskPermission(
- controller,
- "hideStashedDesktopApps"
- ) { c -> c.hideStashedDesktopApps(displayId) }
- }
-
override fun showDesktopApp(taskId: Int) {
ExecutorUtils.executeRemoteCallWithTaskPermission(
controller,
@@ -1488,6 +1422,20 @@
) { c -> c.moveTaskToFront(taskId) }
}
+ override fun stashDesktopApps(displayId: Int) {
+ KtProtoLog.w(
+ WM_SHELL_DESKTOP_MODE,
+ "IDesktopModeImpl: stashDesktopApps is deprecated"
+ )
+ }
+
+ override fun hideStashedDesktopApps(displayId: Int) {
+ KtProtoLog.w(
+ WM_SHELL_DESKTOP_MODE,
+ "IDesktopModeImpl: hideStashedDesktopApps is deprecated"
+ )
+ }
+
override fun getVisibleTaskCount(displayId: Int): Int {
val result = IntArray(1)
ExecutorUtils.executeRemoteCallWithTaskPermission(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
index fa43522..c36f8de 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -28,10 +28,10 @@
/** Show apps on the desktop on the given display */
void showDesktopApps(int displayId, in RemoteTransition remoteTransition);
- /** Stash apps on the desktop to allow launching another app from home screen */
+ /** @deprecated use {@link #showDesktopApps} instead. */
void stashDesktopApps(int displayId);
- /** Hide apps that may be stashed */
+ /** @deprecated this is no longer supported. */
void hideStashedDesktopApps(int displayId);
/** Bring task with the given id to front */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index 7b1ef5c..a749019 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -39,7 +39,7 @@
* @param isSysUiStateValid Is SysUI state valid or not.
* @param flag Current SysUI state.
*/
- default void onSystemUiStateChanged(boolean isSysUiStateValid, int flag) {
+ default void onSystemUiStateChanged(boolean isSysUiStateValid, long flag) {
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 139cde2..85f9194 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -847,7 +847,7 @@
}
}
- private void onSystemUiStateChanged(boolean isValidState, int flag) {
+ private void onSystemUiStateChanged(boolean isValidState, long flag) {
mTouchHandler.onSystemUiStateChanged(isValidState);
}
@@ -1195,7 +1195,7 @@
}
@Override
- public void onSystemUiStateChanged(boolean isSysUiStateValid, int flag) {
+ public void onSystemUiStateChanged(boolean isSysUiStateValid, long flag) {
mMainExecutor.execute(() -> {
PipController.this.onSystemUiStateChanged(isSysUiStateValid, flag);
});
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index afae653..9c00864 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -668,6 +668,18 @@
Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton());
}
+ @Test
+ public void testLetterboxEduLayout_notCreatedWhenLetterboxEducationIsDisabled() {
+ TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
+ CAMERA_COMPAT_CONTROL_HIDDEN);
+ taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled = false;
+
+ mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
+
+ verify(mController, never()).createLetterboxEduWindowManager(any(), eq(taskInfo),
+ eq(mMockTaskListener));
+ }
+
private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat,
@CameraCompatControlState int cameraCompatControlState) {
return createTaskInfo(displayId, taskId, hasSizeCompat, cameraCompatControlState,
@@ -694,6 +706,8 @@
taskInfo.isVisible = isVisible;
taskInfo.isFocused = isFocused;
taskInfo.isTopActivityTransparent = isTopActivityTransparent;
+ taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled = true;
+ taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed = true;
return taskInfo;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index dca7be1..8f59f30 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -182,18 +182,6 @@
}
@Test
- fun addListener_notifiesStashed() {
- repo.setStashed(DEFAULT_DISPLAY, true)
- val listener = TestVisibilityListener()
- val executor = TestShellExecutor()
- repo.addVisibleTasksListener(listener, executor)
- executor.flushAll()
-
- assertThat(listener.stashedOnDefaultDisplay).isTrue()
- assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1)
- }
-
- @Test
fun addListener_tasksOnDifferentDisplay_doesNotNotify() {
repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = true)
val listener = TestVisibilityListener()
@@ -400,65 +388,6 @@
}
@Test
- fun setStashed_stateIsUpdatedForTheDisplay() {
- repo.setStashed(DEFAULT_DISPLAY, true)
- assertThat(repo.isStashed(DEFAULT_DISPLAY)).isTrue()
- assertThat(repo.isStashed(SECOND_DISPLAY)).isFalse()
-
- repo.setStashed(DEFAULT_DISPLAY, false)
- assertThat(repo.isStashed(DEFAULT_DISPLAY)).isFalse()
- }
-
- @Test
- fun setStashed_notifyListener() {
- val listener = TestVisibilityListener()
- val executor = TestShellExecutor()
- repo.addVisibleTasksListener(listener, executor)
- repo.setStashed(DEFAULT_DISPLAY, true)
- executor.flushAll()
- assertThat(listener.stashedOnDefaultDisplay).isTrue()
- assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1)
-
- repo.setStashed(DEFAULT_DISPLAY, false)
- executor.flushAll()
- assertThat(listener.stashedOnDefaultDisplay).isFalse()
- assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(2)
- }
-
- @Test
- fun setStashed_secondCallDoesNotNotify() {
- val listener = TestVisibilityListener()
- val executor = TestShellExecutor()
- repo.addVisibleTasksListener(listener, executor)
- repo.setStashed(DEFAULT_DISPLAY, true)
- repo.setStashed(DEFAULT_DISPLAY, true)
- executor.flushAll()
- assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1)
- }
-
- @Test
- fun setStashed_tracksPerDisplay() {
- val listener = TestVisibilityListener()
- val executor = TestShellExecutor()
- repo.addVisibleTasksListener(listener, executor)
-
- repo.setStashed(DEFAULT_DISPLAY, true)
- executor.flushAll()
- assertThat(listener.stashedOnDefaultDisplay).isTrue()
- assertThat(listener.stashedOnSecondaryDisplay).isFalse()
-
- repo.setStashed(SECOND_DISPLAY, true)
- executor.flushAll()
- assertThat(listener.stashedOnDefaultDisplay).isTrue()
- assertThat(listener.stashedOnSecondaryDisplay).isTrue()
-
- repo.setStashed(DEFAULT_DISPLAY, false)
- executor.flushAll()
- assertThat(listener.stashedOnDefaultDisplay).isFalse()
- assertThat(listener.stashedOnSecondaryDisplay).isTrue()
- }
-
- @Test
fun removeFreeformTask_removesTaskBoundsBeforeMaximize() {
val taskId = 1
repo.saveBoundsBeforeMaximize(taskId, Rect(0, 0, 200, 200))
@@ -598,12 +527,6 @@
var visibleChangesOnDefaultDisplay = 0
var visibleChangesOnSecondaryDisplay = 0
- var stashedOnDefaultDisplay = false
- var stashedOnSecondaryDisplay = false
-
- var stashedChangesOnDefaultDisplay = 0
- var stashedChangesOnSecondaryDisplay = 0
-
override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {
when (displayId) {
DEFAULT_DISPLAY -> {
@@ -617,20 +540,6 @@
else -> fail("Visible task listener received unexpected display id: $displayId")
}
}
-
- override fun onStashedChanged(displayId: Int, stashed: Boolean) {
- when (displayId) {
- DEFAULT_DISPLAY -> {
- stashedOnDefaultDisplay = stashed
- stashedChangesOnDefaultDisplay++
- }
- SECOND_DISPLAY -> {
- stashedOnSecondaryDisplay = stashed
- stashedChangesOnDefaultDisplay++
- }
- else -> fail("Visible task listener received unexpected display id: $displayId")
- }
- }
}
companion object {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 3f76c4f..7e55628 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -1044,29 +1044,6 @@
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_fullscreenTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
- whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
-
- val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY)
- markTaskHidden(stashedFreeformTask)
-
- val fullscreenTask = createFullscreenTask(DEFAULT_DISPLAY)
-
- controller.stashDesktopApps(DEFAULT_DISPLAY)
-
- val result = controller.handleRequest(Binder(), createTransition(fullscreenTask))
- assertThat(result).isNotNull()
- result!!.assertReorderSequence(stashedFreeformTask, fullscreenTask)
- assertThat(result.changes[fullscreenTask.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FREEFORM)
-
- // Stashed state should be cleared
- assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse()
- }
-
- @Test
fun handleRequest_freeformTask_freeformVisible_returnNull() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
@@ -1133,27 +1110,6 @@
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_freeformTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() {
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
- whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
-
- val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY)
- markTaskHidden(stashedFreeformTask)
-
- val freeformTask = createFreeformTask(DEFAULT_DISPLAY)
-
- controller.stashDesktopApps(DEFAULT_DISPLAY)
-
- val result = controller.handleRequest(Binder(), createTransition(freeformTask))
- assertThat(result).isNotNull()
- result?.assertReorderSequence(stashedFreeformTask, freeformTask)
-
- // Stashed state should be cleared
- assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse()
- }
-
- @Test
fun handleRequest_notOpenOrToFrontTransition_returnNull() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
@@ -1269,29 +1225,6 @@
}
@Test
- fun stashDesktopApps_stateUpdates() {
- whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
-
- controller.stashDesktopApps(DEFAULT_DISPLAY)
-
- assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isTrue()
- assertThat(desktopModeTaskRepository.isStashed(SECOND_DISPLAY)).isFalse()
- }
-
- @Test
- fun hideStashedDesktopApps_stateUpdates() {
- whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
-
- desktopModeTaskRepository.setStashed(DEFAULT_DISPLAY, true)
- desktopModeTaskRepository.setStashed(SECOND_DISPLAY, true)
- controller.hideStashedDesktopApps(DEFAULT_DISPLAY)
-
- assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse()
- // Check that second display is not affected
- assertThat(desktopModeTaskRepository.isStashed(SECOND_DISPLAY)).isTrue()
- }
-
- @Test
fun desktopTasksVisibilityChange_visible_setLaunchAdjacentDisabled() {
val task = setUpFreeformTask()
clearInvocations(launchAdjacentController)
diff --git a/libs/androidfw/fuzz/resourcefile_fuzzer/Android.bp b/libs/androidfw/fuzz/resourcefile_fuzzer/Android.bp
index b511244..6196589 100644
--- a/libs/androidfw/fuzz/resourcefile_fuzzer/Android.bp
+++ b/libs/androidfw/fuzz/resourcefile_fuzzer/Android.bp
@@ -19,6 +19,7 @@
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_libs_androidfw_license"],
+ default_team: "trendy_team_android_resources",
}
cc_fuzz {
@@ -31,7 +32,7 @@
static_libs: ["libgmock"],
target: {
android: {
- shared_libs:[
+ shared_libs: [
"libandroidfw",
"libbase",
"libcutils",
@@ -52,4 +53,15 @@
],
},
},
+ fuzz_config: {
+ cc: [
+ "android-resources@google.com",
+ ],
+ componentid: 568761,
+ description: "The fuzzer targets the APIs of libandroidfw",
+ vector: "local_no_privileges_required",
+ service_privilege: "privileged",
+ users: "multi_user",
+ fuzzed_code_usage: "shipped",
+ },
}
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 6d4cc3a..cf7aea4 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -64,8 +64,10 @@
}
public final class NfcAdapter {
+ method @FlaggedApi("android.nfc.nfc_state_change") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable();
method public void disableForegroundDispatch(android.app.Activity);
method public void disableReaderMode(android.app.Activity);
+ method @FlaggedApi("android.nfc.nfc_state_change") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable();
method public void enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]);
method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle);
method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context);
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 310130e..a33e225 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -3,9 +3,7 @@
public final class NfcAdapter {
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean addNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler, String[]);
- method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable();
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable(boolean);
- method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable();
method @FlaggedApi("android.nfc.enable_nfc_reader_option") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableReaderOption(boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean);
method @FlaggedApi("android.nfc.enable_nfc_mainline") public int getAdapterState();
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index e43d104..06098de 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -1106,6 +1106,9 @@
* {@link #ACTION_ADAPTER_STATE_CHANGED} broadcasts to find out when the
* operation is complete.
*
+ * <p>This API is only allowed to be called by system apps
+ * or apps which are Device Owner or Profile Owner.
+ *
* <p>If this returns true, then either NFC is already on, or
* a {@link #ACTION_ADAPTER_STATE_CHANGED} broadcast will be sent
* to indicate a state transition. If this returns false, then
@@ -1113,9 +1116,8 @@
* NFC on (for example we are in airplane mode and NFC is not
* toggleable in airplane mode on this platform).
*
- * @hide
*/
- @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_STATE_CHANGE)
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
public boolean enable() {
try {
@@ -1146,15 +1148,17 @@
* {@link #ACTION_ADAPTER_STATE_CHANGED} broadcasts to find out when the
* operation is complete.
*
+ * <p>This API is only allowed to be called by system apps
+ * or apps which are Device Owner or Profile Owner.
+ *
* <p>If this returns true, then either NFC is already off, or
* a {@link #ACTION_ADAPTER_STATE_CHANGED} broadcast will be sent
* to indicate a state transition. If this returns false, then
* there is some problem that prevents an attempt to turn
* NFC off.
*
- * @hide
*/
- @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_STATE_CHANGE)
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
public boolean disable() {
try {
diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig
index 73b29db..cb2a48c 100644
--- a/nfc/java/android/nfc/flags.aconfig
+++ b/nfc/java/android/nfc/flags.aconfig
@@ -93,3 +93,11 @@
description: "Enable NFC OEM extension support"
bug: "331206243"
}
+
+flag {
+ name: "nfc_state_change"
+ is_exported: true
+ namespace: "nfc"
+ description: "Enable nfc state change API"
+ bug: "319934052"
+}
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index 25ac3c9..635dc42 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -172,7 +172,7 @@
// This is for testing only now
private boolean mEnableWhenCompleted;
- private boolean mOneShot;
+ private boolean mOneShot = true;
private boolean mHideNotification;
private InstallationAsyncTask.Progress mInstallTaskProgress;
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index 4147813..e7823df 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -29,7 +29,7 @@
allprojects {
extra["androidTop"] = androidTop
- extra["jetpackComposeVersion"] = "1.7.0-alpha05"
+ extra["jetpackComposeVersion"] = "1.7.0-alpha08"
}
subprojects {
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index 6344501..4aa57b3 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -53,17 +53,17 @@
dependencies {
api(project(":SettingsLibColor"))
- api("androidx.appcompat:appcompat:1.7.0-alpha03")
+ api("androidx.appcompat:appcompat:1.7.0-beta01")
api("androidx.slice:slice-builders:1.1.0-alpha02")
api("androidx.slice:slice-core:1.1.0-alpha02")
api("androidx.slice:slice-view:1.1.0-alpha02")
- api("androidx.compose.material3:material3:1.3.0-alpha03")
+ api("androidx.compose.material3:material3:1.3.0-alpha06")
api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
api("androidx.lifecycle:lifecycle-livedata-ktx")
api("androidx.lifecycle:lifecycle-runtime-compose")
- api("androidx.navigation:navigation-compose:2.8.0-alpha05")
+ api("androidx.navigation:navigation-compose:2.8.0-alpha08")
api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
api("com.google.android.material:material:1.11.0")
debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt
index 88ba4b0..1a10bf0 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt
@@ -27,14 +27,13 @@
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
-const val URL_SPAN_TAG = "URL_SPAN_TAG"
-
@Composable
fun annotatedStringResource(@StringRes id: Int): AnnotatedString {
val resources = LocalContext.current.resources
@@ -97,12 +96,9 @@
start: Int,
end: Int,
) {
- addStyle(
- SpanStyle(color = urlSpanColor, textDecoration = TextDecoration.Underline),
- start,
- end,
+ val url = LinkAnnotation.Url(
+ url = urlSpan.url,
+ style = SpanStyle(color = urlSpanColor, textDecoration = TextDecoration.Underline),
)
- if (!urlSpan.url.isNullOrEmpty()) {
- addStringAnnotation(URL_SPAN_TAG, urlSpan.url, start, end)
- }
+ addLink(url, start, end)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/AnnotatedText.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/AnnotatedText.kt
index 82ac7e3..f864fa9 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/AnnotatedText.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/AnnotatedText.kt
@@ -17,26 +17,17 @@
package com.android.settingslib.spa.widget.ui
import androidx.annotation.StringRes
-import androidx.compose.foundation.text.ClickableText
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.ui.platform.LocalUriHandler
-import com.android.settingslib.spa.framework.util.URL_SPAN_TAG
import com.android.settingslib.spa.framework.util.annotatedStringResource
@Composable
fun AnnotatedText(@StringRes id: Int) {
- val uriHandler = LocalUriHandler.current
- val annotatedString = annotatedStringResource(id)
- ClickableText(
- text = annotatedString,
+ Text(
+ text = annotatedStringResource(id),
style = MaterialTheme.typography.bodyMedium.copy(
color = MaterialTheme.colorScheme.onSurfaceVariant,
),
- ) { offset ->
- // Gets the url at the clicked position.
- annotatedString.getStringAnnotations(URL_SPAN_TAG, offset, offset)
- .firstOrNull()
- ?.let { uriHandler.openUri(it.item) }
- }
+ )
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt
index 9928355..612f9e5 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt
@@ -19,6 +19,7 @@
import androidx.compose.material3.MaterialTheme
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
@@ -36,18 +37,23 @@
val composeTestRule = createComposeRule()
@Test
- fun testAnnotatedStringResource() {
+ fun annotatedStringResource() {
composeTestRule.setContent {
val annotatedString =
annotatedStringResource(R.string.test_annotated_string_resource)
- val annotations = annotatedString.getStringAnnotations(0, annotatedString.length)
+ val annotations = annotatedString.getLinkAnnotations(0, annotatedString.length)
assertThat(annotations).containsExactly(
AnnotatedString.Range(
- item = "https://www.android.com/",
+ item = LinkAnnotation.Url(
+ url = "https://www.android.com/",
+ style = SpanStyle(
+ color = MaterialTheme.colorScheme.primary,
+ textDecoration = TextDecoration.Underline,
+ ),
+ ),
start = 31,
end = 35,
- tag = URL_SPAN_TAG,
)
)
@@ -57,14 +63,6 @@
start = 22,
end = 26,
),
- AnnotatedString.Range(
- item = SpanStyle(
- color = MaterialTheme.colorScheme.primary,
- textDecoration = TextDecoration.Underline,
- ),
- start = 31,
- end = 35,
- ),
)
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
index 822a608..1040ac6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
@@ -36,6 +36,7 @@
import android.util.ArraySet;
import android.util.Log;
+import androidx.annotation.GuardedBy;
import androidx.annotation.VisibleForTesting;
import com.android.internal.telephony.SmsApplication;
@@ -56,13 +57,22 @@
private static PowerAllowlistBackend sInstance;
+ private final Object mAllowlistedAppsLock = new Object();
+ private final Object mSysAllowlistedAppsLock = new Object();
+ private final Object mDefaultActiveAppsLock = new Object();
+
private final Context mAppContext;
private final IDeviceIdleController mDeviceIdleService;
+
+ @GuardedBy("mAllowlistedAppsLock")
private final ArraySet<String> mAllowlistedApps = new ArraySet<>();
+ @GuardedBy("mSysAllowlistedAppsLock")
private final ArraySet<String> mSysAllowlistedApps = new ArraySet<>();
+ @GuardedBy("mDefaultActiveAppsLock")
private final ArraySet<String> mDefaultActiveApps = new ArraySet<>();
- public PowerAllowlistBackend(Context context) {
+ @VisibleForTesting
+ PowerAllowlistBackend(Context context) {
this(context, IDeviceIdleController.Stub.asInterface(
ServiceManager.getService(DEVICE_IDLE_SERVICE)));
}
@@ -75,24 +85,25 @@
}
public int getAllowlistSize() {
- return mAllowlistedApps.size();
- }
-
- /**
- * Check if target package is in System allow list
- */
- public boolean isSysAllowlisted(String pkg) {
- return mSysAllowlistedApps.contains(pkg);
- }
-
- /**
- * Check if target package is in allow list
- */
- public boolean isAllowlisted(String pkg, int uid) {
- if (mAllowlistedApps.contains(pkg)) {
- return true;
+ synchronized (mAllowlistedAppsLock) {
+ return mAllowlistedApps.size();
}
+ }
+ /** Check if target package is in System allow list */
+ public boolean isSysAllowlisted(String pkg) {
+ synchronized (mSysAllowlistedAppsLock) {
+ return mSysAllowlistedApps.contains(pkg);
+ }
+ }
+
+ /** Check if target package is in allow list */
+ public boolean isAllowlisted(String pkg, int uid) {
+ synchronized (mAllowlistedAppsLock) {
+ if (mAllowlistedApps.contains(pkg)) {
+ return true;
+ }
+ }
if (isDefaultActiveApp(pkg, uid)) {
return true;
}
@@ -100,16 +111,16 @@
return false;
}
- /**
- * Check if it is default active app in multiple area(i.e. SMS, Dialer, Device admin..)
- */
+ /** Check if it is default active app in multiple area */
public boolean isDefaultActiveApp(String pkg, int uid) {
// Additionally, check if pkg is default dialer/sms. They are considered essential apps and
// should be automatically allowlisted (otherwise user may be able to set restriction on
// them, leading to bad device behavior.)
- if (mDefaultActiveApps.contains(pkg)) {
- return true;
+ synchronized (mDefaultActiveAppsLock) {
+ if (mDefaultActiveApps.contains(pkg)) {
+ return true;
+ }
}
final DevicePolicyManager devicePolicyManager = mAppContext.getSystemService(
@@ -143,9 +154,7 @@
DEFAULT_SYSTEM_EXEMPT_POWER_RESTRICTIONS_ENABLED);
}
- /**
- * Check if target package is in allow list except idle app
- */
+ /** Check if target package is in allow list except idle app */
public boolean isAllowlistedExceptIdle(String pkg) {
try {
return mDeviceIdleService.isPowerSaveWhitelistExceptIdleApp(pkg);
@@ -156,6 +165,7 @@
}
/**
+ * Check if target package is in allow list except idle app
*
* @param pkgs a list of packageName
* @return true when one of package is in allow list
@@ -174,20 +184,21 @@
}
/**
- * Add app into power save allow list.
+ * Add app into power save allow list
+ *
* @param pkg packageName of the app
*/
- // TODO: Fix all callers to pass in UID
public void addApp(String pkg) {
addApp(pkg, Process.INVALID_UID);
}
/**
- * Add app into power save allow list.
+ * Add app into power save allow list
+ *
* @param pkg packageName of the app
* @param uid uid of the app
*/
- public void addApp(String pkg, int uid) {
+ public synchronized void addApp(String pkg, int uid) {
try {
if (android.app.Flags.appRestrictionsApi()) {
if (uid == Process.INVALID_UID) {
@@ -204,7 +215,9 @@
}
mDeviceIdleService.addPowerSaveWhitelistApp(pkg);
- mAllowlistedApps.add(pkg);
+ synchronized (mAllowlistedAppsLock) {
+ mAllowlistedApps.add(pkg);
+ }
} catch (RemoteException e) {
Log.w(TAG, "Unable to reach IDeviceIdleController", e);
} catch (NameNotFoundException e) {
@@ -213,7 +226,8 @@
}
/**
- * Remove package from power save allow list.
+ * Remove package from power save allow list
+ *
* @param pkg packageName of the app
*/
public void removeApp(String pkg) {
@@ -222,10 +236,11 @@
/**
* Remove package from power save allow list.
+ *
* @param pkg packageName of the app
* @param uid uid of the app
*/
- public void removeApp(String pkg, int uid) {
+ public synchronized void removeApp(String pkg, int uid) {
try {
if (android.app.Flags.appRestrictionsApi()) {
if (uid == Process.INVALID_UID) {
@@ -241,7 +256,9 @@
}
mDeviceIdleService.removePowerSaveWhitelistApp(pkg);
- mAllowlistedApps.remove(pkg);
+ synchronized (mAllowlistedAppsLock) {
+ mAllowlistedApps.remove(pkg);
+ }
} catch (RemoteException e) {
Log.w(TAG, "Unable to reach IDeviceIdleController", e);
} catch (NameNotFoundException e) {
@@ -249,25 +266,33 @@
}
}
- /**
- * Refresh all of lists
- */
+ /** Refresh all of lists */
@VisibleForTesting
- public void refreshList() {
- mSysAllowlistedApps.clear();
- mAllowlistedApps.clear();
- mDefaultActiveApps.clear();
+ public synchronized void refreshList() {
+ synchronized (mSysAllowlistedAppsLock) {
+ mSysAllowlistedApps.clear();
+ }
+ synchronized (mAllowlistedAppsLock) {
+ mAllowlistedApps.clear();
+ }
+ synchronized (mDefaultActiveAppsLock) {
+ mDefaultActiveApps.clear();
+ }
if (mDeviceIdleService == null) {
return;
}
try {
final String[] allowlistedApps = mDeviceIdleService.getFullPowerWhitelist();
- for (String app : allowlistedApps) {
- mAllowlistedApps.add(app);
+ synchronized (mAllowlistedAppsLock) {
+ for (String app : allowlistedApps) {
+ mAllowlistedApps.add(app);
+ }
}
final String[] sysAllowlistedApps = mDeviceIdleService.getSystemPowerWhitelist();
- for (String app : sysAllowlistedApps) {
- mSysAllowlistedApps.add(app);
+ synchronized (mSysAllowlistedAppsLock) {
+ for (String app : sysAllowlistedApps) {
+ mSysAllowlistedApps.add(app);
+ }
}
final boolean hasTelephony = mAppContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_TELEPHONY);
@@ -278,26 +303,28 @@
if (hasTelephony) {
if (defaultSms != null) {
- mDefaultActiveApps.add(defaultSms.getPackageName());
+ synchronized (mDefaultActiveAppsLock) {
+ mDefaultActiveApps.add(defaultSms.getPackageName());
+ }
}
if (!TextUtils.isEmpty(defaultDialer)) {
- mDefaultActiveApps.add(defaultDialer);
+ synchronized (mDefaultActiveAppsLock) {
+ mDefaultActiveApps.add(defaultDialer);
+ }
}
}
- } catch (RemoteException e) {
- Log.w(TAG, "Unable to reach IDeviceIdleController", e);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to invoke refreshList()", e);
}
}
- /**
- * @param context
- * @return a PowerAllowlistBackend object
- */
+ /** Get the {@link PowerAllowlistBackend} instance */
public static PowerAllowlistBackend getInstance(Context context) {
- if (sInstance == null) {
- sInstance = new PowerAllowlistBackend(context);
+ synchronized (PowerAllowlistBackend.class) {
+ if (sInstance == null) {
+ sInstance = new PowerAllowlistBackend(context);
+ }
+ return sInstance;
}
- return sInstance;
}
-
}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 626e219..0dd956c 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -891,4 +891,14 @@
metadata {
purpose: PURPOSE_BUGFIX
}
-}
\ No newline at end of file
+}
+
+flag {
+ name: "media_controls_user_initiated_dismiss"
+ namespace: "systemui"
+ description: "Only dismiss media notifications when the control was removed by the user."
+ bug: "335875159"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
index d4660fa..23df26f 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
@@ -23,6 +23,7 @@
import android.graphics.Matrix
import android.graphics.Rect
import android.graphics.RectF
+import android.os.Binder
import android.os.Build
import android.os.Handler
import android.os.Looper
@@ -36,7 +37,11 @@
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
+import android.view.WindowManager.TRANSIT_CLOSE
+import android.view.WindowManager.TRANSIT_TO_BACK
import android.view.animation.PathInterpolator
+import android.window.RemoteTransition
+import android.window.TransitionFilter
import androidx.annotation.AnyThread
import androidx.annotation.BinderThread
import androidx.annotation.UiThread
@@ -44,6 +49,9 @@
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.policy.ScreenDecorationsUtils
import com.android.systemui.Flags.activityTransitionUseLargestWindow
+import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary
+import com.android.wm.shell.shared.IShellTransitions
+import com.android.wm.shell.shared.ShellTransitions
import java.util.concurrent.Executor
import kotlin.math.roundToInt
@@ -59,6 +67,9 @@
/** The executor that runs on the main thread. */
private val mainExecutor: Executor,
+ /** The object used to register ephemeral returns and long-lived transitions. */
+ private val transitionRegister: TransitionRegister? = null,
+
/** The animator used when animating a View into an app. */
private val transitionAnimator: TransitionAnimator = defaultTransitionAnimator(mainExecutor),
@@ -74,6 +85,36 @@
// TODO(b/301385865): Remove this flag.
private val disableWmTimeout: Boolean = false,
) {
+ @JvmOverloads
+ constructor(
+ mainExecutor: Executor,
+ shellTransitions: ShellTransitions,
+ transitionAnimator: TransitionAnimator = defaultTransitionAnimator(mainExecutor),
+ dialogToAppAnimator: TransitionAnimator = defaultDialogToAppAnimator(mainExecutor),
+ disableWmTimeout: Boolean = false,
+ ) : this(
+ mainExecutor,
+ TransitionRegister.fromShellTransitions(shellTransitions),
+ transitionAnimator,
+ dialogToAppAnimator,
+ disableWmTimeout,
+ )
+
+ @JvmOverloads
+ constructor(
+ mainExecutor: Executor,
+ iShellTransitions: IShellTransitions,
+ transitionAnimator: TransitionAnimator = defaultTransitionAnimator(mainExecutor),
+ dialogToAppAnimator: TransitionAnimator = defaultDialogToAppAnimator(mainExecutor),
+ disableWmTimeout: Boolean = false,
+ ) : this(
+ mainExecutor,
+ TransitionRegister.fromIShellTransitions(iShellTransitions),
+ transitionAnimator,
+ dialogToAppAnimator,
+ disableWmTimeout,
+ )
+
companion object {
/** The timings when animating a View into an app. */
@JvmField
@@ -233,6 +274,10 @@
}
}
+ if (animationAdapter != null && controller.transitionCookie != null) {
+ registerEphemeralReturnAnimation(controller, transitionRegister)
+ }
+
val launchResult = intentStarter(animationAdapter)
// Only animate if the app is not already on top and will be opened, unless we are on the
@@ -302,6 +347,66 @@
}
}
+ /**
+ * Uses [transitionRegister] to set up the return animation for the given [launchController].
+ *
+ * De-registration is set up automatically once the return animation is run.
+ *
+ * TODO(b/339194555): automatically de-register when the launchable is detached.
+ */
+ private fun registerEphemeralReturnAnimation(
+ launchController: Controller,
+ transitionRegister: TransitionRegister?
+ ) {
+ if (!returnAnimationFrameworkLibrary()) return
+
+ var cleanUpRunnable: Runnable? = null
+ val returnRunner =
+ createRunner(
+ object : DelegateTransitionAnimatorController(launchController) {
+ override val isLaunching = false
+
+ override fun onTransitionAnimationCancelled(
+ newKeyguardOccludedState: Boolean?
+ ) {
+ super.onTransitionAnimationCancelled(newKeyguardOccludedState)
+ cleanUp()
+ }
+
+ override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
+ super.onTransitionAnimationEnd(isExpandingFullyAbove)
+ cleanUp()
+ }
+
+ private fun cleanUp() {
+ cleanUpRunnable?.run()
+ }
+ }
+ )
+
+ // mTypeSet and mModes match back signals only, and not home. This is on purpose, because
+ // we only want ephemeral return animations triggered in these scenarios.
+ val filter =
+ TransitionFilter().apply {
+ mTypeSet = intArrayOf(TRANSIT_CLOSE, TRANSIT_TO_BACK)
+ mRequirements =
+ arrayOf(
+ TransitionFilter.Requirement().apply {
+ mLaunchCookie = launchController.transitionCookie
+ mModes = intArrayOf(TRANSIT_CLOSE, TRANSIT_TO_BACK)
+ }
+ )
+ }
+ val transition =
+ RemoteTransition(
+ RemoteAnimationRunnerCompat.wrap(returnRunner),
+ "${launchController.transitionCookie}_returnTransition"
+ )
+
+ transitionRegister?.register(filter, transition)
+ cleanUpRunnable = Runnable { transitionRegister?.unregister(transition) }
+ }
+
/** Add a [Listener] that can listen to transition animations. */
fun addListener(listener: Listener) {
listeners.add(listener)
@@ -386,8 +491,14 @@
* Note: The background of [view] should be a (rounded) rectangle so that it can be
* properly animated.
*/
+ @JvmOverloads
@JvmStatic
- fun fromView(view: View, cujType: Int? = null): Controller? {
+ fun fromView(
+ view: View,
+ cujType: Int? = null,
+ cookie: TransitionCookie? = null,
+ returnCujType: Int? = null
+ ): Controller? {
// Make sure the View we launch from implements LaunchableView to avoid visibility
// issues.
if (view !is LaunchableView) {
@@ -408,7 +519,7 @@
return null
}
- return GhostedViewTransitionAnimatorController(view, cujType)
+ return GhostedViewTransitionAnimatorController(view, cujType, cookie, returnCujType)
}
}
@@ -432,6 +543,17 @@
get() = false
/**
+ * The cookie associated with the transition controlled by this [Controller].
+ *
+ * This should be defined for all return [Controller] (when [isLaunching] is false) and for
+ * their associated launch [Controller]s.
+ *
+ * For the recommended format, see [TransitionCookie].
+ */
+ val transitionCookie: TransitionCookie?
+ get() = null
+
+ /**
* The intent was started. If [willAnimate] is false, nothing else will happen and the
* animation will not be started.
*/
@@ -652,7 +774,7 @@
return
}
- val window = findRootTaskIfPossible(apps)
+ val window = findTargetWindowIfPossible(apps)
if (window == null) {
Log.i(TAG, "Aborting the animation as no window is opening")
callback?.invoke()
@@ -676,7 +798,7 @@
startAnimation(window, navigationBar, callback)
}
- private fun findRootTaskIfPossible(
+ private fun findTargetWindowIfPossible(
apps: Array<out RemoteAnimationTarget>?
): RemoteAnimationTarget? {
if (apps == null) {
@@ -694,6 +816,19 @@
for (it in apps) {
if (it.mode == targetMode) {
if (activityTransitionUseLargestWindow()) {
+ if (returnAnimationFrameworkLibrary()) {
+ // If the controller contains a cookie, _only_ match if the candidate
+ // contains the matching cookie.
+ if (
+ controller.transitionCookie != null &&
+ it.taskInfo
+ ?.launchCookies
+ ?.contains(controller.transitionCookie) != true
+ ) {
+ continue
+ }
+ }
+
if (
candidate == null ||
!it.hasAnimatingParent && candidate.hasAnimatingParent
@@ -806,11 +941,7 @@
progress: Float,
linearProgress: Float
) {
- // Apply the state to the window only if it is visible, i.e. when the
- // expanding view is *not* visible.
- if (!state.visible) {
- applyStateToWindow(window, state, linearProgress)
- }
+ applyStateToWindow(window, state, linearProgress)
navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) }
listener?.onTransitionAnimationProgress(linearProgress)
@@ -1048,4 +1179,72 @@
return (this.width() * this.height()) > (other.width() * other.height())
}
}
+
+ /**
+ * Wraps one of the two methods we have to register remote transitions with WM Shell:
+ * - for in-process registrations (e.g. System UI) we use [ShellTransitions]
+ * - for cross-process registrations (e.g. Launcher) we use [IShellTransitions]
+ *
+ * Important: each instance of this class must wrap exactly one of the two.
+ */
+ class TransitionRegister
+ private constructor(
+ private val shellTransitions: ShellTransitions? = null,
+ private val iShellTransitions: IShellTransitions? = null,
+ ) {
+ init {
+ assert((shellTransitions != null).xor(iShellTransitions != null))
+ }
+
+ companion object {
+ /** Provides a [TransitionRegister] instance wrapping [ShellTransitions]. */
+ fun fromShellTransitions(shellTransitions: ShellTransitions): TransitionRegister {
+ return TransitionRegister(shellTransitions = shellTransitions)
+ }
+
+ /** Provides a [TransitionRegister] instance wrapping [IShellTransitions]. */
+ fun fromIShellTransitions(iShellTransitions: IShellTransitions): TransitionRegister {
+ return TransitionRegister(iShellTransitions = iShellTransitions)
+ }
+ }
+
+ /** Register [remoteTransition] with WM Shell using the given [filter]. */
+ internal fun register(
+ filter: TransitionFilter,
+ remoteTransition: RemoteTransition,
+ ) {
+ shellTransitions?.registerRemote(filter, remoteTransition)
+ iShellTransitions?.registerRemote(filter, remoteTransition)
+ }
+
+ /** Unregister [remoteTransition] from WM Shell. */
+ internal fun unregister(remoteTransition: RemoteTransition) {
+ shellTransitions?.unregisterRemote(remoteTransition)
+ iShellTransitions?.unregisterRemote(remoteTransition)
+ }
+ }
+
+ /**
+ * A cookie used to uniquely identify a task launched using an
+ * [ActivityTransitionAnimator.Controller].
+ *
+ * The [String] encapsulated by this class should be formatted in such a way to be unique across
+ * the system, but reliably constant for the same associated launchable.
+ *
+ * Recommended naming scheme:
+ * - DO use the fully qualified name of the class that owns the instance of the launchable,
+ * along with a concise and precise description of the purpose of the launchable in question.
+ * - DO NOT introduce uniqueness through the use of timestamps or other runtime variables that
+ * will change if the instance is destroyed and re-created.
+ *
+ * Example: "com.not.the.real.class.name.ShadeController_openSettingsButton"
+ *
+ * Note that sometimes (e.g. in recycler views) there could be multiple instances of the same
+ * launchable, and no static knowledge to adequately differentiate between them using a single
+ * description. In this case, the recommendation is to append a unique identifier related to the
+ * contents of the launchable.
+ *
+ * Example: “com.not.the.real.class.name.ToastWebResult_launchAga_id143256”
+ */
+ data class TransitionCookie(private val cookie: String) : Binder()
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
index e4bb2ad..21557b8 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
@@ -25,10 +25,30 @@
* [Expandable] into an Activity, or return `null` if this [Expandable] should not be animated
* (e.g. if it is currently not attached or visible).
*
- * @param cujType the CUJ type from the [com.android.internal.jank.InteractionJankMonitor]
+ * @param launchCujType The CUJ type from the [com.android.internal.jank.InteractionJankMonitor]
* associated to the launch that will use this controller.
+ * @param cookie The unique cookie associated with the launch that will use this controller.
+ * This is required iff the a return animation should be included.
+ * @param returnCujType The CUJ type from the [com.android.internal.jank.InteractionJankMonitor]
+ * associated to the return animation that will use this controller.
*/
- fun activityTransitionController(cujType: Int? = null): ActivityTransitionAnimator.Controller?
+ fun activityTransitionController(
+ launchCujType: Int? = null,
+ cookie: ActivityTransitionAnimator.TransitionCookie? = null,
+ returnCujType: Int? = null
+ ): ActivityTransitionAnimator.Controller?
+
+ /**
+ * See [activityTransitionController] above.
+ *
+ * Interfaces don't support [JvmOverloads], so this is a useful overload for Java usages that
+ * don't use the return-related parameters.
+ */
+ fun activityTransitionController(
+ launchCujType: Int? = null
+ ): ActivityTransitionAnimator.Controller? {
+ return activityTransitionController(launchCujType, cookie = null, returnCujType = null)
+ }
/**
* Create a [DialogTransitionAnimator.Controller] that can be used to expand this [Expandable]
@@ -48,9 +68,16 @@
fun fromView(view: View): Expandable {
return object : Expandable {
override fun activityTransitionController(
- cujType: Int?,
+ launchCujType: Int?,
+ cookie: ActivityTransitionAnimator.TransitionCookie?,
+ returnCujType: Int?
): ActivityTransitionAnimator.Controller? {
- return ActivityTransitionAnimator.Controller.fromView(view, cujType)
+ return ActivityTransitionAnimator.Controller.fromView(
+ view,
+ launchCujType,
+ cookie,
+ returnCujType
+ )
}
override fun dialogTransitionController(
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
index fd79f62..9d45073 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
@@ -59,8 +59,12 @@
/** The view that will be ghosted and from which the background will be extracted. */
private val ghostedView: View,
- /** The [CujType] associated to this animation. */
- private val cujType: Int? = null,
+ /** The [CujType] associated to this launch animation. */
+ private val launchCujType: Int? = null,
+ override val transitionCookie: ActivityTransitionAnimator.TransitionCookie? = null,
+
+ /** The [CujType] associated to this return animation. */
+ private val returnCujType: Int? = null,
private var interactionJankMonitor: InteractionJankMonitor =
InteractionJankMonitor.getInstance(),
) : ActivityTransitionAnimator.Controller {
@@ -104,6 +108,15 @@
*/
private val background: Drawable?
+ /** CUJ identifier accounting for whether this controller is for a launch or a return. */
+ private val cujType: Int?
+ get() =
+ if (isLaunching) {
+ launchCujType
+ } else {
+ returnCujType
+ }
+
init {
// Make sure the View we launch from implements LaunchableView to avoid visibility issues.
if (ghostedView !is LaunchableView) {
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
index c7f0a96..17a6061 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
@@ -134,13 +134,15 @@
override val expandable: Expandable =
object : Expandable {
override fun activityTransitionController(
- cujType: Int?,
+ launchCujType: Int?,
+ cookie: ActivityTransitionAnimator.TransitionCookie?,
+ returnCujType: Int?
): ActivityTransitionAnimator.Controller? {
if (!isComposed.value) {
return null
}
- return activityController(cujType)
+ return activityController(launchCujType, cookie, returnCujType)
}
override fun dialogTransitionController(
@@ -262,10 +264,27 @@
}
/** Create an [ActivityTransitionAnimator.Controller] that can be used to animate activities. */
- private fun activityController(cujType: Int?): ActivityTransitionAnimator.Controller {
+ private fun activityController(
+ launchCujType: Int?,
+ cookie: ActivityTransitionAnimator.TransitionCookie?,
+ returnCujType: Int?
+ ): ActivityTransitionAnimator.Controller {
val delegate = transitionController()
return object :
ActivityTransitionAnimator.Controller, TransitionAnimator.Controller by delegate {
+ /**
+ * CUJ identifier accounting for whether this controller is for a launch or a return.
+ */
+ private val cujType: Int?
+ get() =
+ if (isLaunching) {
+ launchCujType
+ } else {
+ returnCujType
+ }
+
+ override val transitionCookie = cookie
+
override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
delegate.onTransitionAnimationStart(isExpandingFullyAbove)
overlay.value = composeViewRoot.rootView.overlay as ViewGroupOverlay
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt
index 79d17ef..44b221c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt
@@ -16,6 +16,7 @@
package com.android.systemui.volume.panel.component.anc.ui.composable
+import android.view.Gravity
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -27,9 +28,15 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.clearAndSetSemantics
@@ -39,6 +46,7 @@
import androidx.compose.ui.unit.dp
import com.android.systemui.res.R
import com.android.systemui.volume.panel.component.anc.ui.viewmodel.AncViewModel
+import com.android.systemui.volume.panel.component.popup.ui.composable.VolumePanelPopup
import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent
import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope
import javax.inject.Inject
@@ -54,15 +62,22 @@
override fun VolumePanelComposeScope.Content(modifier: Modifier) {
val slice by viewModel.buttonSlice.collectAsState()
val label = stringResource(R.string.volume_panel_noise_control_title)
+ val screenWidth: Float =
+ with(LocalDensity.current) { LocalConfiguration.current.screenWidthDp.dp.toPx() }
+ var gravity by remember { mutableIntStateOf(Gravity.CENTER_HORIZONTAL) }
val isClickable = viewModel.isClickable(slice)
val onClick =
if (isClickable) {
- { ancPopup.show(null) }
+ { with(ancPopup) { show(null, gravity) } }
} else {
null
}
+
Column(
- modifier = modifier,
+ modifier =
+ modifier.onGloballyPositioned {
+ gravity = VolumePanelPopup.calculateGravity(it, screenWidth)
+ },
verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
index e1ee01e..d53dbf9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
@@ -16,6 +16,7 @@
package com.android.systemui.volume.panel.component.anc.ui.composable
+import android.view.Gravity
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.MaterialTheme
@@ -47,9 +48,10 @@
) {
/** Shows a popup with the [expandable] animation. */
- fun show(expandable: Expandable?) {
+ fun show(expandable: Expandable?, horizontalGravity: Int) {
uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_ANC_POPUP_SHOWN)
- volumePanelPopup.show(expandable, { Title() }, { Content(it) })
+ val gravity = horizontalGravity or Gravity.BOTTOM
+ volumePanelPopup.show(expandable, gravity, { Title() }, { Content(it) })
}
@Composable
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
index 0893b9d..f11c3a5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
@@ -16,6 +16,7 @@
package com.android.systemui.volume.panel.component.button.ui.composable
+import android.view.Gravity
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -29,8 +30,14 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
@@ -41,6 +48,7 @@
import com.android.systemui.animation.Expandable
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel
+import com.android.systemui.volume.panel.component.popup.ui.composable.VolumePanelPopup.Companion.calculateGravity
import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent
import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope
import kotlinx.coroutines.flow.StateFlow
@@ -48,7 +56,7 @@
/** [ComposeVolumePanelUiComponent] implementing a clickable button from a bottom row. */
class ButtonComponent(
private val viewModelFlow: StateFlow<ButtonViewModel?>,
- private val onClick: (Expandable) -> Unit
+ private val onClick: (expandable: Expandable, horizontalGravity: Int) -> Unit
) : ComposeVolumePanelUiComponent {
@Composable
@@ -57,8 +65,13 @@
val viewModel = viewModelByState ?: return
val label = viewModel.label.toString()
+ val screenWidth: Float =
+ with(LocalDensity.current) { LocalConfiguration.current.screenWidthDp.dp.toPx() }
+ var gravity by remember { mutableIntStateOf(Gravity.CENTER_HORIZONTAL) }
+
Column(
- modifier = modifier,
+ modifier =
+ modifier.onGloballyPositioned { gravity = calculateGravity(it, screenWidth) },
verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
@@ -82,7 +95,7 @@
} else {
MaterialTheme.colorScheme.onSurface
},
- onClick = onClick,
+ onClick = { onClick(it, gravity) },
) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
index bb4e957..3b1bf2a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
@@ -17,6 +17,7 @@
package com.android.systemui.volume.panel.component.popup.ui.composable
import android.view.Gravity
+import androidx.annotation.GravityInt
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -31,6 +32,8 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.boundsInRoot
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.paneTitle
@@ -60,13 +63,14 @@
*/
fun show(
expandable: Expandable?,
+ @GravityInt gravity: Int,
title: @Composable (SystemUIDialog) -> Unit,
content: @Composable (SystemUIDialog) -> Unit,
) {
val dialog =
dialogFactory.create(
theme = R.style.Theme_VolumePanel_Popup,
- dialogGravity = Gravity.BOTTOM,
+ dialogGravity = gravity,
) {
PopupComposable(it, title, content)
}
@@ -122,4 +126,23 @@
}
}
}
+
+ companion object {
+
+ /**
+ * Returns absolute ([Gravity.LEFT], [Gravity.RIGHT] or [Gravity.CENTER_HORIZONTAL])
+ * [GravityInt] for the popup based on the [coordinates] global position relative to the
+ * [screenWidthPx].
+ */
+ @GravityInt
+ fun calculateGravity(coordinates: LayoutCoordinates, screenWidthPx: Float): Int {
+ val bottomCenter: Float = coordinates.boundsInRoot().bottomCenter.x
+ val rootBottomCenter: Float = screenWidthPx / 2
+ return when {
+ bottomCenter < rootBottomCenter -> Gravity.LEFT
+ bottomCenter > rootBottomCenter -> Gravity.RIGHT
+ else -> Gravity.CENTER_HORIZONTAL
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
index 12d2bc2..d41acd9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
@@ -16,6 +16,7 @@
package com.android.systemui.volume.panel.component.spatialaudio.ui.composable
+import android.view.Gravity
import androidx.compose.foundation.basicMarquee
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
@@ -47,14 +48,15 @@
) {
/** Shows a popup with the [expandable] animation. */
- fun show(expandable: Expandable) {
+ fun show(expandable: Expandable, horizontalGravity: Int) {
uiEventLogger.logWithPosition(
VolumePanelUiEvent.VOLUME_PANEL_SPATIAL_AUDIO_POP_UP_SHOWN,
0,
null,
viewModel.spatialAudioButtons.value.indexOfFirst { it.button.isActive }
)
- volumePanelPopup.show(expandable, { Title() }, { Content(it) })
+ val gravity = horizontalGravity or Gravity.BOTTOM
+ volumePanelPopup.show(expandable, gravity, { Title() }, { Content(it) })
}
@Composable
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
index 1cdc2b6..407bf4c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
@@ -114,7 +114,7 @@
// Change to media unavailable and notify the listener.
whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(false)
- mediaDataListenerCaptor.value.onMediaDataRemoved("key")
+ mediaDataListenerCaptor.value.onMediaDataRemoved("key", false)
runCurrent()
// Media active now returns false.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
index d9224d7..bd3b77a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
@@ -27,12 +27,16 @@
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.bluetooth.mockBroadcastDialogController
+import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.data.repository.mediaDataRepository
import com.android.systemui.media.controls.domain.pipeline.MediaDataFilterImpl
+import com.android.systemui.media.controls.domain.pipeline.MediaDataProcessor
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaControlInteractor
import com.android.systemui.media.controls.domain.pipeline.interactor.mediaControlInteractor
import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter
+import com.android.systemui.media.controls.domain.pipeline.mediaDataProcessor
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.util.mediaInstanceId
import com.android.systemui.media.mediaOutputDialogManager
@@ -211,6 +215,21 @@
)
}
+ @Test
+ fun removeMediaControl() {
+ val listener = mock<MediaDataProcessor.Listener>()
+ kosmos.mediaDataProcessor.addInternalListener(listener)
+
+ var mediaData = MediaData(userId = USER_ID, instanceId = instanceId, artist = ARTIST)
+ kosmos.mediaDataRepository.addMediaEntry(KEY, mediaData)
+
+ underTest.removeMediaControl(null, instanceId, 0L)
+ kosmos.fakeExecutor.advanceClockToNext()
+ kosmos.fakeExecutor.runAllReady()
+
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(true))
+ }
+
companion object {
private const val USER_ID = 0
private const val KEY = "key"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 93465c8..677477d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -437,12 +437,12 @@
runCurrent()
assertThat(
sysUiState.flags and
- QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED != 0
+ QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED != 0L
)
.isTrue()
assertThat(
sysUiState.flags and
- QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING != 0
+ QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING != 0L
)
.isFalse()
@@ -450,12 +450,12 @@
runCurrent()
assertThat(
sysUiState.flags and
- QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED != 0
+ QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED != 0L
)
.isFalse()
assertThat(
sysUiState.flags and
- QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING != 0
+ QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING != 0L
)
.isTrue()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
new file mode 100644
index 0000000..8cb811d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
@@ -0,0 +1,117 @@
+/*
+ * 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
+
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.service.notification.NotificationListenerService
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.mockNotifCollection
+import com.android.systemui.statusbar.notification.collection.notifPipeline
+import com.android.systemui.statusbar.notification.collection.render.notificationVisibilityProvider
+import com.android.systemui.testKosmos
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NotificationMediaManagerTest : SysuiTestCase() {
+
+ private val KEY = "KEY"
+
+ private val kosmos = testKosmos()
+ private val visibilityProvider = kosmos.notificationVisibilityProvider
+ private val notifPipeline = kosmos.notifPipeline
+ private val notifCollection = kosmos.mockNotifCollection
+ private val dumpManager = kosmos.dumpManager
+ private val mediaDataManager = mock<MediaDataManager>()
+ private val backgroundExecutor = FakeExecutor(FakeSystemClock())
+
+ private var listenerCaptor = argumentCaptor<MediaDataManager.Listener>()
+
+ private lateinit var notificationMediaManager: NotificationMediaManager
+
+ @Before
+ fun setup() {
+ notificationMediaManager =
+ NotificationMediaManager(
+ context,
+ visibilityProvider,
+ notifPipeline,
+ notifCollection,
+ mediaDataManager,
+ dumpManager,
+ backgroundExecutor,
+ )
+
+ verify(mediaDataManager).addListener(listenerCaptor.capture())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DISMISS)
+ fun mediaDataRemoved_userInitiated_dismissNotif() {
+ val notifEntryCaptor = argumentCaptor<NotificationEntry>()
+ val notifEntry = mock<NotificationEntry>()
+ whenever(notifEntry.key).thenReturn(KEY)
+ whenever(notifEntry.ranking).thenReturn(NotificationListenerService.Ranking())
+ whenever(notifPipeline.allNotifs).thenReturn(listOf(notifEntry))
+
+ listenerCaptor.lastValue.onMediaDataRemoved(KEY, true)
+
+ verify(notifCollection).dismissNotification(notifEntryCaptor.capture(), any())
+ assertThat(notifEntryCaptor.lastValue.key).isEqualTo(KEY)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DISMISS)
+ fun mediaDataRemoved_notUserInitiated_doesNotDismissNotif() {
+ listenerCaptor.lastValue.onMediaDataRemoved(KEY, false)
+
+ verify(notifCollection, never()).dismissNotification(any(), any())
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DISMISS)
+ fun mediaDataRemoved_notUserInitiated_flagOff_dismissNotif() {
+ val notifEntryCaptor = argumentCaptor<NotificationEntry>()
+ val notifEntry = mock<NotificationEntry>()
+ whenever(notifEntry.key).thenReturn(KEY)
+ whenever(notifEntry.ranking).thenReturn(NotificationListenerService.Ranking())
+ whenever(notifPipeline.allNotifs).thenReturn(listOf(notifEntry))
+
+ listenerCaptor.lastValue.onMediaDataRemoved(KEY, false)
+
+ verify(notifCollection).dismissNotification(notifEntryCaptor.capture(), any())
+ assertThat(notifEntryCaptor.lastValue.key).isEqualTo(KEY)
+ }
+}
diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
index 76d10cc..a598007 100644
--- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
@@ -136,7 +136,8 @@
<TextView
android:id="@+id/bluetooth_auto_on_toggle_title"
android:layout_width="0dp"
- android:layout_height="68dp"
+ android:layout_height="wrap_content"
+ android:minHeight="68dp"
android:layout_marginBottom="20dp"
android:maxLines="2"
android:ellipsize="end"
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index d191a3c..d5bc10a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -65,7 +65,7 @@
/**
* Sent when some system ui state changes.
*/
- void onSystemUiStateChanged(int stateFlags) = 16;
+ void onSystemUiStateChanged(long stateFlags) = 16;
/**
* Sent when suggested rotation button could be shown
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 94b6fd4..090033d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -162,5 +162,10 @@
oneway void setOverrideHomeButtonLongPress(long duration, float slopMultiplier, boolean haptic)
= 55;
- // Next id = 56
+ /**
+ * Notifies to toggle quick settings panel.
+ */
+ oneway void toggleQuickSettingsPanel() = 56;
+
+ // Next id = 57
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 69aa909..b4377ea 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -22,7 +22,7 @@
import static com.android.systemui.shared.Flags.shadeAllowBackGesture;
-import android.annotation.IntDef;
+import android.annotation.LongDef;
import android.content.Context;
import android.content.res.Resources;
import android.view.ViewConfiguration;
@@ -51,92 +51,94 @@
// Overview is disabled, either because the device is in lock task mode, or because the device
// policy has disabled the feature
- public static final int SYSUI_STATE_SCREEN_PINNING = 1 << 0;
+ public static final long SYSUI_STATE_SCREEN_PINNING = 1L << 0;
// The navigation bar is hidden due to immersive mode
- public static final int SYSUI_STATE_NAV_BAR_HIDDEN = 1 << 1;
+ public static final long SYSUI_STATE_NAV_BAR_HIDDEN = 1L << 1;
// The notification panel is expanded and interactive (either locked or unlocked), and the
// quick settings is not expanded
- public static final int SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED = 1 << 2;
+ public static final long SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED = 1L << 2;
// The keyguard bouncer is showing
- public static final int SYSUI_STATE_BOUNCER_SHOWING = 1 << 3;
+ public static final long SYSUI_STATE_BOUNCER_SHOWING = 1L << 3;
// The navigation bar a11y button should be shown
- public static final int SYSUI_STATE_A11Y_BUTTON_CLICKABLE = 1 << 4;
+ public static final long SYSUI_STATE_A11Y_BUTTON_CLICKABLE = 1L << 4;
// The navigation bar a11y button shortcut is available
- public static final int SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE = 1 << 5;
+ public static final long SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE = 1L << 5;
// The keyguard is showing and not occluded
- public static final int SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING = 1 << 6;
+ public static final long SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING = 1L << 6;
// The recents feature is disabled (either by SUW/SysUI/device policy)
- public static final int SYSUI_STATE_OVERVIEW_DISABLED = 1 << 7;
+ public static final long SYSUI_STATE_OVERVIEW_DISABLED = 1L << 7;
// The home feature is disabled (either by SUW/SysUI/device policy)
- public static final int SYSUI_STATE_HOME_DISABLED = 1 << 8;
+ public static final long SYSUI_STATE_HOME_DISABLED = 1L << 8;
// The keyguard is showing, but occluded
- public static final int SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED = 1 << 9;
+ public static final long SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED = 1L << 9;
// The search feature is disabled (either by SUW/SysUI/device policy)
- public static final int SYSUI_STATE_SEARCH_DISABLED = 1 << 10;
+ public static final long SYSUI_STATE_SEARCH_DISABLED = 1L << 10;
// The notification panel is expanded and interactive (either locked or unlocked), and quick
// settings is expanded.
- public static final int SYSUI_STATE_QUICK_SETTINGS_EXPANDED = 1 << 11;
+ public static final long SYSUI_STATE_QUICK_SETTINGS_EXPANDED = 1L << 11;
// Winscope tracing is enabled
- public static final int SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION = 1 << 12;
+ public static final long SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION = 1L << 12;
// The Assistant gesture should be constrained. It is up to the launcher implementation to
// decide how to constrain it
- public static final int SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED = 1 << 13;
+ public static final long SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED = 1L << 13;
// The bubble stack is expanded. This means that the home gesture should be ignored, since a
// swipe up is an attempt to close the bubble stack, but that the back gesture should remain
// enabled (since it's used to navigate back within the bubbled app, or to collapse the bubble
// stack.
- public static final int SYSUI_STATE_BUBBLES_EXPANDED = 1 << 14;
+ public static final long SYSUI_STATE_BUBBLES_EXPANDED = 1L << 14;
// A SysUI dialog is showing.
- public static final int SYSUI_STATE_DIALOG_SHOWING = 1 << 15;
+ public static final long SYSUI_STATE_DIALOG_SHOWING = 1L << 15;
// The one-handed mode is active
- public static final int SYSUI_STATE_ONE_HANDED_ACTIVE = 1 << 16;
+ public static final long SYSUI_STATE_ONE_HANDED_ACTIVE = 1L << 16;
// Allow system gesture no matter the system bar(s) is visible or not
- public static final int SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY = 1 << 17;
+ public static final long SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY = 1L << 17;
// The IME is showing
- public static final int SYSUI_STATE_IME_SHOWING = 1 << 18;
+ public static final long SYSUI_STATE_IME_SHOWING = 1L << 18;
// The window magnification is overlapped with system gesture insets at the bottom.
- public static final int SYSUI_STATE_MAGNIFICATION_OVERLAP = 1 << 19;
+ public static final long SYSUI_STATE_MAGNIFICATION_OVERLAP = 1L << 19;
// ImeSwitcher is showing
- public static final int SYSUI_STATE_IME_SWITCHER_SHOWING = 1 << 20;
+ public static final long SYSUI_STATE_IME_SWITCHER_SHOWING = 1L << 20;
// Device dozing/AOD state
- public static final int SYSUI_STATE_DEVICE_DOZING = 1 << 21;
+ public static final long SYSUI_STATE_DEVICE_DOZING = 1L << 21;
// The home feature is disabled (either by SUW/SysUI/device policy)
- public static final int SYSUI_STATE_BACK_DISABLED = 1 << 22;
+ public static final long SYSUI_STATE_BACK_DISABLED = 1L << 22;
// The bubble stack is expanded AND the mange menu for bubbles is expanded on top of it.
- public static final int SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED = 1 << 23;
+ public static final long SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED = 1L << 23;
// The voice interaction session window is showing
- public static final int SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING = 1 << 25;
+ public static final long SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING = 1L << 25;
// Freeform windows are showing in desktop mode
- public static final int SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE = 1 << 26;
+ public static final long SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE = 1L << 26;
// Device dreaming state
- public static final int SYSUI_STATE_DEVICE_DREAMING = 1 << 27;
+ public static final long SYSUI_STATE_DEVICE_DREAMING = 1L << 27;
// Whether the device is currently awake (as opposed to asleep, see WakefulnessLifecycle).
// Note that the device is awake on while waking up on, but not while going to sleep.
- public static final int SYSUI_STATE_AWAKE = 1 << 28;
+ public static final long SYSUI_STATE_AWAKE = 1L << 28;
// Whether the device is currently transitioning between awake/asleep indicated by
// SYSUI_STATE_AWAKE.
- public static final int SYSUI_STATE_WAKEFULNESS_TRANSITION = 1 << 29;
+ public static final long SYSUI_STATE_WAKEFULNESS_TRANSITION = 1L << 29;
// The notification panel expansion fraction is > 0
- public static final int SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE = 1 << 30;
+ public static final long SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE = 1L << 30;
// When keyguard will be dismissed but didn't start animation yet
- public static final int SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY = 1 << 31;
+ public static final long SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY = 1L << 31;
+ // Physical keyboard shortcuts helper is showing
+ public static final long SYSUI_STATE_SHORTCUT_HELPER_SHOWING = 1L << 32;
// Mask for SystemUiStateFlags to isolate SYSUI_STATE_AWAKE and
// SYSUI_STATE_WAKEFULNESS_TRANSITION, to match WAKEFULNESS_* constants
- public static final int SYSUI_STATE_WAKEFULNESS_MASK =
+ public static final long SYSUI_STATE_WAKEFULNESS_MASK =
SYSUI_STATE_AWAKE | SYSUI_STATE_WAKEFULNESS_TRANSITION;
// Mirroring the WakefulnessLifecycle#Wakefulness states
- public static final int WAKEFULNESS_ASLEEP = 0;
- public static final int WAKEFULNESS_AWAKE = SYSUI_STATE_AWAKE;
- public static final int WAKEFULNESS_GOING_TO_SLEEP = SYSUI_STATE_WAKEFULNESS_TRANSITION;
- public static final int WAKEFULNESS_WAKING =
+ public static final long WAKEFULNESS_ASLEEP = 0;
+ public static final long WAKEFULNESS_AWAKE = SYSUI_STATE_AWAKE;
+ public static final long WAKEFULNESS_GOING_TO_SLEEP = SYSUI_STATE_WAKEFULNESS_TRANSITION;
+ public static final long WAKEFULNESS_WAKING =
SYSUI_STATE_WAKEFULNESS_TRANSITION | SYSUI_STATE_AWAKE;
// Whether the back gesture is allowed (or ignored) by the Shade
public static final boolean ALLOW_BACK_GESTURE_IN_SHADE = shadeAllowBackGesture();
@Retention(RetentionPolicy.SOURCE)
- @IntDef({SYSUI_STATE_SCREEN_PINNING,
+ @LongDef({SYSUI_STATE_SCREEN_PINNING,
SYSUI_STATE_NAV_BAR_HIDDEN,
SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
SYSUI_STATE_QUICK_SETTINGS_EXPANDED,
@@ -167,10 +169,11 @@
SYSUI_STATE_WAKEFULNESS_TRANSITION,
SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE,
SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY,
+ SYSUI_STATE_SHORTCUT_HELPER_SHOWING,
})
public @interface SystemUiStateFlags {}
- public static String getSystemUiStateString(int flags) {
+ public static String getSystemUiStateString(long flags) {
StringJoiner str = new StringJoiner("|");
if ((flags & SYSUI_STATE_SCREEN_PINNING) != 0) {
str.add("screen_pinned");
@@ -265,6 +268,9 @@
if ((flags & SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY) != 0) {
str.add("keygrd_going_away");
}
+ if ((flags & SYSUI_STATE_SHORTCUT_HELPER_SHOWING) != 0) {
+ str.add("shortcut_helper_showing");
+ }
return str.toString();
}
@@ -285,13 +291,13 @@
* Returns whether the specified sysui state is such that the assistant gesture should be
* disabled.
*/
- public static boolean isAssistantGestureDisabled(int sysuiStateFlags) {
+ public static boolean isAssistantGestureDisabled(long sysuiStateFlags) {
if ((sysuiStateFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0) {
sysuiStateFlags &= ~SYSUI_STATE_NAV_BAR_HIDDEN;
}
// Disable when in quick settings, screen pinning, immersive, the bouncer is showing,
// or search is disabled
- int disableFlags = SYSUI_STATE_SCREEN_PINNING
+ long disableFlags = SYSUI_STATE_SCREEN_PINNING
| SYSUI_STATE_NAV_BAR_HIDDEN
| SYSUI_STATE_BOUNCER_SHOWING
| SYSUI_STATE_SEARCH_DISABLED
@@ -313,7 +319,7 @@
* Returns whether the specified sysui state is such that the back gesture should be
* disabled.
*/
- public static boolean isBackGestureDisabled(int sysuiStateFlags, boolean forTrackpad) {
+ public static boolean isBackGestureDisabled(long sysuiStateFlags, boolean forTrackpad) {
// Always allow when the bouncer/global actions/voice session is showing (even on top of
// the keyguard)
if ((sysuiStateFlags & SYSUI_STATE_BOUNCER_SHOWING) != 0
@@ -328,9 +334,9 @@
return (sysuiStateFlags & getBackGestureDisabledMask(forTrackpad)) != 0;
}
- private static int getBackGestureDisabledMask(boolean forTrackpad) {
+ private static long getBackGestureDisabledMask(boolean forTrackpad) {
// Disable when in immersive, or the notifications are interactive
- int disableFlags = SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
+ long disableFlags = SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
if (!forTrackpad) {
disableFlags |= SYSUI_STATE_NAV_BAR_HIDDEN;
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
index dd8c0df..f0230be 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
@@ -204,11 +204,7 @@
isEnabled: Boolean,
@StringRes infoResId: Int
) {
- getAutoOnToggle(dialog).apply {
- isChecked = isEnabled
- setEnabled(true)
- alpha = ENABLED_ALPHA
- }
+ getAutoOnToggle(dialog).isChecked = isEnabled
getAutoOnToggleInfoTextView(dialog).text = dialog.context.getString(infoResId)
}
@@ -236,12 +232,8 @@
}
getAutoOnToggleView(dialog).visibility = initialUiProperties.autoOnToggleVisibility
- getAutoOnToggle(dialog).setOnCheckedChangeListener { view, isChecked ->
+ getAutoOnToggle(dialog).setOnCheckedChangeListener { _, isChecked ->
mutableBluetoothAutoOnToggle.value = isChecked
- view.apply {
- isEnabled = false
- alpha = DISABLED_ALPHA
- }
uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_AUTO_ON_TOGGLE_CLICKED)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index 5653bc2..2eca02c 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -31,9 +31,12 @@
import com.android.systemui.communal.domain.interactor.CommunalInteractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor;
import com.android.systemui.dock.DockManager;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -88,6 +91,8 @@
private final JavaAdapter mJavaAdapter;
private final SystemClock mSystemClock;
private final Lazy<SelectedUserInteractor> mUserInteractor;
+ private final Lazy<DeviceEntryInteractor> mDeviceEntryInteractor;
+ private final Lazy<SceneContainerOcclusionInteractor> mSceneContainerOcclusionInteractor;
private int mState;
private boolean mShowingAod;
@@ -170,7 +175,9 @@
JavaAdapter javaAdapter,
SystemClock systemClock,
Lazy<SelectedUserInteractor> userInteractor,
- Lazy<CommunalInteractor> communalInteractorLazy) {
+ Lazy<CommunalInteractor> communalInteractorLazy,
+ Lazy<DeviceEntryInteractor> deviceEntryInteractor,
+ Lazy<SceneContainerOcclusionInteractor> sceneContainerOcclusionInteractor) {
mFalsingDataProvider = falsingDataProvider;
mFalsingManager = falsingManager;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -186,6 +193,8 @@
mSystemClock = systemClock;
mUserInteractor = userInteractor;
mCommunalInteractorLazy = communalInteractorLazy;
+ mDeviceEntryInteractor = deviceEntryInteractor;
+ mSceneContainerOcclusionInteractor = sceneContainerOcclusionInteractor;
}
@Override
@@ -196,7 +205,18 @@
mStatusBarStateController.addCallback(mStatusBarStateListener);
mState = mStatusBarStateController.getState();
- mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
+ if (SceneContainerFlag.isEnabled()) {
+ mJavaAdapter.alwaysCollectFlow(
+ mDeviceEntryInteractor.get().isDeviceEntered(),
+ this::isDeviceEnteredChanged
+ );
+ mJavaAdapter.alwaysCollectFlow(
+ mSceneContainerOcclusionInteractor.get().getInvisibleDueToOcclusion(),
+ this::isInvisibleDueToOcclusionChanged
+ );
+ } else {
+ mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
+ }
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
@@ -216,6 +236,14 @@
mDockManager.addListener(mDockEventListener);
}
+ public void isDeviceEnteredChanged(boolean unused) {
+ updateSensorRegistration();
+ }
+
+ public void isInvisibleDueToOcclusionChanged(boolean unused) {
+ updateSensorRegistration();
+ }
+
@Override
public void onSuccessfulUnlock() {
logDebug("REAL: onSuccessfulUnlock");
@@ -302,7 +330,7 @@
@Override
public void onTouchEvent(MotionEvent ev) {
logDebug("REAL: onTouchEvent(" + MotionEvent.actionToString(ev.getActionMasked()) + ")");
- if (!mKeyguardStateController.isShowing()) {
+ if (!isKeyguardShowing()) {
avoidGesture();
return;
}
@@ -402,8 +430,8 @@
final boolean isKeyguard = mState == StatusBarState.KEYGUARD;
final boolean isShadeOverOccludedKeyguard = mState == StatusBarState.SHADE
- && mKeyguardStateController.isShowing()
- && mKeyguardStateController.isOccluded();
+ && isKeyguardShowing()
+ && isKeyguardOccluded();
return mScreenOn && !mShowingAod && (isKeyguard || isShadeOverOccludedKeyguard);
}
@@ -447,6 +475,32 @@
mFalsingManager.onProximityEvent(new ProximityEventImpl(proximityEvent));
}
+ /**
+ * Returns {@code true} if the keyguard is showing (whether or not the screen is on, whether or
+ * not an activity is occluding the keyguard, and whether or not the shade is open on top of the
+ * keyguard), or {@code false} if the user has dismissed the keyguard by authenticating or
+ * swiping up.
+ */
+ private boolean isKeyguardShowing() {
+ if (SceneContainerFlag.isEnabled()) {
+ return !mDeviceEntryInteractor.get().isDeviceEntered().getValue();
+ } else {
+ return mKeyguardStateController.isShowing();
+ }
+ }
+
+ /**
+ * Returns {@code true} if there is an activity display on top of ("occluding") the keyguard, or
+ * {@code false} if an activity is not occluding the keyguard (including if the keyguard is not
+ * showing at all).
+ */
+ private boolean isKeyguardOccluded() {
+ if (SceneContainerFlag.isEnabled()) {
+ return mSceneContainerOcclusionInteractor.get().getInvisibleDueToOcclusion().getValue();
+ } else {
+ return mKeyguardStateController.isOccluded();
+ }
+ }
static void logDebug(String msg) {
if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
index e2fed6d..e5a0e50 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
@@ -53,7 +53,7 @@
updateMediaModel(data)
}
- override fun onMediaDataRemoved(key: String) {
+ override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
updateMediaModel()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index 7f3274c..7655d7a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -247,7 +247,7 @@
state: TransitionState
) {
if (updateTransitionId != transitionId) {
- Log.wtf(TAG, "Attempting to update with old/invalid transitionId: $transitionId")
+ Log.w(TAG, "Attempting to update with old/invalid transitionId: $transitionId")
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LockscreenSceneTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LockscreenSceneTransitionRepository.kt
new file mode 100644
index 0000000..80bdc65
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LockscreenSceneTransitionRepository.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.systemui.keyguard.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+@SysUISingleton
+class LockscreenSceneTransitionRepository @Inject constructor() {
+
+ /**
+ * This [KeyguardState] will indicate which sub state within KTF should be navigated to when the
+ * next transition into the Lockscreen scene is started. It will be consumed exactly once and
+ * after that the state will be set back to [DEFAULT_STATE].
+ */
+ val nextLockscreenTargetState: MutableStateFlow<KeyguardState> = MutableStateFlow(DEFAULT_STATE)
+
+ companion object {
+ val DEFAULT_STATE = KeyguardState.LOCKSCREEN
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 2c05d49..30c6718 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -17,6 +17,8 @@
package com.android.systemui.keyguard.domain.interactor
+import android.annotation.FloatRange
+import android.annotation.SuppressLint
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -34,6 +36,7 @@
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.util.kotlin.pairwise
+import java.util.UUID
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -76,6 +79,8 @@
* single state. This prevent the redundant filters from running.
*/
private val transitionValueCache = mutableMapOf<KeyguardState, MutableSharedFlow<Float>>()
+
+ @SuppressLint("SharedFlowCreation")
private fun getTransitionValueFlow(state: KeyguardState): MutableSharedFlow<Float> {
return transitionValueCache.getOrPut(state) {
MutableSharedFlow<Float>(
@@ -90,6 +95,9 @@
@Deprecated("Not performant - Use something else in this class")
val transitions = repository.transitions
+ val transitionState: StateFlow<TransitionStep> =
+ transitions.stateIn(scope, SharingStarted.Eagerly, TransitionStep())
+
/**
* A pair of the most recent STARTED step, and the transition step immediately preceding it. The
* transition framework enforces that the previous step is either a CANCELED or FINISHED step,
@@ -99,6 +107,7 @@
* FINISHED. In the case of a CANCELED step, we can also figure out which state we were coming
* from when we were canceled.
*/
+ @SuppressLint("SharedFlowCreation")
val startedStepWithPrecedingStep =
repository.transitions
.pairwise()
@@ -144,9 +153,10 @@
}
/** Given an [edge], return a SharedFlow to collect only relevant [TransitionStep]. */
+ @SuppressLint("SharedFlowCreation")
fun getOrCreateFlow(edge: Edge): MutableSharedFlow<TransitionStep> {
return transitionMap.getOrPut(edge) {
- MutableSharedFlow<TransitionStep>(
+ MutableSharedFlow(
extraBufferCapacity = 10,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
@@ -180,6 +190,7 @@
* AOD<->* transition information, mapped to dozeAmount range of AOD (1f) <->
* * (0f).
*/
+ @SuppressLint("SharedFlowCreation")
val dozeAmountTransition: Flow<TransitionStep> =
repository.transitions
.filter { step -> step.from == AOD || step.to == AOD }
@@ -201,11 +212,20 @@
repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED }
/** The destination state of the last [TransitionState.STARTED] transition. */
+ @SuppressLint("SharedFlowCreation")
val startedKeyguardState: SharedFlow<KeyguardState> =
startedKeyguardTransitionStep
.map { step -> step.to }
.shareIn(scope, SharingStarted.Eagerly, replay = 1)
+ /** The from state of the last [TransitionState.STARTED] transition. */
+ // TODO: is it performant to have several SharedFlows side by side instead of one?
+ @SuppressLint("SharedFlowCreation")
+ val startedKeyguardFromState: SharedFlow<KeyguardState> =
+ startedKeyguardTransitionStep
+ .map { step -> step.from }
+ .shareIn(scope, SharingStarted.Eagerly, replay = 1)
+
/** Which keyguard state to use when the device goes to sleep. */
val asleepKeyguardState: StateFlow<KeyguardState> =
keyguardRepository.isAodAvailable
@@ -243,6 +263,7 @@
* sufficient. However, if you're having issues with state *during* transitions started after
* one or more canceled transitions, you probably need to use [currentKeyguardState].
*/
+ @SuppressLint("SharedFlowCreation")
val finishedKeyguardState: SharedFlow<KeyguardState> =
finishedKeyguardTransitionStep
.map { step -> step.to }
@@ -491,7 +512,19 @@
return startedKeyguardState.replayCache.last()
}
+ fun getStartedFromState(): KeyguardState {
+ return startedKeyguardFromState.replayCache.last()
+ }
+
fun getFinishedState(): KeyguardState {
return finishedKeyguardState.replayCache.last()
}
+
+ suspend fun startTransition(info: TransitionInfo) = repository.startTransition(info)
+
+ fun updateTransition(
+ transitionId: UUID,
+ @FloatRange(from = 0.0, to = 1.0) value: Float,
+ state: TransitionState
+ ) = repository.updateTransition(transitionId, value, state)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index 2d944c6..9443570 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -103,6 +103,7 @@
KeyguardState.LOCKSCREEN -> true
KeyguardState.GONE -> true
KeyguardState.OCCLUDED -> true
+ KeyguardState.UNDEFINED -> true
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
index d95c38e..3c66186 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.domain.interactor
import com.android.systemui.CoreStartable
+import com.android.systemui.keyguard.domain.interactor.scenetransition.LockscreenSceneTransitionInteractor
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
@@ -31,6 +32,13 @@
abstract fun bind(impl: KeyguardTransitionCoreStartable): CoreStartable
@Binds
+ @IntoMap
+ @ClassKey(LockscreenSceneTransitionInteractor::class)
+ abstract fun bindLockscreenSceneTransitionInteractor(
+ impl: LockscreenSceneTransitionInteractor
+ ): CoreStartable
+
+ @Binds
@IntoSet
abstract fun fromPrimaryBouncer(
impl: FromPrimaryBouncerTransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
new file mode 100644
index 0000000..6e00aa7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor.scenetransition
+
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.LockscreenSceneTransitionRepository
+import com.android.systemui.keyguard.data.repository.LockscreenSceneTransitionRepository.Companion.DEFAULT_STATE
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.UNDEFINED
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
+import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.util.kotlin.pairwise
+import java.util.UUID
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+
+/**
+ * This class listens to scene framework scene transitions and manages keyguard transition framework
+ * (KTF) states accordingly.
+ *
+ * There are a few rules:
+ * - When scene framework is on a scene outside of Lockscreen, then KTF is in state UNDEFINED
+ * - When scene framework is on Lockscreen, KTF is allowed to change its scenes freely
+ * - When scene framework is transitioning away from Lockscreen, then KTF transitions to UNDEFINED
+ * and shares its progress.
+ * - When scene framework is transitioning to Lockscreen, then KTF starts a transition to LOCKSCREEN
+ * but it is allowed to interrupt this transition and transition to other internal KTF states
+ *
+ * There are a few notable differences between SceneTransitionLayout (STL) and KTF that require
+ * special treatment when synchronizing both state machines.
+ * - STL does not emit cancelations as KTF does
+ * - Both STL and KTF require state continuity, though the rules from where starting the next
+ * transition is allowed is different on each side:
+ * - STL has a concept of "currentScene" which can be chosen to be either A or B in a A -> B
+ * transition. The currentScene determines which transition can be started next. In KTF the
+ * currentScene is always the `to` state. Which means transitions can only be started from B.
+ * This also holds true when A -> B was canceled: the next transition needs to start from B.
+ * - KTF can not settle back in its from scene, instead it needs to cancel and start a reversed
+ * transition.
+ */
+@SysUISingleton
+class LockscreenSceneTransitionInteractor
+@Inject
+constructor(
+ val transitionInteractor: KeyguardTransitionInteractor,
+ @Application private val applicationScope: CoroutineScope,
+ private val sceneInteractor: SceneInteractor,
+ private val repository: LockscreenSceneTransitionRepository,
+) : CoreStartable, SceneInteractor.OnSceneAboutToChangeListener {
+
+ private var currentTransitionId: UUID? = null
+ private var progressJob: Job? = null
+
+ override fun start() {
+ sceneInteractor.registerSceneStateProcessor(this)
+ listenForSceneTransitionProgress()
+ }
+
+ override fun onSceneAboutToChange(toScene: SceneKey, sceneState: Any?) {
+ if (toScene != Scenes.Lockscreen || sceneState == null) return
+ if (sceneState !is KeyguardState) {
+ throw IllegalArgumentException("Lockscreen sceneState needs to be a KeyguardState.")
+ }
+ repository.nextLockscreenTargetState.value = sceneState
+ }
+
+ private fun listenForSceneTransitionProgress() {
+ applicationScope.launch {
+ sceneInteractor.transitionState
+ .pairwise(ObservableTransitionState.Idle(Scenes.Lockscreen))
+ .collect { (prevTransition, transition) ->
+ when (transition) {
+ is ObservableTransitionState.Idle -> handleIdle(prevTransition, transition)
+ is ObservableTransitionState.Transition -> handleTransition(transition)
+ }
+ }
+ }
+ }
+
+ private suspend fun handleIdle(
+ prevTransition: ObservableTransitionState,
+ idle: ObservableTransitionState.Idle
+ ) {
+ if (currentTransitionId == null) return
+ if (prevTransition !is ObservableTransitionState.Transition) return
+
+ if (idle.currentScene == prevTransition.toScene) {
+ finishCurrentTransition()
+ } else {
+ val targetState =
+ if (idle.currentScene == Scenes.Lockscreen) {
+ transitionInteractor.getStartedFromState()
+ } else {
+ UNDEFINED
+ }
+ finishReversedTransitionTo(targetState)
+ }
+ }
+
+ private fun finishCurrentTransition() {
+ transitionInteractor.updateTransition(currentTransitionId!!, 1f, FINISHED)
+ resetTransitionData()
+ }
+
+ private suspend fun finishReversedTransitionTo(state: KeyguardState) {
+ val newTransition =
+ TransitionInfo(
+ ownerName = this::class.java.simpleName,
+ from = transitionInteractor.getStartedState(),
+ to = state,
+ animator = null,
+ modeOnCanceled = TransitionModeOnCanceled.REVERSE
+ )
+ currentTransitionId = transitionInteractor.startTransition(newTransition)
+ transitionInteractor.updateTransition(currentTransitionId!!, 1f, FINISHED)
+ resetTransitionData()
+ }
+
+ private fun resetTransitionData() {
+ progressJob?.cancel()
+ progressJob = null
+ currentTransitionId = null
+ }
+
+ private suspend fun handleTransition(transition: ObservableTransitionState.Transition) {
+ if (transition.fromScene == Scenes.Lockscreen) {
+ if (currentTransitionId != null) {
+ val currentToState = transitionInteractor.getStartedState()
+ if (currentToState == UNDEFINED) {
+ transitionKtfTo(transitionInteractor.getStartedFromState())
+ }
+ }
+ startTransitionFromLockscreen()
+ collectProgress(transition)
+ } else if (transition.toScene == Scenes.Lockscreen) {
+ if (currentTransitionId != null) {
+ transitionKtfTo(UNDEFINED)
+ }
+ startTransitionToLockscreen()
+ collectProgress(transition)
+ } else {
+ transitionKtfTo(UNDEFINED)
+ }
+ }
+
+ private suspend fun transitionKtfTo(state: KeyguardState) {
+ val currentTransition = transitionInteractor.transitionState.value
+ if (currentTransition.isFinishedIn(state)) {
+ // This is already the state we want to be in
+ resetTransitionData()
+ } else if (currentTransition.isTransitioning(to = state)) {
+ finishCurrentTransition()
+ } else {
+ finishReversedTransitionTo(state)
+ }
+ }
+
+ private fun collectProgress(transition: ObservableTransitionState.Transition) {
+ progressJob?.cancel()
+ progressJob = applicationScope.launch { transition.progress.collect { updateProgress(it) } }
+ }
+
+ private suspend fun startTransitionToLockscreen() {
+ val newTransition =
+ TransitionInfo(
+ ownerName = this::class.java.simpleName,
+ from = UNDEFINED,
+ to = repository.nextLockscreenTargetState.value,
+ animator = null,
+ modeOnCanceled = TransitionModeOnCanceled.RESET
+ )
+ repository.nextLockscreenTargetState.value = DEFAULT_STATE
+ startTransition(newTransition)
+ }
+
+ private suspend fun startTransitionFromLockscreen() {
+ val currentState = transitionInteractor.getStartedState()
+ val newTransition =
+ TransitionInfo(
+ ownerName = this::class.java.simpleName,
+ from = currentState,
+ to = UNDEFINED,
+ animator = null,
+ modeOnCanceled = TransitionModeOnCanceled.RESET
+ )
+ startTransition(newTransition)
+ }
+
+ private suspend fun startTransition(transitionInfo: TransitionInfo) {
+ if (currentTransitionId != null) {
+ resetTransitionData()
+ }
+ currentTransitionId = transitionInteractor.startTransition(transitionInfo)
+ }
+
+ private fun updateProgress(progress: Float) {
+ if (currentTransitionId == null) return
+ transitionInteractor.updateTransition(
+ currentTransitionId!!,
+ progress.coerceIn(0f, 1f),
+ RUNNING
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
index 7d05539..6d96db3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
@@ -17,7 +17,7 @@
/** List of all possible states to transition to/from */
enum class KeyguardState {
- /*
+ /**
* The display is completely off, as well as any sensors that would trigger the device to wake
* up.
*/
@@ -29,13 +29,13 @@
* notifications is enabled, allowing the device to quickly wake up.
*/
DOZING,
- /*
+ /**
* A device state after the device times out, which can be from both LOCKSCREEN or GONE states.
* DOZING is an example of special version of this state. Dreams may be implemented by third
* parties to present their own UI over keyguard, like a screensaver.
*/
DREAMING,
- /*
+ /**
* A device state after the device times out, which can be from both LOCKSCREEN or GONE states.
* It is a special version of DREAMING state but not DOZING. The active dream will be windowless
* and hosted in the lockscreen.
@@ -47,17 +47,17 @@
* low-power mode without a UI, then it is DOZING.
*/
AOD,
- /*
+ /**
* The security screen prompt containing UI to prompt the user to use a biometric credential
* (ie: fingerprint). When supported, this may show before showing the primary bouncer.
*/
ALTERNATE_BOUNCER,
- /*
+ /**
* The security screen prompt UI, containing PIN, Password, Pattern for the user to verify their
* credentials.
*/
PRIMARY_BOUNCER,
- /*
+ /**
* Device is actively displaying keyguard UI and is not in low-power mode. Device may be
* unlocked if SWIPE security method is used, or if face lockscreen bypass is false.
*/
@@ -68,15 +68,20 @@
* or dream, as well as swipe down for the notifications and up for the bouncer.
*/
GLANCEABLE_HUB,
- /*
- * Keyguard is no longer visible. In most cases the user has just authenticated and keyguard
- * is being removed, but there are other cases where the user is swiping away keyguard, such as
+ /**
+ * Keyguard is no longer visible. In most cases the user has just authenticated and keyguard is
+ * being removed, but there are other cases where the user is swiping away keyguard, such as
* with SWIPE security method or face unlock without bypass.
*/
GONE,
- /*
- * An activity is displaying over the keyguard.
+ /**
+ * Only used in scene framework. This means we are currently on any scene framework scene that
+ * is not Lockscreen. Transitions to and from UNDEFINED are always bound to the
+ * [SceneTransitionLayout] scene transition that either transitions to or from the Lockscreen
+ * scene. These transitions are automatically handled by [LockscreenSceneTransitionInteractor].
*/
+ UNDEFINED,
+ /** An activity is displaying over the keyguard. */
OCCLUDED;
companion object {
@@ -109,6 +114,7 @@
LOCKSCREEN -> true
GONE -> true
OCCLUDED -> true
+ UNDEFINED -> true
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
index 0fa6f4f..2b4c4af 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
@@ -30,4 +30,12 @@
value: Float,
transitionState: TransitionState,
) : this(info.from, info.to, value, transitionState, info.ownerName)
+
+ fun isTransitioning(from: KeyguardState? = null, to: KeyguardState? = null): Boolean {
+ return (from == null || this.from == from) && (to == null || this.to == to)
+ }
+
+ fun isFinishedIn(state: KeyguardState): Boolean {
+ return to == state && transitionState == TransitionState.FINISHED
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
index 87324a2..6f8389f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
@@ -117,7 +117,8 @@
KeyguardState.DOZING,
KeyguardState.DREAMING,
KeyguardState.PRIMARY_BOUNCER,
- KeyguardState.AOD -> emit(0f)
+ KeyguardState.AOD,
+ KeyguardState.UNDEFINED -> emit(0f)
KeyguardState.ALTERNATE_BOUNCER,
KeyguardState.LOCKSCREEN -> emit(1f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index 53b2697..ae83c9e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -148,10 +148,11 @@
KeyguardState.GLANCEABLE_HUB,
KeyguardState.GONE,
KeyguardState.OCCLUDED,
- KeyguardState.DREAMING_LOCKSCREEN_HOSTED, -> 0f
+ KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+ KeyguardState.UNDEFINED, -> 0f
KeyguardState.AOD,
KeyguardState.ALTERNATE_BOUNCER,
- KeyguardState.LOCKSCREEN -> 1f
+ KeyguardState.LOCKSCREEN, -> 1f
}
}
val useBackgroundProtection: StateFlow<Boolean> = isUdfpsSupported
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImpl.kt
index c02478b..96ef7d2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImpl.kt
@@ -206,11 +206,11 @@
listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) }
}
- override fun onMediaDataRemoved(key: String) {
+ override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
allEntries.remove(key)
userEntries.remove(key)?.let {
// Only notify listeners if something actually changed
- listeners.forEach { it.onMediaDataRemoved(key) }
+ listeners.forEach { it.onMediaDataRemoved(key, userInitiated) }
}
}
@@ -246,7 +246,7 @@
// Only remove media when the profile is unavailable.
if (DEBUG) Log.d(TAG, "Removing $key after profile change")
userEntries.remove(key, data)
- listeners.forEach { listener -> listener.onMediaDataRemoved(key) }
+ listeners.forEach { listener -> listener.onMediaDataRemoved(key, false) }
}
}
}
@@ -261,7 +261,7 @@
userEntries.clear()
keyCopy.forEach {
if (DEBUG) Log.d(TAG, "Removing $it after user change")
- listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it) }
+ listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it, false) }
}
allEntries.forEach { (key, data) ->
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
index 3a831156..143d66b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
@@ -545,8 +545,8 @@
* External listeners registered with [addListener] will be notified after the event propagates
* through the internal listener pipeline.
*/
- private fun notifyMediaDataRemoved(key: String) {
- internalListeners.forEach { it.onMediaDataRemoved(key) }
+ private fun notifyMediaDataRemoved(key: String, userInitiated: Boolean = false) {
+ internalListeners.forEach { it.onMediaDataRemoved(key, userInitiated) }
}
/**
@@ -578,7 +578,7 @@
if (it.active == !timedOut && !forceUpdate) {
if (it.resumption) {
if (DEBUG) Log.d(TAG, "timing out resume player $key")
- dismissMediaData(key, 0L /* delay */)
+ dismissMediaData(key, delay = 0L, userInitiated = false)
}
return
}
@@ -627,17 +627,17 @@
}
}
- private fun removeEntry(key: String, logEvent: Boolean = true) {
+ private fun removeEntry(key: String, logEvent: Boolean = true, userInitiated: Boolean = false) {
mediaEntries.remove(key)?.let {
if (logEvent) {
logger.logMediaRemoved(it.appUid, it.packageName, it.instanceId)
}
}
- notifyMediaDataRemoved(key)
+ notifyMediaDataRemoved(key, userInitiated)
}
/** Dismiss a media entry. Returns false if the key was not found. */
- override fun dismissMediaData(key: String, delay: Long): Boolean {
+ override fun dismissMediaData(key: String, delay: Long, userInitiated: Boolean): Boolean {
val existed = mediaEntries[key] != null
backgroundExecutor.execute {
mediaEntries[key]?.let { mediaData ->
@@ -649,7 +649,10 @@
}
}
}
- foregroundExecutor.executeDelayed({ removeEntry(key) }, delay)
+ foregroundExecutor.executeDelayed(
+ { removeEntry(key = key, userInitiated = userInitiated) },
+ delay
+ )
return existed
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt
index ad70db5..88910f9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt
@@ -53,8 +53,8 @@
listeners.toSet().forEach { it.onSmartspaceMediaDataLoaded(key, data) }
}
- override fun onMediaDataRemoved(key: String) {
- remove(key)
+ override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
+ remove(key, userInitiated)
}
override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
@@ -71,8 +71,8 @@
}
}
- override fun onKeyRemoved(key: String) {
- remove(key)
+ override fun onKeyRemoved(key: String, userInitiated: Boolean) {
+ remove(key, userInitiated)
}
/**
@@ -92,10 +92,10 @@
}
}
- private fun remove(key: String) {
+ private fun remove(key: String, userInitiated: Boolean) {
entries.remove(key)?.let {
val listenersCopy = listeners.toSet()
- listenersCopy.forEach { it.onMediaDataRemoved(key) }
+ listenersCopy.forEach { it.onMediaDataRemoved(key, userInitiated) }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
index 5432a18..8d19ce8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
@@ -213,7 +213,7 @@
listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) }
}
- override fun onMediaDataRemoved(key: String) {
+ override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
mediaFilterRepository.removeMediaEntry(key)?.let { mediaData ->
val instanceId = mediaData.instanceId
mediaFilterRepository.removeSelectedUserMediaEntry(instanceId)?.let {
@@ -221,7 +221,7 @@
MediaDataLoadingModel.Removed(instanceId)
)
// Only notify listeners if something actually changed
- listeners.forEach { it.onMediaDataRemoved(key) }
+ listeners.forEach { it.onMediaDataRemoved(key, userInitiated) }
}
}
}
@@ -270,7 +270,7 @@
mediaFilterRepository.addMediaDataLoadingState(
MediaDataLoadingModel.Removed(data.instanceId)
)
- listeners.forEach { listener -> listener.onMediaDataRemoved(key) }
+ listeners.forEach { listener -> listener.onMediaDataRemoved(key, false) }
}
}
}
@@ -288,7 +288,7 @@
MediaDataLoadingModel.Removed(instanceId)
)
getKey(instanceId)?.let {
- listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it) }
+ listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it, false) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt
index 2331aa21..8099e59 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt
@@ -60,7 +60,7 @@
)
/** Dismiss a media entry. Returns false if the key was not found. */
- fun dismissMediaData(key: String, delay: Long): Boolean
+ fun dismissMediaData(key: String, delay: Long, userInitiated: Boolean): Boolean
/**
* Called whenever the recommendation has been expired or removed by the user. This will remove
@@ -136,7 +136,7 @@
) {}
/** Called whenever a previously existing Media notification was removed. */
- override fun onMediaDataRemoved(key: String) {}
+ override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {}
/**
* Called whenever a previously existing Smartspace media data was removed.
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
index 1d7c025..eed7752 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
@@ -498,8 +498,8 @@
* External listeners registered with [MediaCarouselInteractor.addListener] will be notified
* after the event propagates through the internal listener pipeline.
*/
- private fun notifyMediaDataRemoved(key: String) {
- internalListeners.forEach { it.onMediaDataRemoved(key) }
+ private fun notifyMediaDataRemoved(key: String, userInitiated: Boolean = false) {
+ internalListeners.forEach { it.onMediaDataRemoved(key, userInitiated) }
}
/**
@@ -531,7 +531,7 @@
if (it.active == !timedOut && !forceUpdate) {
if (it.resumption) {
if (DEBUG) Log.d(TAG, "timing out resume player $key")
- dismissMediaData(key, 0L /* delay */)
+ dismissMediaData(key, delayMs = 0L, userInitiated = false)
}
return
}
@@ -580,17 +580,17 @@
}
}
- private fun removeEntry(key: String, logEvent: Boolean = true) {
+ private fun removeEntry(key: String, logEvent: Boolean = true, userInitiated: Boolean = false) {
mediaDataRepository.removeMediaEntry(key)?.let {
if (logEvent) {
logger.logMediaRemoved(it.appUid, it.packageName, it.instanceId)
}
}
- notifyMediaDataRemoved(key)
+ notifyMediaDataRemoved(key, userInitiated)
}
/** Dismiss a media entry. Returns false if the key was not found. */
- fun dismissMediaData(key: String, delayMs: Long): Boolean {
+ fun dismissMediaData(key: String, delayMs: Long, userInitiated: Boolean): Boolean {
val existed = mediaDataRepository.mediaEntries.value[key] != null
backgroundExecutor.execute {
mediaDataRepository.mediaEntries.value[key]?.let { mediaData ->
@@ -602,16 +602,19 @@
}
}
}
- foregroundExecutor.executeDelayed({ removeEntry(key) }, delayMs)
+ foregroundExecutor.executeDelayed(
+ { removeEntry(key, userInitiated = userInitiated) },
+ delayMs
+ )
return existed
}
/** Dismiss a media entry. Returns false if the corresponding key was not found. */
- fun dismissMediaData(instanceId: InstanceId, delayMs: Long): Boolean {
+ fun dismissMediaData(instanceId: InstanceId, delayMs: Long, userInitiated: Boolean): Boolean {
val mediaEntries = mediaDataRepository.mediaEntries.value
val filteredEntries = mediaEntries.filter { (_, data) -> data.instanceId == instanceId }
return if (filteredEntries.isNotEmpty()) {
- dismissMediaData(filteredEntries.keys.first(), delayMs)
+ dismissMediaData(filteredEntries.keys.first(), delayMs, userInitiated)
} else {
false
}
@@ -1579,7 +1582,7 @@
) {}
/** Called whenever a previously existing Media notification was removed. */
- fun onMediaDataRemoved(key: String) {}
+ fun onMediaDataRemoved(key: String, userInitiated: Boolean) {}
/**
* Called whenever a previously existing Smartspace media data was removed.
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
index 0e2814b..043fbfa 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
@@ -111,10 +111,10 @@
}
}
- override fun onMediaDataRemoved(key: String) {
+ override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
val token = entries.remove(key)
token?.stop()
- token?.let { listeners.forEach { it.onKeyRemoved(key) } }
+ token?.let { listeners.forEach { it.onKeyRemoved(key, userInitiated) } }
}
fun dump(pw: PrintWriter) {
@@ -136,7 +136,7 @@
/** Called when the route has changed for a given notification. */
fun onMediaDeviceChanged(key: String, oldKey: String?, data: MediaDeviceData?)
/** Called when the notification was removed. */
- fun onKeyRemoved(key: String)
+ fun onKeyRemoved(key: String, userInitiated: Boolean)
}
private inner class Entry(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt
index b2a8f2e..b178d84 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt
@@ -137,7 +137,7 @@
// farther and dismiss the media data so that media controls for the local session
// don't hang around while casting.
if (!keyedTokens.get(key)!!.contains(TokenId(remote.sessionToken))) {
- dispatchMediaDataRemoved(key)
+ dispatchMediaDataRemoved(key, userInitiated = false)
}
}
}
@@ -151,11 +151,11 @@
backgroundExecutor.execute { dispatchSmartspaceMediaDataLoaded(key, data) }
}
- override fun onMediaDataRemoved(key: String) {
+ override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
// Queue on background thread to ensure ordering of loaded and removed events is maintained.
backgroundExecutor.execute {
keyedTokens.remove(key)
- dispatchMediaDataRemoved(key)
+ dispatchMediaDataRemoved(key, userInitiated)
}
}
@@ -174,8 +174,10 @@
}
}
- private fun dispatchMediaDataRemoved(key: String) {
- foregroundExecutor.execute { listeners.toSet().forEach { it.onMediaDataRemoved(key) } }
+ private fun dispatchMediaDataRemoved(key: String, userInitiated: Boolean) {
+ foregroundExecutor.execute {
+ listeners.toSet().forEach { it.onMediaDataRemoved(key, userInitiated) }
+ }
}
private fun dispatchSmartspaceMediaDataLoaded(key: String, info: SmartspaceMediaData) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt
index 29f3967..fc31903 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt
@@ -169,7 +169,7 @@
mediaListeners[key] = PlaybackStateListener(key, data)
}
- override fun onMediaDataRemoved(key: String) {
+ override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
mediaListeners.remove(key)?.destroy()
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
index c888935..9e62300 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
@@ -205,12 +205,12 @@
)
}
- override fun dismissMediaData(key: String, delay: Long): Boolean {
- return mediaDataProcessor.dismissMediaData(key, delay)
+ override fun dismissMediaData(key: String, delay: Long, userInitiated: Boolean): Boolean {
+ return mediaDataProcessor.dismissMediaData(key, delay, userInitiated)
}
fun removeMediaControl(instanceId: InstanceId, delay: Long) {
- mediaDataProcessor.dismissMediaData(instanceId, delay)
+ mediaDataProcessor.dismissMediaData(instanceId, delay, userInitiated = false)
}
override fun dismissSmartspaceRecommendation(key: String, delay: Long) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
index 9f2d132..d1fee90 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
@@ -90,7 +90,8 @@
instanceId: InstanceId,
delayMs: Long
): Boolean {
- val dismissed = mediaDataProcessor.dismissMediaData(instanceId, delayMs)
+ val dismissed =
+ mediaDataProcessor.dismissMediaData(instanceId, delayMs, userInitiated = true)
if (!dismissed) {
Log.w(
TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index 45b68ca..b072534 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -193,6 +193,7 @@
private val mediaContent: ViewGroup
@VisibleForTesting var pageIndicator: PageIndicator
private var needsReordering: Boolean = false
+ private var isUserInitiatedRemovalQueued: Boolean = false
private var keysNeedRemoval = mutableSetOf<String>()
var shouldScrollToKey: Boolean = false
private var isRtl: Boolean = false
@@ -385,12 +386,15 @@
reorderAllPlayers(previousVisiblePlayerKey = null)
}
- keysNeedRemoval.forEach { removePlayer(it) }
+ keysNeedRemoval.forEach {
+ removePlayer(it, userInitiated = isUserInitiatedRemovalQueued)
+ }
if (keysNeedRemoval.size > 0) {
// Carousel visibility may need to be updated after late removals
updateHostVisibility()
}
keysNeedRemoval.clear()
+ isUserInitiatedRemovalQueued = false
// Update user visibility so that no extra impression will be logged when
// activeMediaIndex resets to 0
@@ -474,18 +478,18 @@
val canRemove = data.isPlaying?.let { !it } ?: data.isClearable && !data.active
if (canRemove && !Utils.useMediaResumption(context)) {
- // This view isn't playing, let's remove this! This happens e.g. when
- // dismissing/timing out a view. We still have the data around because
- // resumption could be on, but we should save the resources and release
- // this.
+ // This media control is both paused and timed out, and the resumption
+ // setting is off - let's remove it
if (isReorderingAllowed) {
- onMediaDataRemoved(key)
+ onMediaDataRemoved(key, userInitiated = MediaPlayerData.isSwipedAway)
} else {
+ isUserInitiatedRemovalQueued = MediaPlayerData.isSwipedAway
keysNeedRemoval.add(key)
}
} else {
keysNeedRemoval.remove(key)
}
+ MediaPlayerData.isSwipedAway = false
}
override fun onSmartspaceMediaDataLoaded(
@@ -565,11 +569,12 @@
addSmartspaceMediaRecommendations(key, data, shouldPrioritize)
}
}
+ MediaPlayerData.isSwipedAway = false
}
- override fun onMediaDataRemoved(key: String) {
- debugLogger.logMediaRemoved(key)
- removePlayer(key)
+ override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
+ debugLogger.logMediaRemoved(key, userInitiated)
+ removePlayer(key, userInitiated = userInitiated)
}
override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
@@ -1033,7 +1038,8 @@
fun removePlayer(
key: String,
dismissMediaData: Boolean = true,
- dismissRecommendation: Boolean = true
+ dismissRecommendation: Boolean = true,
+ userInitiated: Boolean = false,
): MediaControlPanel? {
if (key == MediaPlayerData.smartspaceMediaKey()) {
MediaPlayerData.smartspaceMediaData?.let {
@@ -1052,7 +1058,7 @@
if (dismissMediaData) {
// Inform the media manager of a potentially late dismissal
- mediaManager.dismissMediaData(key, delay = 0L)
+ mediaManager.dismissMediaData(key, delay = 0L, userInitiated = userInitiated)
}
if (dismissRecommendation) {
// Inform the media manager of a potentially late dismissal
@@ -1512,7 +1518,8 @@
}
}
- private fun onSwipeToDismiss() {
+ @VisibleForTesting
+ fun onSwipeToDismiss() {
if (mediaFlags.isMediaControlsRefactorEnabled()) {
mediaCarouselViewModel.onSwipeToDismiss()
return
@@ -1531,6 +1538,7 @@
it.mIsImpressed = false
}
}
+ MediaPlayerData.isSwipedAway = true
logger.logSwipeDismiss()
mediaManager.onSwipeToDismiss()
}
@@ -1557,6 +1565,7 @@
"state: ${desiredHostState?.expansion}, " +
"only active ${desiredHostState?.showsOnlyActiveMedia}"
)
+ println("isSwipedAway: ${MediaPlayerData.isSwipedAway}")
}
}
}
@@ -1595,7 +1604,7 @@
val data: MediaData,
val key: String,
val updateTime: Long = 0,
- val isSsReactivated: Boolean = false
+ val isSsReactivated: Boolean = false,
)
private val comparator =
@@ -1620,6 +1629,9 @@
// A map that tracks order of visible media players before they get reordered.
private val visibleMediaPlayers = LinkedHashMap<String, MediaSortKey>()
+ // Whether the user swiped away the carousel since its last update
+ internal var isSwipedAway: Boolean = false
+
fun addMediaPlayer(
key: String,
data: MediaData,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt
index ebf1c6a..1be25a7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt
@@ -53,8 +53,16 @@
{ "add player $str1, active: $bool1" }
)
- fun logMediaRemoved(key: String) =
- buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "removing player $str1" })
+ fun logMediaRemoved(key: String, userInitiated: Boolean) =
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = key
+ bool1 = userInitiated
+ },
+ { "removing player $str1, by user $bool1" }
+ )
fun logRecommendationLoaded(key: String, isActive: Boolean) =
buffer.log(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index e6c785e..0bc3c439 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -786,10 +786,11 @@
if (mKey != null) {
closeGuts();
if (!mMediaDataManagerLazy.get().dismissMediaData(mKey,
- MediaViewController.GUTS_ANIMATION_DURATION + 100)) {
+ /* delay */ MediaViewController.GUTS_ANIMATION_DURATION + 100,
+ /* userInitiated */ true)) {
Log.w(TAG, "Manager failed to dismiss media " + mKey);
// Remove directly from carousel so user isn't stuck with defunct controls
- mMediaCarouselController.removePlayer(mKey, false, false);
+ mMediaCarouselController.removePlayer(mKey, false, false, true);
}
} else {
Log.w(TAG, "Dismiss media with null notification. Token uid="
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
index eca76b6..91050c8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
@@ -105,7 +105,7 @@
updateViewVisibility()
}
- override fun onMediaDataRemoved(key: String) {
+ override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
updateViewVisibility()
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
index 88a5f78..061e7ec 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
@@ -48,7 +48,7 @@
}
@Override
- public void onMediaDataRemoved(@NonNull String key) {
+ public void onMediaDataRemoved(@NonNull String key, boolean userInitiated) {
final boolean hasActiveMedia = mMediaDataManager.hasActiveMedia();
if (DEBUG) {
Log.d(TAG, "onMediaDataRemoved(" + key + "), mAdded=" + mAdded + ", hasActiveMedia="
diff --git a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
index 89e4760..a144dc2 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
+++ b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
@@ -29,6 +29,7 @@
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags
import dagger.Lazy
import javax.inject.Inject
@@ -48,7 +49,7 @@
* Returns an override value for the given [flag] or `null` if the scene framework isn't enabled
* or if the flag value doesn't need to be overridden.
*/
- fun flagValueOverride(flag: Int): Boolean? {
+ fun flagValueOverride(@SystemUiStateFlags flag: Long): Boolean? {
if (!SceneContainerFlag.isEnabled) {
return null
}
@@ -79,7 +80,7 @@
* to be overridden by the scene framework.
*/
val EvaluatorByFlag =
- mapOf<Int, (SceneContainerPluginState) -> Boolean>(
+ mapOf<Long, (SceneContainerPluginState) -> Boolean>(
SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE to { it.scene != Scenes.Gone },
SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED to
{
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
index 2dd2327..481b476 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
@@ -23,6 +23,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import dalvik.annotation.optimization.NeverCompile;
@@ -42,10 +43,10 @@
private final DisplayTracker mDisplayTracker;
private final SceneContainerPlugin mSceneContainerPlugin;
- private @QuickStepContract.SystemUiStateFlags int mFlags;
+ private @SystemUiStateFlags long mFlags;
private final List<SysUiStateCallback> mCallbacks = new ArrayList<>();
- private int mFlagsToSet = 0;
- private int mFlagsToClear = 0;
+ private long mFlagsToSet = 0;
+ private long mFlagsToClear = 0;
public SysUiState(DisplayTracker displayTracker, SceneContainerPlugin sceneContainerPlugin) {
mDisplayTracker = displayTracker;
@@ -67,12 +68,13 @@
}
/** Returns the current sysui state flags. */
- public int getFlags() {
+ @SystemUiStateFlags
+ public long getFlags() {
return mFlags;
}
/** Methods to this call can be chained together before calling {@link #commitUpdate(int)}. */
- public SysUiState setFlag(int flag, boolean enabled) {
+ public SysUiState setFlag(@SystemUiStateFlags long flag, boolean enabled) {
final Boolean overrideOrNull = mSceneContainerPlugin.flagValueOverride(flag);
if (overrideOrNull != null && enabled != overrideOrNull) {
if (DEBUG) {
@@ -91,7 +93,7 @@
return this;
}
- /** Call to save all the flags updated from {@link #setFlag(int, boolean)}. */
+ /** Call to save all the flags updated from {@link #setFlag(long, boolean)}. */
public void commitUpdate(int displayId) {
updateFlags(displayId);
mFlagsToSet = 0;
@@ -105,14 +107,14 @@
return;
}
- int newState = mFlags;
+ long newState = mFlags;
newState |= mFlagsToSet;
newState &= ~mFlagsToClear;
notifyAndSetSystemUiStateChanged(newState, mFlags);
}
/** Notify all those who are registered that the state has changed. */
- private void notifyAndSetSystemUiStateChanged(int newFlags, int oldFlags) {
+ private void notifyAndSetSystemUiStateChanged(long newFlags, long oldFlags) {
if (DEBUG) {
Log.d(TAG, "SysUiState changed: old=" + oldFlags + " new=" + newFlags);
}
@@ -137,7 +139,7 @@
/** Callback to be notified whenever system UI state flags are changed. */
public interface SysUiStateCallback{
/** To be called when any SysUiStateFlag gets updated */
- void onSystemUiStateChanged(@QuickStepContract.SystemUiStateFlags int sysUiFlags);
+ void onSystemUiStateChanged(@SystemUiStateFlags long sysUiFlags);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt b/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt
index 5c49156..1e18f24 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt
@@ -40,7 +40,7 @@
*/
fun SysUiState.updateFlags(
@DisplayId displayId: Int,
- vararg flagValuePairs: Pair<Int, Boolean>,
+ vararg flagValuePairs: Pair<Long, Boolean>,
) {
flagValuePairs.forEach { (flag, enabled) -> setFlag(flag, enabled) }
commitUpdate(displayId)
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index a6b6d61..80c4379 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -128,7 +128,7 @@
private boolean mLongPressHomeEnabled;
private boolean mAssistantTouchGestureEnabled;
private int mNavBarMode;
- private int mA11yButtonState;
+ private long mA11yButtonState;
private int mRotationWatcherRotation;
private boolean mTogglingNavbarTaskbar;
private boolean mWallpaperVisible;
@@ -374,7 +374,7 @@
* {@link Secure#ACCESSIBILITY_BUTTON_MODE_GESTURE}, otherwise it is reset to 0.
*/
private void updateA11yState() {
- final int prevState = mA11yButtonState;
+ final long prevState = mA11yButtonState;
final boolean clickable;
final boolean longClickable;
if (mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()
@@ -431,7 +431,7 @@
* 48 = the combination of {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_CLICKABLE} and
* {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE}
*/
- public int getA11yButtonState() {
+ public long getA11yButtonState() {
return mA11yButtonState;
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 906ebad..0e819c2 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -1602,7 +1602,7 @@
void updateAccessibilityStateFlags() {
mLongPressHomeEnabled = mNavBarHelper.getLongPressHomeEnabled();
if (mView != null) {
- int a11yFlags = mNavBarHelper.getA11yButtonState();
+ long a11yFlags = mNavBarHelper.getA11yButtonState();
boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
mView.setAccessibilityButtonState(clickable, longClickable);
@@ -1611,7 +1611,7 @@
}
public void updateSystemUiStateFlags() {
- int a11yFlags = mNavBarHelper.getA11yButtonState();
+ long a11yFlags = mNavBarHelper.getA11yButtonState();
boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index f67973b..b360af0 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -298,7 +298,7 @@
}
private void updateSysuiFlags() {
- int a11yFlags = mNavBarHelper.getA11yButtonState();
+ long a11yFlags = mNavBarHelper.getA11yButtonState();
boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 295b293..9487085 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -84,6 +84,7 @@
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.shared.system.InputMonitorCompat;
import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -270,7 +271,8 @@
private BackAnimation mBackAnimation;
private int mLeftInset;
private int mRightInset;
- private int mSysUiFlags;
+ @SystemUiStateFlags
+ private long mSysUiFlags;
// For Tf-Lite model.
private BackGestureTfClassifierProvider mBackGestureTfClassifierProvider;
@@ -334,7 +336,7 @@
private final SysUiState.SysUiStateCallback mSysUiStateCallback =
new SysUiState.SysUiStateCallback() {
@Override
- public void onSystemUiStateChanged(int sysUiFlags) {
+ public void onSystemUiStateChanged(@SystemUiStateFlags long sysUiFlags) {
mSysUiFlags = sysUiFlags;
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index faf2bbc..e4cb211 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -108,6 +108,7 @@
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -117,6 +118,8 @@
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellInterface;
+import dagger.Lazy;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -128,8 +131,6 @@
import javax.inject.Inject;
import javax.inject.Provider;
-import dagger.Lazy;
-
/**
* Class to send information from overview to launcher with a binder.
*/
@@ -414,7 +415,13 @@
@Override
public void toggleNotificationPanel() {
verifyCallerAndClearCallingIdentityPostMain("toggleNotificationPanel", () ->
- mCommandQueue.togglePanel());
+ mCommandQueue.toggleNotificationsPanel());
+ }
+
+ @Override
+ public void toggleQuickSettingsPanel() {
+ verifyCallerAndClearCallingIdentityPostMain("toggleQuickSettingsPanel", () ->
+ mCommandQueue.toggleQuickSettingsPanel());
}
private boolean verifyCaller(String reason) {
@@ -765,7 +772,7 @@
}
}
- private void notifySystemUiStateFlags(int flags) {
+ private void notifySystemUiStateFlags(@SystemUiStateFlags long flags) {
if (SysUiState.DEBUG) {
Log.d(TAG_OPS, "Notifying sysui state change to overview service: proxy="
+ mOverviewProxy + " flags=" + flags);
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
index eabc42b..3e2c630 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
@@ -21,6 +21,7 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.TransitionKey
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneDataSource
@@ -36,6 +37,7 @@
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
+@SysUISingleton
/** Source of truth for scene framework application state. */
class SceneContainerRepository
@Inject
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 08efe39..0d0f6e0 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -55,6 +55,18 @@
private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
) {
+ interface OnSceneAboutToChangeListener {
+
+ /**
+ * Notifies that the scene is about to change to [toScene].
+ *
+ * The implementation can choose to consume the [sceneState] to prepare the incoming scene.
+ */
+ fun onSceneAboutToChange(toScene: SceneKey, sceneState: Any?)
+ }
+
+ private val onSceneAboutToChangeListener = mutableSetOf<OnSceneAboutToChangeListener>()
+
/**
* The current scene.
*
@@ -149,6 +161,10 @@
return repository.allSceneKeys()
}
+ fun registerSceneStateProcessor(processor: OnSceneAboutToChangeListener) {
+ onSceneAboutToChangeListener.add(processor)
+ }
+
/**
* Requests a scene change to the given scene.
*
@@ -161,6 +177,7 @@
toScene: SceneKey,
loggingReason: String,
transitionKey: TransitionKey? = null,
+ sceneState: Any? = null,
) {
val currentSceneKey = currentScene.value
if (
@@ -180,6 +197,7 @@
isInstant = false,
)
+ onSceneAboutToChangeListener.forEach { it.onSceneAboutToChange(toScene, sceneState) }
repository.changeScene(toScene, transitionKey)
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 4a64277..3ce12dd 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -234,7 +234,7 @@
bouncerInteractor.onImeHiddenByUser.collectLatest {
if (sceneInteractor.currentScene.value == Scenes.Bouncer) {
sceneInteractor.changeScene(
- toScene = Scenes.Lockscreen,
+ toScene = Scenes.Lockscreen, // TODO(b/336581871): add sceneState?
loggingReason = "IME hidden",
)
}
@@ -252,6 +252,7 @@
when {
isAnySimLocked -> {
switchToScene(
+ // TODO(b/336581871): add sceneState?
targetSceneKey = Scenes.Bouncer,
loggingReason = "Need to authenticate locked SIM card."
)
@@ -259,6 +260,7 @@
unlockStatus.isUnlocked &&
deviceEntryInteractor.canSwipeToEnter.value == false -> {
switchToScene(
+ // TODO(b/336581871): add sceneState?
targetSceneKey = Scenes.Gone,
loggingReason =
"All SIM cards unlocked and device already unlocked and " +
@@ -267,6 +269,7 @@
}
else -> {
switchToScene(
+ // TODO(b/336581871): add sceneState?
targetSceneKey = Scenes.Lockscreen,
loggingReason =
"All SIM cards unlocked and device still locked" +
@@ -325,7 +328,8 @@
Scenes.Gone to "device was unlocked in Bouncer scene"
} else {
val prevScene = previousScene.value
- (prevScene ?: Scenes.Gone) to
+ (prevScene
+ ?: Scenes.Gone) to
"device was unlocked in Bouncer scene, from sceneKey=$prevScene"
}
isOnLockscreen ->
@@ -364,6 +368,7 @@
powerInteractor.isAsleep.collect { isAsleep ->
if (isAsleep) {
switchToScene(
+ // TODO(b/336581871): add sceneState?
targetSceneKey = Scenes.Lockscreen,
loggingReason = "device is starting to sleep",
)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt
index bd93226..969cf48 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt
@@ -22,6 +22,8 @@
import android.graphics.Rect
import android.graphics.Region
import android.util.AttributeSet
+import android.view.GestureDetector
+import android.view.GestureDetector.SimpleOnGestureListener
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
@@ -43,13 +45,43 @@
private val displayMetrics = context.resources.displayMetrics
private val tmpRect = Rect()
private lateinit var actionsContainerBackground: View
+ private lateinit var actionsContainer: View
private lateinit var dismissButton: View
+ // Prepare an internal `GestureDetector` to determine when we can initiate a touch-interception
+ // session (with the client's provided `onTouchInterceptListener`). We delegate out to their
+ // listener only for gestures that can't be handled by scrolling our `actionsContainer`.
+ private val gestureDetector =
+ GestureDetector(
+ context,
+ object : SimpleOnGestureListener() {
+ override fun onScroll(
+ ev1: MotionEvent?,
+ ev2: MotionEvent,
+ distanceX: Float,
+ distanceY: Float
+ ): Boolean {
+ actionsContainer.getBoundsOnScreen(tmpRect)
+ val touchedInActionsContainer =
+ tmpRect.contains(ev2.rawX.toInt(), ev2.rawY.toInt())
+ val canHandleInternallyByScrolling =
+ touchedInActionsContainer
+ && actionsContainer.canScrollHorizontally(distanceX.toInt())
+ return !canHandleInternallyByScrolling
+ }
+ }
+ )
+
init {
- setOnTouchListener({ _: View, _: MotionEvent ->
+
+ // Delegate to the client-provided `onTouchInterceptListener` if we've already initiated
+ // touch-interception.
+ setOnTouchListener({ _: View, ev: MotionEvent ->
userInteractionCallback?.invoke()
- true
+ onTouchInterceptListener?.invoke(ev) ?: false
})
+
+ gestureDetector.setIsLongpressEnabled(false)
}
override fun onFinishInflate() {
@@ -60,7 +92,15 @@
blurredScreenshotPreview = requireViewById(R.id.screenshot_preview_blur)
screenshotStatic = requireViewById(R.id.screenshot_static)
actionsContainerBackground = requireViewById(R.id.actions_container_background)
+ actionsContainer = requireViewById(R.id.actions_container)
dismissButton = requireViewById(R.id.screenshot_dismiss_button)
+
+ // Configure to extend the timeout during ongoing gestures (i.e. scrolls) that are already
+ // being handled by our child views.
+ actionsContainer.setOnTouchListener({ _: View, ev: MotionEvent ->
+ userInteractionCallback?.invoke()
+ false
+ })
}
fun getTouchRegion(gestureInsets: Insets): Region {
@@ -171,9 +211,16 @@
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
userInteractionCallback?.invoke()
- if (onTouchInterceptListener?.invoke(ev) == true) {
- return true
+ // Let the client-provided listener see all `DOWN` events so that they'll be able to
+ // interpret the remainder of the gesture, even if interception starts partway-through.
+ // TODO: is this really necessary? And if we don't go on to start interception, should we
+ // follow up with `ACTION_CANCEL`?
+ if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ onTouchInterceptListener?.invoke(ev)
}
- return super.onInterceptTouchEvent(ev)
+
+ // Only allow the client-provided touch interceptor to take over the gesture if our
+ // top-level `GestureDetector` decides not to scroll the action container.
+ return gestureDetector.onTouchEvent(ev)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
index d2c93da..884ccef 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -140,7 +140,7 @@
private fun animateCollapseShadeInternal() {
sceneInteractor.changeScene(
- getCollapseDestinationScene(),
+ getCollapseDestinationScene(), // TODO(b/336581871): add sceneState?
"ShadeController.animateCollapseShade",
SlightlyFasterShadeCollapse,
)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt
index c9949cd..55bd8c6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt
@@ -44,6 +44,7 @@
} else {
Scenes.Shade
}
+ // TODO(b/336581871): add sceneState?
sceneInteractor.changeScene(key, "animateCollapseQs")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index d955349..c912616 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -134,7 +134,7 @@
private static final int MSG_DISMISS_KEYBOARD_SHORTCUTS = 32 << MSG_SHIFT;
private static final int MSG_HANDLE_SYSTEM_KEY = 33 << MSG_SHIFT;
private static final int MSG_SHOW_GLOBAL_ACTIONS = 34 << MSG_SHIFT;
- private static final int MSG_TOGGLE_PANEL = 35 << MSG_SHIFT;
+ private static final int MSG_TOGGLE_NOTIFICATION_PANEL = 35 << MSG_SHIFT;
private static final int MSG_SHOW_SHUTDOWN_UI = 36 << MSG_SHIFT;
private static final int MSG_SET_TOP_APP_HIDES_STATUS_BAR = 37 << MSG_SHIFT;
private static final int MSG_ROTATION_PROPOSAL = 38 << MSG_SHIFT;
@@ -180,6 +180,7 @@
private static final int MSG_SET_QS_TILES = 79 << MSG_SHIFT;
private static final int MSG_ENTER_DESKTOP = 80 << MSG_SHIFT;
private static final int MSG_SET_SPLITSCREEN_FOCUS = 81 << MSG_SHIFT;
+ private static final int MSG_TOGGLE_QUICK_SETTINGS_PANEL = 82 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
public static final int FLAG_EXCLUDE_RECENTS_PANEL = 1 << 1;
@@ -222,10 +223,33 @@
*/
default void disable(int displayId, @DisableFlags int state1, @Disable2Flags int state2,
boolean animate) { }
+
+ /**
+ * Called to expand Notifications panel with animation.
+ */
default void animateExpandNotificationsPanel() { }
+ /**
+ * Called to collapse Notifications panel with animation.
+ * @param flags Exclusion flags. See {@link FLAG_EXCLUDE_NONE}.
+ * @param force True to force the operation.
+ */
default void animateCollapsePanels(int flags, boolean force) { }
- default void togglePanel() { }
- default void animateExpandSettingsPanel(String obj) { }
+
+ /**
+ * Called to toggle Notifications panel.
+ */
+ default void toggleNotificationsPanel() { }
+
+ /**
+ * Called to expand Quick Settings panel with animation.
+ * @param subPanel subPanel one wish to expand.
+ */
+ default void animateExpandSettingsPanel(String subPanel) { }
+
+ /**
+ * Called to toggle Quick Settings panel.
+ */
+ default void toggleQuickSettingsPanel() { }
/**
* Called to notify IME window status changes.
@@ -696,10 +720,10 @@
}
}
- public void togglePanel() {
+ public void toggleNotificationsPanel() {
synchronized (mLock) {
- mHandler.removeMessages(MSG_TOGGLE_PANEL);
- mHandler.obtainMessage(MSG_TOGGLE_PANEL, 0, 0).sendToTarget();
+ mHandler.removeMessages(MSG_TOGGLE_NOTIFICATION_PANEL);
+ mHandler.obtainMessage(MSG_TOGGLE_NOTIFICATION_PANEL, 0, 0).sendToTarget();
}
}
@@ -710,6 +734,13 @@
}
}
+ public void toggleQuickSettingsPanel() {
+ synchronized (mLock) {
+ mHandler.removeMessages(MSG_TOGGLE_QUICK_SETTINGS_PANEL);
+ mHandler.obtainMessage(MSG_TOGGLE_QUICK_SETTINGS_PANEL, 0, 0).sendToTarget();
+ }
+ }
+
@Override
public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
boolean showImeSwitcher) {
@@ -1494,9 +1525,9 @@
mCallbacks.get(i).animateCollapsePanels(msg.arg1, msg.arg2 != 0);
}
break;
- case MSG_TOGGLE_PANEL:
+ case MSG_TOGGLE_NOTIFICATION_PANEL:
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).togglePanel();
+ mCallbacks.get(i).toggleNotificationsPanel();
}
break;
case MSG_EXPAND_SETTINGS:
@@ -1504,6 +1535,11 @@
mCallbacks.get(i).animateExpandSettingsPanel((String) msg.obj);
}
break;
+ case MSG_TOGGLE_QUICK_SETTINGS_PANEL:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).toggleQuickSettingsPanel();
+ }
+ break;
case MSG_SHOW_IME_BUTTON:
args = (SomeArgs) msg.obj;
handleShowImeButton(args.argi1 /* displayId */, (IBinder) args.arg1 /* token */,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index d7d3732..5bf2f41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -15,6 +15,8 @@
*/
package com.android.systemui.statusbar;
+import static com.android.systemui.Flags.mediaControlsUserInitiatedDismiss;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
@@ -175,14 +177,18 @@
}
@Override
- public void onMediaDataRemoved(@NonNull String key) {
+ public void onMediaDataRemoved(@NonNull String key, boolean userInitiated) {
+ if (mediaControlsUserInitiatedDismiss() && !userInitiated) {
+ // Dismissing the notification will send the app's deleteIntent, so ignore if
+ // this was an automatic removal
+ Log.d(TAG, "Not dismissing " + key + " because it was removed by the system");
+ return;
+ }
mNotifPipeline.getAllNotifs()
.stream()
.filter(entry -> Objects.equals(entry.getKey(), key))
.findAny()
.ifPresent(entry -> {
- // TODO(b/160713608): "removing" this notification won't happen and
- // won't send the 'deleteIntent' if the notification is ongoing.
mNotifCollection.dismissNotification(entry,
getDismissedByUserStats(entry));
});
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
index 1a223c1..933eb20 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -18,12 +18,10 @@
package com.android.systemui.statusbar.notification.collection.coordinator
-import android.os.SystemProperties
import android.os.UserHandle
import android.provider.Settings
import androidx.annotation.VisibleForTesting
import com.android.systemui.Dumpable
-import com.android.systemui.Flags.notificationMinimalismPrototype
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
@@ -41,6 +39,7 @@
import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.headsUpEvents
import com.android.systemui.util.asIndenting
@@ -264,7 +263,7 @@
}
private fun unseenFeatureEnabled(): Flow<Boolean> {
- if (notificationMinimalismPrototype()) {
+ if (NotificationMinimalismPrototype.V1.isEnabled) {
return flowOf(true)
}
return secureSettings
@@ -342,18 +341,6 @@
var hasFilteredAnyNotifs = false
/**
- * the [notificationMinimalismPrototype] will now show seen notifications on the locked
- * shade by default, but this property read allows that to be quickly disabled for
- * testing
- */
- private val minimalismShowOnLockedShade
- get() =
- SystemProperties.getBoolean(
- "persist.notification_minimalism_prototype.show_on_locked_shade",
- true
- )
-
- /**
* Encapsulates a definition of "being on the keyguard". Note that these two definitions
* are wildly different: [StatusBarState.KEYGUARD] is when on the lock screen and does
* not include shade or occluded states, whereas [KeyguardRepository.isKeyguardShowing]
@@ -364,7 +351,10 @@
* allow seen notifications to appear in the locked shade.
*/
private fun isOnKeyguard(): Boolean =
- if (notificationMinimalismPrototype() && minimalismShowOnLockedShade) {
+ if (
+ NotificationMinimalismPrototype.V1.isEnabled &&
+ NotificationMinimalismPrototype.V1.showOnLockedShade
+ ) {
statusBarStateController.state == StatusBarState.KEYGUARD
} else {
keyguardRepository.isKeyguardShowing()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt
new file mode 100644
index 0000000..8889a10
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.shared
+
+import android.os.SystemProperties
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the minimalism prototype flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object NotificationMinimalismPrototype {
+ object V1 {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_NOTIFICATION_MINIMALISM_PROTOTYPE
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the heads-up cycling animation enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.notificationMinimalismPrototype()
+
+ /**
+ * the prototype will now show seen notifications on the locked shade by default, but this
+ * property read allows that to be quickly disabled for testing
+ */
+ val showOnLockedShade: Boolean
+ get() =
+ if (isUnexpectedlyInLegacyMode()) false
+ else
+ SystemProperties.getBoolean(
+ "persist.notification_minimalism_prototype.show_on_locked_shade",
+ true
+ )
+
+ /** gets the configurable max number of notifications */
+ val maxNotifs: Int
+ get() =
+ if (isUnexpectedlyInLegacyMode()) -1
+ else
+ SystemProperties.getInt(
+ "persist.notification_minimalism_prototype.lock_screen_max_notifs",
+ 1
+ )
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an
+ * eng build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception
+ * if the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index 5bd4c75..a446745 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -17,11 +17,9 @@
package com.android.systemui.statusbar.notification.stack
import android.content.res.Resources
-import android.os.SystemProperties
import android.util.Log
import android.view.View.GONE
import androidx.annotation.VisibleForTesting
-import com.android.systemui.Flags.notificationMinimalismPrototype
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
@@ -31,6 +29,7 @@
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
import com.android.systemui.statusbar.policy.SplitShadeStateController
import com.android.systemui.util.Compile
import com.android.systemui.util.children
@@ -381,16 +380,13 @@
fun updateResources() {
maxKeyguardNotifications =
infiniteIfNegative(
- if (notificationMinimalismPrototype()) {
- SystemProperties.getInt(
- "persist.notification_minimalism_prototype.lock_screen_max_notifs",
- 1
- )
+ if (NotificationMinimalismPrototype.V1.isEnabled) {
+ NotificationMinimalismPrototype.V1.maxNotifs
} else {
resources.getInteger(R.integer.keyguard_max_notification_count)
}
)
- maxNotificationsExcludesMedia = notificationMinimalismPrototype()
+ maxNotificationsExcludesMedia = NotificationMinimalismPrototype.V1.isEnabled
dividerHeight =
max(1f, resources.getDimensionPixelSize(R.dimen.notification_divider_height).toFloat())
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 e93c0f6..7dac77e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -59,6 +59,7 @@
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeHeaderController;
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -82,6 +83,7 @@
private final com.android.systemui.shade.ShadeController mShadeController;
private final CommandQueue mCommandQueue;
private final PanelExpansionInteractor mPanelExpansionInteractor;
+ private final Lazy<ShadeInteractor> mShadeInteractorLazy;
private final ShadeHeaderController mShadeHeaderController;
private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
private final MetricsLogger mMetricsLogger;
@@ -121,6 +123,7 @@
ShadeController shadeController,
CommandQueue commandQueue,
PanelExpansionInteractor panelExpansionInteractor,
+ Lazy<ShadeInteractor> shadeInteractorLazy,
ShadeHeaderController shadeHeaderController,
RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
MetricsLogger metricsLogger,
@@ -148,6 +151,7 @@
mShadeController = shadeController;
mCommandQueue = commandQueue;
mPanelExpansionInteractor = panelExpansionInteractor;
+ mShadeInteractorLazy = shadeInteractorLazy;
mShadeHeaderController = shadeHeaderController;
mRemoteInputQuickSettingsDisabler = remoteInputQuickSettingsDisabler;
mMetricsLogger = metricsLogger;
@@ -487,14 +491,23 @@
}
@Override
- public void togglePanel() {
- if (mPanelExpansionInteractor.isPanelExpanded()) {
+ public void toggleNotificationsPanel() {
+ if (mShadeInteractorLazy.get().isAnyExpanded().getValue()) {
mShadeController.animateCollapseShade();
} else {
mShadeController.animateExpandShade();
}
}
+ @Override
+ public void toggleQuickSettingsPanel() {
+ if (mShadeInteractorLazy.get().isQsExpanded().getValue()) {
+ mShadeController.animateCollapseShade();
+ } else {
+ mShadeController.animateExpandQs();
+ }
+ }
+
private boolean isGoingToSleep() {
return mWakefulnessLifecycle.getWakefulness()
== WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index f0dab3b..b715646 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -696,6 +696,7 @@
Trace.beginSection("StatusBarKeyguardViewManager#show");
mNotificationShadeWindowController.setKeyguardShowing(true);
if (SceneContainerFlag.isEnabled()) {
+ // TODO(b/336581871): add sceneState?
mSceneInteractorLazy.get().changeScene(
Scenes.Lockscreen, "StatusBarKeyguardViewManager.show");
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index b86a7c9..e073f7c 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -98,7 +98,7 @@
CoreStartable,
CommandQueue.Callbacks {
private static final String TAG = WMShell.class.getName();
- private static final int INVALID_SYSUI_STATE_MASK =
+ private static final long INVALID_SYSUI_STATE_MASK =
SYSUI_STATE_DIALOG_SHOWING
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
index 6dc5b72..bbdd805 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
@@ -27,6 +27,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doAnswer;
@@ -104,7 +105,7 @@
}).when(mAccessibilityManager).setMagnificationConnection(
any(IMagnificationConnection.class));
- when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
+ when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState);
doAnswer(invocation -> {
mMagnification.mMagnificationSettingsControllerCallback
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
index bf6ca06..e371b39 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
@@ -46,12 +46,12 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
import org.mockito.Mock
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
private const val ON: Int = 1
private const val OFF: Int = 0
@@ -90,7 +90,7 @@
secureSettings = FakeSettings()
systemClock = FakeSystemClock()
backgroundDelayableExecutor = FakeExecutor(systemClock)
- whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState)
+ whenever(sysuiState.setFlag(anyLong(), anyBoolean())).thenReturn(sysuiState)
fontScalingDialogDelegate =
spy(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
index ebb6b48..8895a5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
@@ -23,6 +23,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -118,7 +119,7 @@
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(mDevices);
when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager);
- when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
+ when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState);
when(mCachedDevice.getAddress()).thenReturn(DEVICE_ADDRESS);
when(mHearingDeviceItem.getCachedBluetoothDevice()).thenReturn(mCachedDevice);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
index 8e4c155..fd37cad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
@@ -8,6 +8,7 @@
import android.graphics.Point
import android.graphics.Rect
import android.os.Looper
+import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.IRemoteAnimationFinishedCallback
@@ -17,15 +18,20 @@
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.LinearLayout
+import android.window.RemoteTransition
+import android.window.TransitionFilter
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.shared.Flags
import com.android.systemui.util.mockito.any
+import com.android.wm.shell.shared.ShellTransitions
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertNotNull
import junit.framework.Assert.assertNull
import junit.framework.Assert.assertTrue
import junit.framework.AssertionFailedError
import kotlin.concurrent.thread
+import kotlin.test.assertEquals
import org.junit.After
import org.junit.Assert.assertThrows
import org.junit.Before
@@ -48,6 +54,7 @@
private val transitionContainer = LinearLayout(mContext)
private val mainExecutor = context.mainExecutor
private val testTransitionAnimator = fakeTransitionAnimator(mainExecutor)
+ private val testShellTransitions = FakeShellTransitions()
@Mock lateinit var callback: ActivityTransitionAnimator.Callback
@Mock lateinit var listener: ActivityTransitionAnimator.Listener
@Spy private val controller = TestTransitionAnimatorController(transitionContainer)
@@ -55,12 +62,16 @@
private lateinit var activityTransitionAnimator: ActivityTransitionAnimator
@get:Rule val rule = MockitoJUnit.rule()
+ @get:Rule val setFlagsRule = SetFlagsRule()
@Before
fun setup() {
activityTransitionAnimator =
ActivityTransitionAnimator(
mainExecutor,
+ ActivityTransitionAnimator.TransitionRegister.fromShellTransitions(
+ testShellTransitions
+ ),
testTransitionAnimator,
testTransitionAnimator,
disableWmTimeout = true,
@@ -164,6 +175,34 @@
}
@Test
+ fun registersReturnIffCookieIsPresent() {
+ setFlagsRule.enableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY)
+ `when`(callback.isOnKeyguard()).thenReturn(false)
+
+ startIntentWithAnimation(activityTransitionAnimator, controller) { _ ->
+ ActivityManager.START_DELIVERED_TO_TOP
+ }
+
+ waitForIdleSync()
+ assertTrue(testShellTransitions.remotes.isEmpty())
+ assertTrue(testShellTransitions.remotesForTakeover.isEmpty())
+
+ val controller =
+ object : DelegateTransitionAnimatorController(controller) {
+ override val transitionCookie
+ get() = ActivityTransitionAnimator.TransitionCookie("testCookie")
+ }
+
+ startIntentWithAnimation(activityTransitionAnimator, controller) { _ ->
+ ActivityManager.START_DELIVERED_TO_TOP
+ }
+
+ waitForIdleSync()
+ assertEquals(1, testShellTransitions.remotes.size)
+ assertTrue(testShellTransitions.remotesForTakeover.isEmpty())
+ }
+
+ @Test
fun doesNotStartIfAnimationIsCancelled() {
val runner = activityTransitionAnimator.createRunner(controller)
runner.onAnimationCancelled()
@@ -243,6 +282,35 @@
}
/**
+ * A fake implementation of [ShellTransitions] which saves filter-transition pairs locally and
+ * allows inspection.
+ */
+private class FakeShellTransitions : ShellTransitions {
+ val remotes = mutableMapOf<TransitionFilter, RemoteTransition>()
+ val remotesForTakeover = mutableMapOf<TransitionFilter, RemoteTransition>()
+
+ override fun registerRemote(filter: TransitionFilter, remoteTransition: RemoteTransition) {
+ remotes[filter] = remoteTransition
+ }
+
+ override fun registerRemoteForTakeover(
+ filter: TransitionFilter,
+ remoteTransition: RemoteTransition
+ ) {
+ remotesForTakeover[filter] = remoteTransition
+ }
+
+ override fun unregisterRemote(remoteTransition: RemoteTransition) {
+ while (remotes.containsValue(remoteTransition)) {
+ remotes.values.remove(remoteTransition)
+ }
+ while (remotesForTakeover.containsValue(remoteTransition)) {
+ remotesForTakeover.values.remove(remoteTransition)
+ }
+ }
+}
+
+/**
* A simple implementation of [ActivityTransitionAnimator.Controller] which throws if it is called
* outside of the main thread.
*/
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt
index b31fe21..42fcd54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt
@@ -16,12 +16,16 @@
package com.android.systemui.animation
+import android.os.HandlerThread
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import android.view.View
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
+import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.view.LaunchableFrameLayout
+import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertThrows
import org.junit.Test
import org.junit.runner.RunWith
@@ -30,6 +34,13 @@
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class GhostedViewTransitionAnimatorControllerTest : SysuiTestCase() {
+ companion object {
+ private const val LAUNCH_CUJ = 0
+ private const val RETURN_CUJ = 1
+ }
+
+ private val interactionJankMonitor = FakeInteractionJankMonitor()
+
@Test
fun animatingOrphanViewDoesNotCrash() {
val state = TransitionAnimator.State(top = 0, bottom = 0, left = 0, right = 0)
@@ -47,4 +58,63 @@
GhostedViewTransitionAnimatorController(FrameLayout(mContext))
}
}
+
+ @Test
+ fun cujsAreLoggedCorrectly() {
+ val parent = FrameLayout(mContext)
+
+ val launchView = LaunchableFrameLayout(mContext)
+ parent.addView((launchView))
+ val launchController =
+ GhostedViewTransitionAnimatorController(
+ launchView,
+ launchCujType = LAUNCH_CUJ,
+ returnCujType = RETURN_CUJ,
+ interactionJankMonitor = interactionJankMonitor
+ )
+ launchController.onTransitionAnimationStart(isExpandingFullyAbove = true)
+ assertThat(interactionJankMonitor.ongoing).containsExactly(LAUNCH_CUJ)
+ launchController.onTransitionAnimationEnd(isExpandingFullyAbove = true)
+ assertThat(interactionJankMonitor.ongoing).isEmpty()
+ assertThat(interactionJankMonitor.finished).containsExactly(LAUNCH_CUJ)
+
+ val returnView = LaunchableFrameLayout(mContext)
+ parent.addView((returnView))
+ val returnController =
+ object : GhostedViewTransitionAnimatorController(
+ returnView,
+ launchCujType = LAUNCH_CUJ,
+ returnCujType = RETURN_CUJ,
+ interactionJankMonitor = interactionJankMonitor
+ ) {
+ override val isLaunching = false
+ }
+ returnController.onTransitionAnimationStart(isExpandingFullyAbove = true)
+ assertThat(interactionJankMonitor.ongoing).containsExactly(RETURN_CUJ)
+ returnController.onTransitionAnimationEnd(isExpandingFullyAbove = true)
+ assertThat(interactionJankMonitor.ongoing).isEmpty()
+ assertThat(interactionJankMonitor.finished).containsExactly(LAUNCH_CUJ, RETURN_CUJ)
+ }
+
+ /**
+ * A fake implementation of [InteractionJankMonitor] which stores ongoing and finished CUJs and
+ * allows inspection.
+ */
+ private class FakeInteractionJankMonitor : InteractionJankMonitor(
+ HandlerThread("testThread")
+ ) {
+ val ongoing: MutableSet<Int> = mutableSetOf()
+ val finished: MutableSet<Int> = mutableSetOf()
+
+ override fun begin(v: View?, cujType: Int): Boolean {
+ ongoing.add(cujType)
+ return true
+ }
+
+ override fun end(cujType: Int): Boolean {
+ ongoing.remove(cujType)
+ finished.add(cujType)
+ return true
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
index a569cee..49f2043 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
@@ -21,7 +21,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
@@ -92,7 +92,7 @@
when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager);
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(null);
- when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
+ when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState);
when(mSystemUIDialogFactory.create(any(), any())).thenReturn(mDialog);
mBroadcastDialogDelegate = new BroadcastDialogDelegate(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
index 62c98b0..7215619 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
@@ -50,7 +50,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnit
@@ -104,7 +104,7 @@
dispatcher = UnconfinedTestDispatcher(scheduler)
testScope = TestScope(dispatcher)
- whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState)
+ whenever(sysuiState.setFlag(anyLong(), anyBoolean())).thenReturn(sysuiState)
mBluetoothTileDialogDelegate =
BluetoothTileDialogDelegate(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
index bc6c459..5361cef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
@@ -35,9 +35,13 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.communal.domain.interactor.CommunalInteractor;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerFake;
+import com.android.systemui.flags.DisableSceneContainer;
+import com.android.systemui.flags.EnableSceneContainer;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -50,6 +54,7 @@
import com.android.systemui.util.sensors.ThresholdSensor;
import com.android.systemui.util.time.FakeSystemClock;
+import kotlinx.coroutines.flow.MutableStateFlow;
import kotlinx.coroutines.flow.StateFlowKt;
import org.junit.Before;
@@ -89,6 +94,14 @@
private SelectedUserInteractor mSelectedUserInteractor;
@Mock
private CommunalInteractor mCommunalInteractor;
+ @Mock
+ private DeviceEntryInteractor mDeviceEntryInteractor;
+ private final MutableStateFlow<Boolean> mIsDeviceEntered =
+ StateFlowKt.MutableStateFlow(false);
+ @Mock
+ private SceneContainerOcclusionInteractor mSceneContainerOcclusionInteractor;
+ private final MutableStateFlow<Boolean> mIsInvisibleDueToOcclusion =
+ StateFlowKt.MutableStateFlow(false);
private final DockManagerFake mDockManager = new DockManagerFake();
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock);
@@ -99,15 +112,21 @@
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
when(mShadeInteractor.isQsExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false));
+ when(mDeviceEntryInteractor.isDeviceEntered()).thenReturn(mIsDeviceEntered);
+ when(mSceneContainerOcclusionInteractor.getInvisibleDueToOcclusion()).thenReturn(
+ mIsInvisibleDueToOcclusion);
+
mFalsingCollector = new FalsingCollectorImpl(mFalsingDataProvider, mFalsingManager,
mKeyguardUpdateMonitor, mHistoryTracker, mProximitySensor,
mStatusBarStateController, mKeyguardStateController,
() -> mShadeInteractor, mBatteryController,
mDockManager, mFakeExecutor,
mJavaAdapter, mFakeSystemClock, () -> mSelectedUserInteractor,
- () -> mCommunalInteractor
+ () -> mCommunalInteractor, () -> mDeviceEntryInteractor,
+ () -> mSceneContainerOcclusionInteractor
);
mFalsingCollector.init();
}
@@ -189,7 +208,8 @@
}
@Test
- public void testRegisterSensor_OccludingActivity() {
+ @DisableSceneContainer
+ public void testRegisterSensor_OccludingActivity_sceneContainerDisabled() {
when(mKeyguardStateController.isOccluded()).thenReturn(true);
ArgumentCaptor<StatusBarStateController.StateListener> stateListenerArgumentCaptor =
@@ -203,6 +223,21 @@
}
@Test
+ @EnableSceneContainer
+ public void testRegisterSensor_OccludingActivity_sceneContainerEnabled() {
+ mIsInvisibleDueToOcclusion.setValue(true);
+
+ ArgumentCaptor<StatusBarStateController.StateListener> stateListenerArgumentCaptor =
+ ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
+ verify(mStatusBarStateController).addCallback(stateListenerArgumentCaptor.capture());
+
+ mFalsingCollector.onScreenTurningOn();
+ reset(mProximitySensor);
+ stateListenerArgumentCaptor.getValue().onStateChanged(StatusBarState.SHADE);
+ verify(mProximitySensor).register(any(ThresholdSensor.Listener.class));
+ }
+
+ @Test
public void testPassThroughEnterKeyEvent() {
KeyEvent enterDown = KeyEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER,
0, 0, 0, 0, 0, 0, 0, "");
@@ -280,7 +315,8 @@
}
@Test
- public void testAvoidUnlocked() {
+ @DisableSceneContainer
+ public void testAvoidUnlocked_sceneContainerDisabled() {
MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
@@ -296,6 +332,23 @@
}
@Test
+ @EnableSceneContainer
+ public void testAvoidUnlocked_sceneContainerEnabled() {
+ MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+ MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
+
+ mIsDeviceEntered.setValue(true);
+
+ // Nothing passed initially
+ mFalsingCollector.onTouchEvent(down);
+ verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class));
+
+ // Up event would normally flush the up event, but doesn't.
+ mFalsingCollector.onTouchEvent(up);
+ verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class));
+ }
+
+ @Test
public void testGestureWhenDozing() {
// We check the FalsingManager for taps during the transition to AoD (dozing=true,
// pulsing=false), so the FalsingCollector needs to continue to analyze events that occur
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt
new file mode 100644
index 0000000..d0d9891
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt
@@ -0,0 +1,1320 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor.scenetransition
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.realKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.data.repository.sceneContainerRepository
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
+ private val kosmos =
+ testKosmos().apply { keyguardTransitionRepository = realKeyguardTransitionRepository }
+
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.lockscreenSceneTransitionInteractor
+
+ private val progress = MutableStateFlow(0f)
+
+ private val sceneTransitions =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(Scenes.Lockscreen)
+ )
+
+ private val lsToGone =
+ ObservableTransitionState.Transition(
+ Scenes.Lockscreen,
+ Scenes.Gone,
+ flowOf(Scenes.Lockscreen),
+ progress,
+ false,
+ flowOf(false)
+ )
+
+ private val goneToLs =
+ ObservableTransitionState.Transition(
+ Scenes.Gone,
+ Scenes.Lockscreen,
+ flowOf(Scenes.Lockscreen),
+ progress,
+ false,
+ flowOf(false)
+ )
+
+ @Before
+ fun setUp() {
+ underTest.start()
+ kosmos.sceneContainerRepository.setTransitionState(sceneTransitions)
+ testScope.launch {
+ kosmos.realKeyguardTransitionRepository.emitInitialStepsFromOff(
+ KeyguardState.LOCKSCREEN
+ )
+ }
+ }
+
+ /** STL: Ls -> Gone, then settle with Idle(Gone). This is the default case. */
+ @Test
+ fun transition_from_ls_scene_end_in_gone() =
+ testScope.runTest {
+ sceneTransitions.value = lsToGone
+
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.RUNNING,
+ progress = 0f,
+ )
+
+ progress.value = 0.4f
+ assertTransition(
+ step = currentStep!!,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone)
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+ }
+
+ /**
+ * STL: Ls -> Gone, then settle with Idle(Ls). KTF in this scenario needs to invert the
+ * transition LS -> UNDEFINED to UNDEFINED -> LS as there is no mechanism in KTF to
+ * finish/settle to progress 0.0f.
+ */
+ @Test
+ fun transition_from_ls_scene_end_in_ls() =
+ testScope.runTest {
+ sceneTransitions.value = lsToGone
+
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.RUNNING,
+ progress = 0f,
+ )
+
+ progress.value = 0.4f
+ assertTransition(
+ step = currentStep!!,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen)
+
+ assertTransition(
+ step = allSteps[allSteps.size - 3],
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.CANCELED,
+ progress = 0.4f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 2],
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.STARTED,
+ progress = 0.6f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 1],
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+ }
+
+ /**
+ * STL: Ls -> Gone, then settle with Idle(Ls). KTF starts in AOD and needs to inverse correctly
+ * back to AOD.
+ */
+ @Test
+ fun transition_from_ls_scene_on_aod_end_in_ls() =
+ testScope.runTest {
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+
+ kosmos.realKeyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = this.javaClass.simpleName,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ animator = null,
+ modeOnCanceled = TransitionModeOnCanceled.RESET
+ )
+ )
+ sceneTransitions.value = lsToGone
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.AOD,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.RUNNING,
+ progress = 0f,
+ )
+
+ progress.value = 0.4f
+ assertTransition(
+ step = currentStep!!,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen)
+
+ assertTransition(
+ step = allSteps[allSteps.size - 3],
+ from = KeyguardState.AOD,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.CANCELED,
+ progress = 0.4f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 2],
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.AOD,
+ state = TransitionState.STARTED,
+ progress = 0.6f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 1],
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.AOD,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+ }
+
+ /**
+ * STL: Gone -> Ls, then settle with Idle(Ls). This is the default case in the reverse
+ * direction.
+ */
+ @Test
+ fun transition_to_ls_scene_end_in_ls() =
+ testScope.runTest {
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ sceneTransitions.value = goneToLs
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.RUNNING,
+ progress = 0f,
+ )
+
+ progress.value = 0.4f
+ assertTransition(
+ step = currentStep!!,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen)
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+ }
+
+ /** STL: Gone -> Ls (AOD), will transition to AOD once */
+ @Test
+ fun transition_to_ls_scene_with_changed_next_scene_is_respected_just_once() =
+ testScope.runTest {
+ underTest.onSceneAboutToChange(Scenes.Lockscreen, KeyguardState.AOD)
+ sceneTransitions.value = goneToLs
+
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.AOD,
+ state = TransitionState.RUNNING,
+ progress = 0f,
+ )
+
+ sceneTransitions.value =
+ ObservableTransitionState.Transition(
+ Scenes.Shade,
+ Scenes.Lockscreen,
+ flowOf(Scenes.Lockscreen),
+ progress,
+ false,
+ flowOf(false)
+ )
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.RUNNING,
+ progress = 0f,
+ )
+ }
+
+ /**
+ * STL: Gone -> Ls, then settle with Idle(Gone). KTF in this scenario needs to invert the
+ * transition UNDEFINED -> LS to LS -> UNDEFINED as there is no mechanism in KTF to
+ * finish/settle to progress 0.0f.
+ */
+ @Test
+ fun transition_to_ls_scene_end_in_from_scene() =
+ testScope.runTest {
+ sceneTransitions.value = goneToLs
+
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.RUNNING,
+ progress = 0f,
+ )
+
+ progress.value = 0.4f
+ assertTransition(
+ step = currentStep!!,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone)
+ val stepM3 = allSteps[allSteps.size - 3]
+ val stepM2 = allSteps[allSteps.size - 2]
+
+ assertTransition(
+ step = stepM3,
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.CANCELED,
+ progress = 0.4f,
+ )
+
+ assertTransition(
+ step = stepM2,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.STARTED,
+ progress = 0.6f,
+ )
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+ }
+
+ /**
+ * STL: Gone -> Ls, then interrupted by Shade -> Ls. KTF in this scenario needs to invert the
+ * transition UNDEFINED -> LS to LS -> UNDEFINED as there is no mechanism in KTF to
+ * finish/settle to progress 0.0f. Then restart a different transition UNDEFINED -> Ls.
+ */
+ @Test
+ fun transition_to_ls_scene_end_in_to_ls_transition() =
+ testScope.runTest {
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+ sceneTransitions.value = goneToLs
+ progress.value = 0.4f
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ sceneTransitions.value =
+ ObservableTransitionState.Transition(
+ Scenes.Shade,
+ Scenes.Lockscreen,
+ flowOf(Scenes.Lockscreen),
+ progress,
+ false,
+ flowOf(false)
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 5],
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.CANCELED,
+ progress = 0.4f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 4],
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.STARTED,
+ progress = 0.6f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 3],
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 2],
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+
+ progress.value = 0.2f
+ assertTransition(
+ step = allSteps[allSteps.size - 1],
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.RUNNING,
+ progress = 0.2f,
+ )
+ }
+
+ /**
+ * STL: Gone -> Ls, then interrupted by Ls -> Shade. This is like continuing the transition from
+ * Ls before the transition before has properly settled. This can happen in STL e.g. with an
+ * accelerated swipe (quick successive fling gestures).
+ */
+ @Test
+ fun transition_to_ls_scene_end_in_from_ls_transition() =
+ testScope.runTest {
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+ sceneTransitions.value = goneToLs
+ progress.value = 0.4f
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ sceneTransitions.value =
+ ObservableTransitionState.Transition(
+ Scenes.Lockscreen,
+ Scenes.Shade,
+ flowOf(Scenes.Lockscreen),
+ progress,
+ false,
+ flowOf(false)
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 3],
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.CANCELED,
+ progress = 0.4f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 2],
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.STARTED,
+ progress = 0.0f,
+ )
+
+ progress.value = 0.2f
+ assertTransition(
+ step = allSteps[allSteps.size - 1],
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.RUNNING,
+ progress = 0.2f,
+ )
+ }
+
+ /**
+ * STL: Gone -> Ls, then interrupted by Gone -> Shade. This is going back to Gone but starting a
+ * transition from Gone before settling in Gone. KTF needs to make sure the transition is
+ * properly inversed and settled in UNDEFINED.
+ */
+ @Test
+ fun transition_to_ls_scene_end_in_other_transition() =
+ testScope.runTest {
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+ sceneTransitions.value = goneToLs
+ progress.value = 0.4f
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ sceneTransitions.value =
+ ObservableTransitionState.Transition(
+ Scenes.Gone,
+ Scenes.Shade,
+ flowOf(Scenes.Lockscreen),
+ progress,
+ false,
+ flowOf(false)
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 3],
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.CANCELED,
+ progress = 0.4f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 2],
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.STARTED,
+ progress = 0.6f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 1],
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+ }
+
+ /**
+ * STL: Gone -> Ls, then interrupt in KTF LS -> AOD, then stl still finishes in Ls. After a KTF
+ * transition is started (UNDEFINED -> LOCKSCREEN) KTF immediately considers the active scene to
+ * be LOCKSCREEN. This means that all listeners for LOCKSCREEN are active and may start a new
+ * transition LOCKSCREEN -> *. Here we test LS -> AOD.
+ *
+ * KTF is allowed to already start and play the other transition, while the STL transition may
+ * finish later (gesture completes much later). When we eventually settle the STL transition in
+ * Ls we do not want to force KTF back to its original destination (LOCKSCREEN). Instead, for
+ * this scenario the settle can be ignored.
+ */
+ @Test
+ fun transition_to_ls_scene_interrupted_by_ktf_transition_then_finish_in_lockscreen() =
+ testScope.runTest {
+ sceneTransitions.value = goneToLs
+
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.RUNNING,
+ progress = 0f,
+ )
+
+ progress.value = 0.4f
+ assertTransition(
+ step = currentStep!!,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ kosmos.realKeyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = this.javaClass.simpleName,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ animator = null,
+ modeOnCanceled = TransitionModeOnCanceled.RESET
+ )
+ )
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+
+ // Scene progress should not affect KTF transition anymore
+ progress.value = 0.7f
+ assertTransition(currentStep!!, progress = 0f)
+
+ // Scene transition still finishes but should not impact KTF transition
+ sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen)
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+ }
+
+ /**
+ * STL: Gone -> Ls, then interrupt in KTF LS -> AOD, then stl finishes in Gone.
+ *
+ * Refers to: `transition_to_ls_scene_interrupted_by_ktf_transition_then_finish_in_lockscreen`
+ *
+ * This is similar to the previous scenario but the gesture may have gone back to its origin. In
+ * this case we can not ignore the settlement, because whatever KTF has done in the meantime it
+ * needs to immediately finish in UNDEFINED (there is a jump cut).
+ */
+ @Test
+ fun transition_to_ls_scene_interrupted_by_ktf_transition_then_finish_in_gone() =
+ testScope.runTest {
+ sceneTransitions.value = goneToLs
+
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.RUNNING,
+ progress = 0f,
+ )
+
+ progress.value = 0.4f
+ assertTransition(
+ step = currentStep!!,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ kosmos.realKeyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = this.javaClass.simpleName,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ animator = null,
+ modeOnCanceled = TransitionModeOnCanceled.RESET
+ )
+ )
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+
+ progress.value = 0.7f
+ assertThat(currentStep?.value).isEqualTo(0f)
+
+ sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone)
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.AOD,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+ }
+
+ /**
+ * STL: Gone -> Ls, then interrupt in KTF LS -> AOD, then STL Gone -> Shade
+ *
+ * Refers to: `transition_to_ls_scene_interrupted_by_ktf_transition_then_finish_in_lockscreen`
+ *
+ * This is similar to the previous scenario but the gesture may have been interrupted by any
+ * other transition. KTF needs to immediately finish in UNDEFINED (there is a jump cut).
+ */
+ @Test
+ fun transition_to_ls_interrupted_by_ktf_transition_then_interrupted_by_other_transition() =
+ testScope.runTest {
+ sceneTransitions.value = goneToLs
+
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.RUNNING,
+ progress = 0f,
+ )
+
+ progress.value = 0.4f
+ assertTransition(
+ step = currentStep!!,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ kosmos.realKeyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = this.javaClass.simpleName,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ animator = null,
+ modeOnCanceled = TransitionModeOnCanceled.RESET
+ )
+ )
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+
+ progress.value = 0.7f
+ assertTransition(currentStep!!, progress = 0f)
+
+ sceneTransitions.value =
+ ObservableTransitionState.Transition(
+ Scenes.Gone,
+ Scenes.Shade,
+ flowOf(Scenes.Lockscreen),
+ progress,
+ false,
+ flowOf(false)
+ )
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.AOD,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+ }
+
+ /**
+ * STL: Gone -> Ls, then interrupt in KTF LS -> AOD, then STL Ls -> Shade
+ *
+ * In this scenario it is important that the last STL transition Ls -> Shade triggers a cancel
+ * of the * -> AOD transition but then also properly starts a transition AOD (not LOCKSCREEN) ->
+ * UNDEFINED transition.
+ */
+ @Test
+ fun transition_to_ls_interrupted_by_ktf_transition_then_interrupted_by_from_ls_transition() =
+ testScope.runTest {
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+ sceneTransitions.value = goneToLs
+ progress.value = 0.4f
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ kosmos.realKeyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = this.javaClass.simpleName,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ animator = null,
+ modeOnCanceled = TransitionModeOnCanceled.RESET
+ )
+ )
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+
+ progress.value = 0.7f
+ assertTransition(currentStep!!, progress = 0f)
+
+ sceneTransitions.value =
+ ObservableTransitionState.Transition(
+ Scenes.Lockscreen,
+ Scenes.Shade,
+ flowOf(Scenes.Lockscreen),
+ progress,
+ false,
+ flowOf(false)
+ )
+ allSteps[allSteps.size - 3]
+
+ assertTransition(
+ step = allSteps[allSteps.size - 3],
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ state = TransitionState.CANCELED,
+ progress = 0f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 2],
+ from = KeyguardState.AOD,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+
+ progress.value = 0.2f
+ assertTransition(
+ step = allSteps[allSteps.size - 1],
+ from = KeyguardState.AOD,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.RUNNING,
+ progress = 0.2f,
+ )
+ }
+
+ /**
+ * STL: Gone -> Ls, then interrupt in KTF LS -> AOD, then STL Shade -> Ls
+ *
+ * In this scenario it is important KTF is brought back into a FINISHED UNDEFINED state
+ * considering the state is already on AOD from where a new UNDEFINED -> LOCKSCREEN transition
+ * can be started.
+ */
+ @Test
+ fun transition_to_ls_interrupted_by_ktf_transition_then_interrupted_by_to_ls_transition() =
+ testScope.runTest {
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+ sceneTransitions.value = goneToLs
+ progress.value = 0.4f
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ kosmos.realKeyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = this.javaClass.simpleName,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ animator = null,
+ modeOnCanceled = TransitionModeOnCanceled.RESET
+ )
+ )
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+
+ progress.value = 0.7f
+ assertTransition(currentStep!!, progress = 0f)
+
+ sceneTransitions.value =
+ ObservableTransitionState.Transition(
+ Scenes.Shade,
+ Scenes.Lockscreen,
+ flowOf(Scenes.Lockscreen),
+ progress,
+ false,
+ flowOf(false)
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 5],
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ state = TransitionState.CANCELED,
+ progress = 0f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 4],
+ from = KeyguardState.AOD,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.STARTED,
+ progress = 1f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 3],
+ from = KeyguardState.AOD,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 2],
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 1],
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.RUNNING,
+ progress = 0.7f,
+ )
+ }
+
+ /**
+ * STL: Gone -> Ls, then interrupt multiple canceled KTF transitions, then STL Ls -> Shade
+ *
+ * Similar to
+ * `transition_to_ls_scene_interrupted_by_ktf_transition_then_interrupted_by_from_ls_transition`
+ * but here KTF is canceled multiple times such that in the end OCCLUDED -> UNDEFINED is
+ * properly started. (not from AOD or LOCKSCREEN)
+ *
+ * Note: there is no test which tests multiple cancels from the STL side, this is because all
+ * STL transitions trigger a response from LockscreenSceneTransitionInteractor which forces KTF
+ * into a specific state, so testing each pair is enough. Meanwhile KTF can move around without
+ * any reaction from LockscreenSceneTransitionInteractor.
+ */
+ @Test
+ fun transition_to_ls_interrupted_by_ktf_cancel_sequence_interrupted_by_from_ls_transition() =
+ testScope.runTest {
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+ sceneTransitions.value = lsToGone
+ progress.value = 0.4f
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ kosmos.realKeyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = this.javaClass.simpleName,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ animator = null,
+ modeOnCanceled = TransitionModeOnCanceled.RESET
+ )
+ )
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+
+ kosmos.realKeyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = this.javaClass.simpleName,
+ from = KeyguardState.AOD,
+ to = KeyguardState.DOZING,
+ animator = null,
+ modeOnCanceled = TransitionModeOnCanceled.RESET
+ )
+ )
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.AOD,
+ to = KeyguardState.DOZING,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+
+ kosmos.realKeyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = this.javaClass.simpleName,
+ from = KeyguardState.DOZING,
+ to = KeyguardState.OCCLUDED,
+ animator = null,
+ modeOnCanceled = TransitionModeOnCanceled.RESET
+ )
+ )
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.DOZING,
+ to = KeyguardState.OCCLUDED,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+
+ progress.value = 0.7f
+ assertTransition(currentStep!!, progress = 0f)
+
+ sceneTransitions.value =
+ ObservableTransitionState.Transition(
+ Scenes.Lockscreen,
+ Scenes.Shade,
+ flowOf(Scenes.Lockscreen),
+ progress,
+ false,
+ flowOf(false)
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 3],
+ from = KeyguardState.DOZING,
+ to = KeyguardState.OCCLUDED,
+ state = TransitionState.CANCELED,
+ progress = 0f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 2],
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+
+ progress.value = 0.2f
+ assertTransition(
+ step = allSteps[allSteps.size - 1],
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.RUNNING,
+ progress = 0.2f,
+ )
+ }
+
+ /**
+ * STL: Gone -> Ls, then interrupted by KTF LS -> AOD which is FINISHED before STL Ls -> Shade
+ *
+ * Similar to
+ * `transition_to_ls_scene_interrupted_by_ktf_transition_then_interrupted_by_from_ls_transition`
+ * but here KTF is finishing the transition and only then gets interrupted. Should correctly
+ * start AOD -> UNDEFINED.
+ */
+ @Test
+ fun transition_to_ls_scene_interrupted_and_finished_by_ktf_interrupted_by_from_ls_transition() =
+ testScope.runTest {
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+ sceneTransitions.value = lsToGone
+ progress.value = 0.4f
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ val ktfUuid =
+ kosmos.realKeyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = this.javaClass.simpleName,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ animator = null,
+ modeOnCanceled = TransitionModeOnCanceled.RESET
+ )
+ )
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+
+ kosmos.realKeyguardTransitionRepository.updateTransition(
+ ktfUuid!!,
+ 1f,
+ TransitionState.FINISHED
+ )
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+
+ sceneTransitions.value =
+ ObservableTransitionState.Transition(
+ Scenes.Lockscreen,
+ Scenes.Shade,
+ flowOf(Scenes.Lockscreen),
+ progress,
+ false,
+ flowOf(false)
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 3],
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 2],
+ from = KeyguardState.AOD,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+
+ progress.value = 0.2f
+ assertTransition(
+ step = allSteps[allSteps.size - 1],
+ from = KeyguardState.AOD,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.RUNNING,
+ progress = 0.2f,
+ )
+ }
+
+ /**
+ * STL: Ls -> Gone, then interrupted by Ls -> Bouncer. This happens when the next transition is
+ * immediately started from Gone without settling in Idle. This specifically happens when
+ * dragging down on Ls and then changing direction. The transition will switch from -> Shade to
+ * -> Bouncer without settling or signaling any cancellation as STL considers this to be the
+ * same gesture.
+ *
+ * In STL there is no guarantee that transitions settle in Idle before continuing.
+ */
+ @Ignore("Suffers from a race condition that will be fixed in followup CL")
+ @Test
+ fun transition_from_ls_scene_interrupted_by_other_from_ls_transition() =
+ testScope.runTest {
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+ sceneTransitions.value = lsToGone
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.RUNNING,
+ progress = 0f,
+ )
+
+ progress.value = 0.4f
+ sceneTransitions.value =
+ ObservableTransitionState.Transition(
+ Scenes.Lockscreen,
+ Scenes.Bouncer,
+ flowOf(Scenes.Lockscreen),
+ progress,
+ false,
+ flowOf(false)
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 5],
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.CANCELED,
+ progress = 0.4f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 4],
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.STARTED,
+ progress = 0.6f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 3],
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 2],
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+ }
+
+ /**
+ * STL: Ls -> Gone, then interrupted by Gone -> Ls. This happens when the next transition is
+ * immediately started from Gone without settling in Idle. In STL there is no guarantee that
+ * transitions settle in Idle before continuing.
+ */
+ @Test
+ fun transition_from_ls_scene_interrupted_by_to_ls_transition() =
+ testScope.runTest {
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions)
+ sceneTransitions.value = lsToGone
+ progress.value = 0.4f
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ sceneTransitions.value =
+ ObservableTransitionState.Transition(
+ Scenes.Gone,
+ Scenes.Lockscreen,
+ flowOf(Scenes.Lockscreen),
+ progress,
+ false,
+ flowOf(false)
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 3],
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+
+ assertTransition(
+ step = allSteps[allSteps.size - 2],
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.STARTED,
+ progress = 0f,
+ )
+
+ progress.value = 0.2f
+ assertTransition(
+ step = allSteps[allSteps.size - 1],
+ from = KeyguardState.UNDEFINED,
+ to = KeyguardState.LOCKSCREEN,
+ state = TransitionState.RUNNING,
+ progress = 0.2f,
+ )
+ }
+
+ /**
+ * STL: Ls -> Gone, then interrupted by Gone -> Bouncer. This happens when the next transition
+ * is immediately started from Gone without settling in Idle. In STL there is no guarantee that
+ * transitions settle in Idle before continuing.
+ */
+ @Test
+ fun transition_from_ls_scene_interrupted_by_other_stl_transition() =
+ testScope.runTest {
+ val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions)
+ sceneTransitions.value = lsToGone
+ progress.value = 0.4f
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.RUNNING,
+ progress = 0.4f,
+ )
+
+ sceneTransitions.value =
+ ObservableTransitionState.Transition(
+ Scenes.Gone,
+ Scenes.Bouncer,
+ flowOf(Scenes.Lockscreen),
+ progress,
+ false,
+ flowOf(false)
+ )
+
+ assertTransition(
+ step = currentStep!!,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.UNDEFINED,
+ state = TransitionState.FINISHED,
+ progress = 1f,
+ )
+ }
+
+ private fun assertTransition(
+ step: TransitionStep,
+ from: KeyguardState? = null,
+ to: KeyguardState? = null,
+ state: TransitionState? = null,
+ progress: Float? = null
+ ) {
+ if (from != null) assertThat(step.from).isEqualTo(from)
+ if (to != null) assertThat(step.to).isEqualTo(to)
+ if (state != null) assertThat(step.transitionState).isEqualTo(state)
+ if (progress != null) assertThat(step.value).isEqualTo(progress)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt
index e56a253..265ade3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt
@@ -172,20 +172,20 @@
fun testOnRemovedForCurrent_callsListener() {
// GIVEN a media was removed for main user
mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
- mediaDataFilter.onMediaDataRemoved(KEY)
+ mediaDataFilter.onMediaDataRemoved(KEY, false)
// THEN we should tell the listener
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
}
@Test
fun testOnRemovedForGuest_doesNotCallListener() {
// GIVEN a media was removed for guest user
mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
- mediaDataFilter.onMediaDataRemoved(KEY)
+ mediaDataFilter.onMediaDataRemoved(KEY, false)
// THEN we should NOT tell the listener
- verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean())
}
@Test
@@ -197,7 +197,7 @@
setUser(USER_GUEST)
// THEN we should remove the main user's media
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
}
@Test
@@ -230,7 +230,7 @@
setPrivateProfileUnavailable()
// THEN we should add the private profile media
- verify(listener).onMediaDataRemoved(eq(KEY_ALT))
+ verify(listener).onMediaDataRemoved(eq(KEY_ALT), eq(false))
}
@Test
@@ -360,7 +360,7 @@
@Test
fun testOnNotificationRemoved_doesntHaveMedia() {
mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain)
- mediaDataFilter.onMediaDataRemoved(KEY)
+ mediaDataFilter.onMediaDataRemoved(KEY, false)
assertThat(mediaDataFilter.hasAnyMediaOrRecommendation()).isFalse()
assertThat(mediaDataFilter.hasAnyMedia()).isFalse()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
index 5a2d22d..99bf2db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
@@ -346,7 +346,7 @@
// THEN it is removed and listeners are informed
foregroundExecutor.advanceClockToLast()
foregroundExecutor.runAllReady()
- verify(listener).onMediaDataRemoved(PACKAGE_NAME)
+ verify(listener).onMediaDataRemoved(PACKAGE_NAME, false)
}
@Test
@@ -532,7 +532,7 @@
addNotificationAndLoad()
val data = mediaDataCaptor.value
mediaDataManager.onNotificationRemoved(KEY)
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
}
@@ -777,7 +777,7 @@
eq(false)
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
- verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(listener, never()).onMediaDataRemoved(eq(KEY), eq(false))
// WHEN the second is removed
mediaDataManager.onNotificationRemoved(KEY_2)
// THEN the data is for resumption and the second key is removed
@@ -791,7 +791,7 @@
eq(false)
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
- verify(listener).onMediaDataRemoved(eq(KEY_2))
+ verify(listener).onMediaDataRemoved(eq(KEY_2), eq(false))
}
@Test
@@ -816,7 +816,7 @@
mediaDataManager.onNotificationRemoved(KEY)
// THEN the media data is removed
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
}
@Test
@@ -866,7 +866,7 @@
mediaDataManager.onNotificationRemoved(KEY)
// THEN the media data is removed
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
}
@Test
@@ -905,7 +905,7 @@
assertThat(mediaDataCaptor.value.isPlaying).isFalse()
// And the oldest resume control was removed
- verify(listener).onMediaDataRemoved(eq("0:$PACKAGE_NAME"))
+ verify(listener).onMediaDataRemoved(eq("0:$PACKAGE_NAME"), eq(false))
}
fun testOnNotificationRemoved_lockDownMode() {
@@ -915,7 +915,7 @@
val data = mediaDataCaptor.value
mediaDataManager.onNotificationRemoved(KEY)
- verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean())
verify(logger, never())
.logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
@@ -1148,7 +1148,7 @@
mediaDataManager.setMediaResumptionEnabled(false)
// THEN the resume controls are dismissed
- verify(listener).onMediaDataRemoved(eq(PACKAGE_NAME))
+ verify(listener).onMediaDataRemoved(eq(PACKAGE_NAME), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
}
@@ -1156,19 +1156,19 @@
fun testDismissMedia_listenerCalled() {
addNotificationAndLoad()
val data = mediaDataCaptor.value
- val removed = mediaDataManager.dismissMediaData(KEY, 0L)
+ val removed = mediaDataManager.dismissMediaData(KEY, 0L, true)
assertThat(removed).isTrue()
foregroundExecutor.advanceClockToLast()
foregroundExecutor.runAllReady()
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(true))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
}
@Test
fun testDismissMedia_keyDoesNotExist_returnsFalse() {
- val removed = mediaDataManager.dismissMediaData(KEY, 0L)
+ val removed = mediaDataManager.dismissMediaData(KEY, 0L, true)
assertThat(removed).isFalse()
}
@@ -2077,7 +2077,7 @@
sessionCallbackCaptor.value.invoke(KEY)
// It remains as a regular player
- verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean())
verify(listener, never())
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
}
@@ -2093,7 +2093,7 @@
mediaDataManager.onNotificationRemoved(KEY)
// It is fully removed
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
verify(listener, never())
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
@@ -2146,7 +2146,7 @@
mediaDataManager.onNotificationRemoved(KEY)
// It remains as a regular player
- verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean())
verify(listener, never())
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
}
@@ -2199,7 +2199,7 @@
sessionCallbackCaptor.value.invoke(KEY)
// It is fully removed
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
verify(listener, never())
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
@@ -2253,7 +2253,7 @@
sessionCallbackCaptor.value.invoke(KEY)
// It is fully removed.
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
verify(listener, never())
.onMediaDataLoaded(
@@ -2279,7 +2279,7 @@
sessionCallbackCaptor.value.invoke(KEY)
// It is fully removed
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
verify(listener, never())
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
@@ -2329,7 +2329,7 @@
mediaDataManager.onNotificationRemoved(KEY)
// We still make sure to remove it
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java
index bb5b572..dd05a0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java
@@ -202,24 +202,24 @@
@Test
public void mediaDataRemoved() {
// WHEN media data is removed without first receiving device or data
- mManager.onMediaDataRemoved(KEY);
+ mManager.onMediaDataRemoved(KEY, false);
// THEN a removed event isn't emitted
- verify(mListener, never()).onMediaDataRemoved(eq(KEY));
+ verify(mListener, never()).onMediaDataRemoved(eq(KEY), anyBoolean());
}
@Test
public void mediaDataRemovedAfterMediaEvent() {
mManager.onMediaDataLoaded(KEY, null, mMediaData, true /* immediately */,
0 /* receivedSmartspaceCardLatency */, false /* isSsReactivated */);
- mManager.onMediaDataRemoved(KEY);
- verify(mListener).onMediaDataRemoved(eq(KEY));
+ mManager.onMediaDataRemoved(KEY, false);
+ verify(mListener).onMediaDataRemoved(eq(KEY), eq(false));
}
@Test
public void mediaDataRemovedAfterDeviceEvent() {
mManager.onMediaDeviceChanged(KEY, null, mDeviceData);
- mManager.onMediaDataRemoved(KEY);
- verify(mListener).onMediaDataRemoved(eq(KEY));
+ mManager.onMediaDataRemoved(KEY, false);
+ verify(mListener).onMediaDataRemoved(eq(KEY), eq(false));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
index 77ad263..35eefd9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
@@ -205,9 +205,9 @@
assertThat(currentMedia).containsExactly(mediaCommonModel)
- mediaDataFilter.onMediaDataRemoved(KEY)
+ mediaDataFilter.onMediaDataRemoved(KEY, false)
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
assertThat(currentMedia).doesNotContain(mediaCommonModel)
}
@@ -218,9 +218,9 @@
// GIVEN a media was removed for guest user
mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
- mediaDataFilter.onMediaDataRemoved(KEY)
+ mediaDataFilter.onMediaDataRemoved(KEY, false)
- verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(listener, never()).onMediaDataRemoved(eq(KEY), eq(false))
assertThat(currentMedia).isEmpty()
}
@@ -239,7 +239,7 @@
setUser(USER_GUEST)
// THEN we should remove the main user's media
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
assertThat(currentMedia).isEmpty()
}
@@ -291,7 +291,7 @@
val mediaLoadedStatesModel = MediaDataLoadingModel.Loaded(dataMain.instanceId)
// THEN we should remove the private profile media
- verify(listener).onMediaDataRemoved(eq(KEY_ALT))
+ verify(listener).onMediaDataRemoved(eq(KEY_ALT), eq(false))
assertThat(currentMedia)
.containsExactly(MediaCommonModel.MediaControl(mediaLoadedStatesModel))
}
@@ -502,7 +502,7 @@
val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain)
- mediaDataFilter.onMediaDataRemoved(KEY)
+ mediaDataFilter.onMediaDataRemoved(KEY, false)
assertThat(hasAnyMediaOrRecommendation(selectedUserEntries, smartspaceMediaData))
.isFalse()
assertThat(hasAnyMedia(selectedUserEntries)).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
index 1de7ee3..5791826 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
@@ -384,7 +384,7 @@
// THEN it is removed and listeners are informed
foregroundExecutor.advanceClockToLast()
foregroundExecutor.runAllReady()
- verify(listener).onMediaDataRemoved(PACKAGE_NAME)
+ verify(listener).onMediaDataRemoved(PACKAGE_NAME, false)
}
@Test
@@ -567,7 +567,7 @@
addNotificationAndLoad()
val data = mediaDataCaptor.value
mediaDataProcessor.onNotificationRemoved(KEY)
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
}
@@ -812,7 +812,7 @@
eq(false)
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
- verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean())
// WHEN the second is removed
mediaDataProcessor.onNotificationRemoved(KEY_2)
// THEN the data is for resumption and the second key is removed
@@ -826,7 +826,7 @@
eq(false)
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
- verify(listener).onMediaDataRemoved(eq(KEY_2))
+ verify(listener).onMediaDataRemoved(eq(KEY_2), eq(false))
}
@Test
@@ -851,7 +851,7 @@
mediaDataProcessor.onNotificationRemoved(KEY)
// THEN the media data is removed
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
}
@Test
@@ -901,7 +901,7 @@
mediaDataProcessor.onNotificationRemoved(KEY)
// THEN the media data is removed
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
}
@Test
@@ -940,7 +940,7 @@
assertThat(mediaDataCaptor.value.isPlaying).isFalse()
// And the oldest resume control was removed
- verify(listener).onMediaDataRemoved(eq("0:$PACKAGE_NAME"))
+ verify(listener).onMediaDataRemoved(eq("0:$PACKAGE_NAME"), eq(false))
}
fun testOnNotificationRemoved_lockDownMode() {
@@ -950,7 +950,7 @@
val data = mediaDataCaptor.value
mediaDataProcessor.onNotificationRemoved(KEY)
- verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(listener, never()).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger, never())
.logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
@@ -1183,7 +1183,7 @@
mediaDataProcessor.setMediaResumptionEnabled(false)
// THEN the resume controls are dismissed
- verify(listener).onMediaDataRemoved(eq(PACKAGE_NAME))
+ verify(listener).onMediaDataRemoved(eq(PACKAGE_NAME), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
}
@@ -1191,19 +1191,19 @@
fun testDismissMedia_listenerCalled() {
addNotificationAndLoad()
val data = mediaDataCaptor.value
- val removed = mediaDataProcessor.dismissMediaData(KEY, 0L)
+ val removed = mediaDataProcessor.dismissMediaData(KEY, 0L, true)
assertThat(removed).isTrue()
foregroundExecutor.advanceClockToLast()
foregroundExecutor.runAllReady()
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(true))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
}
@Test
fun testDismissMedia_keyDoesNotExist_returnsFalse() {
- val removed = mediaDataProcessor.dismissMediaData(KEY, 0L)
+ val removed = mediaDataProcessor.dismissMediaData(KEY, 0L, true)
assertThat(removed).isFalse()
}
@@ -2102,7 +2102,7 @@
sessionCallbackCaptor.value.invoke(KEY)
// It remains as a regular player
- verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean())
verify(listener, never())
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
}
@@ -2118,7 +2118,7 @@
mediaDataProcessor.onNotificationRemoved(KEY)
// It is fully removed
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
verify(listener, never())
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
@@ -2171,7 +2171,7 @@
mediaDataProcessor.onNotificationRemoved(KEY)
// It remains as a regular player
- verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean())
verify(listener, never())
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
}
@@ -2224,7 +2224,7 @@
sessionCallbackCaptor.value.invoke(KEY)
// It is fully removed
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
verify(listener, never())
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
@@ -2278,7 +2278,7 @@
sessionCallbackCaptor.value.invoke(KEY)
// It is fully removed.
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
verify(listener, never())
.onMediaDataLoaded(
@@ -2304,7 +2304,7 @@
sessionCallbackCaptor.value.invoke(KEY)
// It is fully removed
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
verify(listener, never())
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
@@ -2354,7 +2354,7 @@
mediaDataProcessor.onNotificationRemoved(KEY)
// We still make sure to remove it
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
index a447e44..befe64c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
@@ -60,6 +60,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.any
@@ -158,7 +159,8 @@
@Test
fun removeUnknown() {
- manager.onMediaDataRemoved("unknown")
+ manager.onMediaDataRemoved("unknown", false)
+ verify(listener, never()).onKeyRemoved(eq(KEY), anyBoolean())
}
@Test
@@ -170,7 +172,7 @@
@Test
fun loadAndRemoveMediaData() {
manager.onMediaDataLoaded(KEY, null, mediaData)
- manager.onMediaDataRemoved(KEY)
+ manager.onMediaDataRemoved(KEY, false)
fakeBgExecutor.runAllReady()
verify(lmm).unregisterCallback(any())
verify(muteAwaitManager).stopListening()
@@ -386,9 +388,9 @@
fun listenerReceivesKeyRemoved() {
manager.onMediaDataLoaded(KEY, null, mediaData)
// WHEN the notification is removed
- manager.onMediaDataRemoved(KEY)
+ manager.onMediaDataRemoved(KEY, true)
// THEN the listener receives key removed event
- verify(listener).onKeyRemoved(eq(KEY))
+ verify(listener).onKeyRemoved(eq(KEY), eq(true))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt
index 5a3c220..030bca2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt
@@ -165,10 +165,10 @@
@Test
fun noMediaSession_removedEventNotFiltered() {
- filter.onMediaDataRemoved(KEY)
+ filter.onMediaDataRemoved(KEY, false)
bgExecutor.runAllReady()
fgExecutor.runAllReady()
- verify(mediaListener).onMediaDataRemoved(eq(KEY))
+ verify(mediaListener).onMediaDataRemoved(eq(KEY), eq(false))
}
@Test
@@ -193,11 +193,11 @@
whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
sessionListener.onActiveSessionsChanged(controllers)
// WHEN a removed event is received
- filter.onMediaDataRemoved(KEY)
+ filter.onMediaDataRemoved(KEY, false)
bgExecutor.runAllReady()
fgExecutor.runAllReady()
// THEN the event is not filtered
- verify(mediaListener).onMediaDataRemoved(eq(KEY))
+ verify(mediaListener).onMediaDataRemoved(eq(KEY), eq(false))
}
@Test
@@ -294,7 +294,7 @@
anyBoolean()
)
// AND there should be a removed event for key2
- verify(mediaListener).onMediaDataRemoved(eq(key2))
+ verify(mediaListener).onMediaDataRemoved(eq(key2), eq(false))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt
index 3cc65c9..cdbf9d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt
@@ -166,12 +166,12 @@
@Test
fun testOnMediaDataRemoved_unregistersPlaybackListener() {
mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
- mediaTimeoutListener.onMediaDataRemoved(KEY)
+ mediaTimeoutListener.onMediaDataRemoved(KEY, false)
verify(mediaController).unregisterCallback(anyObject())
// Ignores duplicate requests
clearInvocations(mediaController)
- mediaTimeoutListener.onMediaDataRemoved(KEY)
+ mediaTimeoutListener.onMediaDataRemoved(KEY, false)
verify(mediaController, never()).unregisterCallback(anyObject())
}
@@ -181,7 +181,7 @@
mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
assertThat(executor.numPending()).isEqualTo(1)
// WHEN the media is removed
- mediaTimeoutListener.onMediaDataRemoved(KEY)
+ mediaTimeoutListener.onMediaDataRemoved(KEY, false)
// THEN the timeout runnable is cancelled
assertThat(executor.numPending()).isEqualTo(0)
}
@@ -398,7 +398,7 @@
// WHEN we have a resume control
testOnMediaDataLoaded_resumption_registersTimeout()
// AND the media is removed
- mediaTimeoutListener.onMediaDataRemoved(PACKAGE)
+ mediaTimeoutListener.onMediaDataRemoved(PACKAGE, false)
// THEN the timeout runnable is cancelled
assertThat(executor.numPending()).isEqualTo(0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
index 0a5aace..3bb8b8f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
@@ -33,6 +33,8 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -74,12 +76,14 @@
import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
+import org.junit.After
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.anyLong
import org.mockito.Mockito.floatThat
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
@@ -136,6 +140,9 @@
private lateinit var testDispatcher: TestDispatcher
private lateinit var mediaCarouselController: MediaCarouselController
+ private var originalResumeSetting =
+ Settings.Secure.getInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 1)
+
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
@@ -186,6 +193,15 @@
)
}
+ @After
+ fun tearDown() {
+ Settings.Secure.putInt(
+ context.contentResolver,
+ Settings.Secure.MEDIA_CONTROLS_RESUME,
+ originalResumeSetting
+ )
+ }
+
@Test
fun testPlayerOrdering() {
// Test values: key, data, last active time
@@ -822,6 +838,7 @@
@Test
fun testKeyguardGone_showMediaCarousel() =
kosmos.testScope.runTest {
+ kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
var updatedVisibility = false
mediaCarouselController.updateHostVisibility = { updatedVisibility = true }
mediaCarouselController.mediaCarousel = mediaCarousel
@@ -844,6 +861,7 @@
@Test
fun keyguardShowing_notAllowedOnLockscreen_updateVisibility() {
kosmos.testScope.runTest {
+ kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
var updatedVisibility = false
mediaCarouselController.updateHostVisibility = { updatedVisibility = true }
mediaCarouselController.mediaCarousel = mediaCarousel
@@ -870,6 +888,7 @@
@Test
fun keyguardShowing_allowedOnLockscreen_updateVisibility() {
kosmos.testScope.runTest {
+ kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
var updatedVisibility = false
mediaCarouselController.updateHostVisibility = { updatedVisibility = true }
mediaCarouselController.mediaCarousel = mediaCarousel
@@ -968,6 +987,45 @@
verify(panel).updateAnimatorDurationScale()
}
+ @Test
+ fun swipeToDismiss_pausedAndResumeOff_userInitiated() {
+ // When resumption is disabled, paused media should be dismissed after being swiped away
+ Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
+
+ val pausedMedia = DATA.copy(isPlaying = false)
+ listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, pausedMedia)
+ mediaCarouselController.onSwipeToDismiss()
+
+ // When it can be removed immediately on update
+ whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(true)
+ val inactiveMedia = pausedMedia.copy(active = false)
+ listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, inactiveMedia)
+
+ // This is processed as a user-initiated dismissal
+ verify(debugLogger).logMediaRemoved(eq(PAUSED_LOCAL), eq(true))
+ verify(mediaDataManager).dismissMediaData(eq(PAUSED_LOCAL), anyLong(), eq(true))
+ }
+
+ @Test
+ fun swipeToDismiss_pausedAndResumeOff_delayed_userInitiated() {
+ // When resumption is disabled, paused media should be dismissed after being swiped away
+ Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
+ mediaCarouselController.updateHostVisibility = {}
+
+ val pausedMedia = DATA.copy(isPlaying = false)
+ listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, pausedMedia)
+ mediaCarouselController.onSwipeToDismiss()
+
+ // When it can't be removed immediately on update
+ whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(false)
+ val inactiveMedia = pausedMedia.copy(active = false)
+ listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, inactiveMedia)
+ visualStabilityCallback.value.onReorderingAllowed()
+
+ // This is processed as a user-initiated dismissal
+ verify(mediaDataManager).dismissMediaData(eq(PAUSED_LOCAL), anyLong(), eq(true))
+ }
+
/**
* Helper method when a configuration change occurs.
*
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
index 83e4d31..0c9fee9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
@@ -1344,7 +1344,7 @@
assertThat(dismiss.isEnabled).isEqualTo(true)
dismiss.callOnClick()
verify(logger).logLongPressDismiss(anyInt(), eq(PACKAGE), eq(instanceId))
- verify(mediaDataManager).dismissMediaData(eq(mediaKey), anyLong())
+ verify(mediaDataManager).dismissMediaData(eq(mediaKey), anyLong(), eq(true))
}
@Test
@@ -1360,7 +1360,8 @@
@Test
fun player_dismissButtonClick_notInManager() {
val mediaKey = "key for dismissal"
- whenever(mediaDataManager.dismissMediaData(eq(mediaKey), anyLong())).thenReturn(false)
+ whenever(mediaDataManager.dismissMediaData(eq(mediaKey), anyLong(), eq(true)))
+ .thenReturn(false)
player.attachPlayer(viewHolder)
val state = mediaData.copy(notificationKey = KEY)
@@ -1369,8 +1370,8 @@
assertThat(dismiss.isEnabled).isEqualTo(true)
dismiss.callOnClick()
- verify(mediaDataManager).dismissMediaData(eq(mediaKey), anyLong())
- verify(mediaCarouselController).removePlayer(eq(mediaKey), eq(false), eq(false))
+ verify(mediaDataManager).dismissMediaData(eq(mediaKey), anyLong(), eq(true))
+ verify(mediaCarouselController).removePlayer(eq(mediaKey), eq(false), eq(false), eq(true))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
index ff7c970..8f8630e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
@@ -104,11 +104,11 @@
listener.onMediaDataLoaded(mKey, mOldKey, mData, /* immediately= */true,
/* receivedSmartspaceCardLatency= */0, /* isSsReactived= */ false);
- listener.onMediaDataRemoved(mKey);
+ listener.onMediaDataRemoved(mKey, false);
verify(mDreamOverlayStateController, never()).removeComplication(any());
when(mMediaDataManager.hasActiveMedia()).thenReturn(false);
- listener.onMediaDataRemoved(mKey);
+ listener.onMediaDataRemoved(mKey, false);
verify(mDreamOverlayStateController).removeComplication(eq(mMediaEntryComplication));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt b/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt
index 8e05410..c06a28e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt
@@ -42,13 +42,13 @@
fun updateFlags() {
underTest.updateFlags(
Display.DEFAULT_DISPLAY,
- 1 to true,
- 2 to false,
- 3 to true,
+ 1L to true,
+ 2L to false,
+ 3L to true,
)
- assertThat(underTest.flags and 1).isNotEqualTo(0)
- assertThat(underTest.flags and 2).isEqualTo(0)
- assertThat(underTest.flags and 3).isNotEqualTo(0)
+ assertThat(underTest.flags and 1L).isNotEqualTo(0L)
+ assertThat(underTest.flags and 2L).isEqualTo(0L)
+ assertThat(underTest.flags and 3L).isNotEqualTo(0L)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
index 224e755..2ff660f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
@@ -125,7 +125,7 @@
private AccessibilityManager.AccessibilityServicesStateChangeListener
mAccessibilityServicesStateChangeListener;
- private static final int ACCESSIBILITY_BUTTON_CLICKABLE_STATE =
+ private static final long ACCESSIBILITY_BUTTON_CLICKABLE_STATE =
SYSUI_STATE_A11Y_BUTTON_CLICKABLE | SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
private NavBarHelper mNavBarHelper;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 0e7a215..6cea1e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -38,7 +38,7 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
@@ -300,7 +300,7 @@
doNothing().when(mWindowManager).addView(any(), any());
doNothing().when(mWindowManager).removeViewImmediate(any());
mMockSysUiState = mock(SysUiState.class);
- when(mMockSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mMockSysUiState);
+ when(mMockSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mMockSysUiState);
mContext.addMockSystemService(WindowManager.class, mWindowManager);
mSysuiTestableContextExternal.addMockSystemService(WindowManager.class, mWindowManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt
index 8d01e80d..bba275e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt
@@ -16,18 +16,18 @@
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.wm.shell.back.BackAnimation
import com.android.wm.shell.pip.Pip
+import java.util.Optional
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentMatchers
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.any
import org.mockito.Mockito.anyBoolean
-import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyLong
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
-import java.util.Optional
@SmallTest
class TaskbarDelegateTest : SysuiTestCase() {
@@ -74,7 +74,7 @@
`when`(mNavBarHelper.edgeBackGestureHandler).thenReturn(mEdgeBackGestureHandler)
`when`(mLightBarControllerFactory.create(any())).thenReturn(mLightBarTransitionController)
`when`(mNavBarHelper.currentSysuiState).thenReturn(mCurrentSysUiState)
- `when`(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState)
+ `when`(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState)
mTaskStackChangeListeners = TaskStackChangeListeners.getTestInstance()
mTaskbarDelegate = TaskbarDelegate(context, mLightBarControllerFactory,
mStatusBarKeyguardViewManager)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
index f88a5a0..b75b318 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
@@ -20,7 +20,7 @@
import static junit.framework.Assert.assertNotSame;
import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -81,7 +81,7 @@
public void setup() {
MockitoAnnotations.initMocks(this);
- when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
+ when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState);
when(mSystemUIDialogFactory.create()).thenReturn(mSystemUIDialog);
when(mSystemUIDialog.getContext()).thenReturn(mContext);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
index effae5f..74deae3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
@@ -72,7 +72,7 @@
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.atLeast
import org.mockito.Mockito.clearInvocations
-import org.mockito.Mockito.intThat
+import org.mockito.Mockito.longThat
import org.mockito.Mockito.mock
import org.mockito.Mockito.spy
import org.mockito.Mockito.times
@@ -162,7 +162,7 @@
verify(overviewProxy)
.onSystemUiStateChanged(
- intThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_AWAKE }
+ longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_AWAKE }
)
}
@@ -172,7 +172,7 @@
verify(overviewProxy)
.onSystemUiStateChanged(
- intThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_WAKING }
+ longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_WAKING }
)
}
@@ -182,7 +182,7 @@
verify(overviewProxy)
.onSystemUiStateChanged(
- intThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_ASLEEP }
+ longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_ASLEEP }
)
}
@@ -194,7 +194,7 @@
verify(overviewProxy)
.onSystemUiStateChanged(
- intThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_GOING_TO_SLEEP }
+ longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_GOING_TO_SLEEP }
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
index 6846c72..fcc6b4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
@@ -55,6 +55,7 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
@@ -94,7 +95,7 @@
fun setup() {
MockitoAnnotations.initMocks(this)
whenever(dprLazy.get()).thenReturn(devicePolicyResolver)
- whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState)
+ whenever(sysuiState.setFlag(anyLong(), anyBoolean())).thenReturn(sysuiState)
whenever(screenCaptureDisabledDialogDelegate.createSysUIDialog())
.thenReturn(screenCaptureDisabledDialog)
whenever(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 5b47c94..766113f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -655,7 +655,7 @@
when(mView.getParent()).thenReturn(mViewParent);
when(mQs.getHeader()).thenReturn(mQsHeader);
when(mDownMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_DOWN);
- when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
+ when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState);
mMainHandler = new Handler(Looper.getMainLooper());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index 04fa590..845744a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -42,13 +42,10 @@
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FakeFeatureFlagsClassic;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.keyguard.data.repository.FakeCommandQueue;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
-import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.kosmos.KosmosJavaAdapter;
@@ -59,9 +56,7 @@
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.qs.QSFragmentLegacy;
import com.android.systemui.res.R;
-import com.android.systemui.scene.data.repository.SceneContainerRepository;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.logger.SceneLogger;
import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
@@ -176,12 +171,7 @@
protected Handler mMainHandler;
protected LockscreenShadeTransitionController.Callback mLockscreenShadeTransitionCallback;
- protected final ShadeExpansionStateManager mShadeExpansionStateManager =
- new ShadeExpansionStateManager();
-
protected FragmentHostManager.FragmentListener mFragmentListener;
- private FromLockscreenTransitionInteractor mFromLockscreenTransitionInteractor;
- private FromPrimaryBouncerTransitionInteractor mFromPrimaryBouncerTransitionInteractor;
@Before
public void setup() {
@@ -190,19 +180,11 @@
mStatusBarStateController = mKosmos.getStatusBarStateController();
mKosmos.getFakeDeviceProvisioningRepository().setDeviceProvisioned(true);
- FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic();
FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
PowerInteractor powerInteractor = mKosmos.getPowerInteractor();
- SceneInteractor sceneInteractor = new SceneInteractor(
- mTestScope.getBackgroundScope(),
- new SceneContainerRepository(
- mTestScope.getBackgroundScope(),
- mKosmos.getFakeSceneContainerConfig(),
- mKosmos.getSceneDataSource()),
- mock(SceneLogger.class),
- mKosmos.getDeviceUnlockedInteractor());
+ SceneInteractor sceneInteractor = mKosmos.getSceneInteractor();
KeyguardTransitionInteractor keyguardTransitionInteractor =
mKosmos.getKeyguardTransitionInteractor();
@@ -220,10 +202,6 @@
() -> mKosmos.getSharedNotificationContainerInteractor(),
mTestScope);
- mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
- mFromPrimaryBouncerTransitionInteractor =
- mKosmos.getFromPrimaryBouncerTransitionInteractor();
-
ResourcesSplitShadeStateController splitShadeStateController =
new ResourcesSplitShadeStateController();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index c088609..fe6a88d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -51,6 +51,7 @@
import com.android.systemui.shade.ShadeHeaderController;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -80,6 +81,7 @@
@Mock private QuickSettingsController mQuickSettingsController;
@Mock private ShadeViewController mShadeViewController;
@Mock private PanelExpansionInteractor mPanelExpansionInteractor;
+ @Mock private Lazy<ShadeInteractor> mShadeInteractorLazy;
@Mock private ShadeHeaderController mShadeHeaderController;
@Mock private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
private final MetricsLogger mMetricsLogger = new FakeMetricsLogger();
@@ -115,6 +117,7 @@
mShadeController,
mCommandQueue,
mPanelExpansionInteractor,
+ mShadeInteractorLazy,
mShadeHeaderController,
mRemoteInputQuickSettingsDisabler,
mMetricsLogger,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt
index 408157b..3e69e87 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt
@@ -17,7 +17,10 @@
package com.android.systemui.keyguard.data.repository
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
var Kosmos.keyguardTransitionRepository: KeyguardTransitionRepository by
Kosmos.Fixture { fakeKeyguardTransitionRepository }
var Kosmos.fakeKeyguardTransitionRepository by Kosmos.Fixture { FakeKeyguardTransitionRepository() }
+var Kosmos.realKeyguardTransitionRepository: KeyguardTransitionRepository by
+ Kosmos.Fixture { KeyguardTransitionRepositoryImpl(testDispatcher) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/LockscreenSceneTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/LockscreenSceneTransitionRepository.kt
new file mode 100644
index 0000000..7d0e8b1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/LockscreenSceneTransitionRepository.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.lockscreenSceneTransitionRepository by
+ Kosmos.Fixture { LockscreenSceneTransitionRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
new file mode 100644
index 0000000..3c1f7b1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor.scenetransition
+
+import com.android.systemui.keyguard.data.repository.lockscreenSceneTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+
+var Kosmos.lockscreenSceneTransitionInteractor by
+ Kosmos.Fixture {
+ LockscreenSceneTransitionInteractor(
+ transitionInteractor = keyguardTransitionInteractor,
+ applicationScope = applicationCoroutineScope,
+ sceneInteractor = sceneInteractor,
+ repository = lockscreenSceneTransitionRepository,
+ )
+ }
diff --git a/services/backup/java/com/android/server/backup/transport/TransportConnection.java b/services/backup/java/com/android/server/backup/transport/TransportConnection.java
index 1009787..67ebb3e 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportConnection.java
+++ b/services/backup/java/com/android/server/backup/transport/TransportConnection.java
@@ -658,11 +658,13 @@
* This class is a proxy to TransportClient methods that doesn't hold a strong reference to the
* TransportClient, allowing it to be GC'ed. If the reference was lost it logs a message.
*/
- private static class TransportConnectionMonitor implements ServiceConnection {
+ @VisibleForTesting
+ static class TransportConnectionMonitor implements ServiceConnection {
private final Context mContext;
private final WeakReference<TransportConnection> mTransportClientRef;
- private TransportConnectionMonitor(Context context,
+ @VisibleForTesting
+ TransportConnectionMonitor(Context context,
TransportConnection transportConnection) {
mContext = context;
mTransportClientRef = new WeakReference<>(transportConnection);
@@ -704,7 +706,13 @@
/** @see TransportConnection#finalize() */
private void referenceLost(String caller) {
- mContext.unbindService(this);
+ try {
+ mContext.unbindService(this);
+ } catch (IllegalArgumentException e) {
+ TransportUtils.log(Priority.WARN, TAG,
+ caller + " called but unbindService failed: " + e.getMessage());
+ return;
+ }
TransportUtils.log(
Priority.INFO,
TAG,
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index 9069689..026d29c 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -135,7 +135,7 @@
*/
public PendingIntent buildPermissionTransferUserConsentIntent(String packageName,
@UserIdInt int userId, int associationId) {
- if (PackageUtils.isPackageAllowlisted(mContext, mPackageManager, packageName)) {
+ if (PackageUtils.isPermSyncAutoEnabled(mContext, mPackageManager, packageName)) {
Slog.i(LOG_TAG, "User consent Intent should be skipped. Returning null.");
// Auto enable perm sync for the allowlisted packages, but don't override user decision
PermissionSyncRequest request = getPermissionSyncRequest(associationId);
diff --git a/services/companion/java/com/android/server/companion/utils/PackageUtils.java b/services/companion/java/com/android/server/companion/utils/PackageUtils.java
index 254d28b..94ab9dd 100644
--- a/services/companion/java/com/android/server/companion/utils/PackageUtils.java
+++ b/services/companion/java/com/android/server/companion/utils/PackageUtils.java
@@ -21,6 +21,11 @@
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.os.Binder.getCallingUid;
+import static com.android.internal.R.array.config_companionDeviceCerts;
+import static com.android.internal.R.array.config_companionDevicePackages;
+import static com.android.internal.R.array.config_companionPermSyncEnabledCerts;
+import static com.android.internal.R.array.config_companionPermSyncEnabledPackages;
+
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -185,15 +190,30 @@
*/
public static boolean isPackageAllowlisted(Context context,
PackageManagerInternal packageManagerInternal, @NonNull String packageName) {
- final String[] allowlistedPackages = context.getResources()
- .getStringArray(com.android.internal.R.array.config_companionDevicePackages);
+ return isPackageAllowlisted(context, packageManagerInternal, packageName,
+ config_companionDevicePackages, config_companionDeviceCerts);
+ }
+
+ /**
+ * Check if perm sync is allowlisted and auto-enabled for the package.
+ */
+ public static boolean isPermSyncAutoEnabled(Context context,
+ PackageManagerInternal packageManagerInternal, String packageName) {
+ return isPackageAllowlisted(context, packageManagerInternal, packageName,
+ config_companionPermSyncEnabledPackages, config_companionPermSyncEnabledCerts);
+ }
+
+ private static boolean isPackageAllowlisted(Context context,
+ PackageManagerInternal packageManagerInternal, String packageName,
+ int packagesConfig, int certsConfig) {
+ final String[] allowlistedPackages = context.getResources().getStringArray(packagesConfig);
if (!ArrayUtils.contains(allowlistedPackages, packageName)) {
Slog.d(TAG, packageName + " is not allowlisted.");
return false;
}
final String[] allowlistedPackagesSignatureDigests = context.getResources()
- .getStringArray(com.android.internal.R.array.config_companionDeviceCerts);
+ .getStringArray(certsConfig);
final Set<String> allowlistedSignatureDigestsForRequestingPackage = new HashSet<>();
for (int i = 0; i < allowlistedPackages.length; i++) {
if (allowlistedPackages[i].equals(packageName)) {
diff --git a/services/core/Android.bp b/services/core/Android.bp
index d153c18..dc1155a 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -232,6 +232,7 @@
"android.hardware.rebootescrow-V1-java",
"android.hardware.power.stats-V2-java",
"android.hidl.manager-V1.2-java",
+ "audio-permission-aidl-java",
"cbor-java",
"com.android.media.audio-aconfig-java",
"icu4j_calendar_astronomer",
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 1db3483..ad93f6f 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -55,6 +55,7 @@
import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM;
import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM_OPS;
import static android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE;
+import static android.app.AppOpsManager.UID_STATE_NONEXISTENT;
import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES;
import static android.app.AppOpsManager._NUM_OP;
import static android.app.AppOpsManager.extractFlagsFromKey;
@@ -70,7 +71,6 @@
import static android.content.Intent.EXTRA_REPLACING;
import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
-import static android.permission.flags.Flags.runtimePermissionAppopsMappingEnabled;
import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
@@ -130,6 +130,7 @@
import android.os.UserHandle;
import android.os.storage.StorageManagerInternal;
import android.permission.PermissionManager;
+import android.permission.flags.Flags;
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -140,6 +141,7 @@
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
+import android.util.SparseLongArray;
import android.util.TimeUtils;
import android.util.Xml;
@@ -153,7 +155,6 @@
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IAppOpsStartedCallback;
import com.android.internal.app.MessageSamplingConfig;
-import com.android.internal.camera.flags.Flags;
import com.android.internal.compat.IPlatformCompat;
import com.android.internal.os.Clock;
import com.android.internal.pm.pkg.component.ParsedAttribution;
@@ -1421,6 +1422,9 @@
// The callback method from AppOpsUidStateTracker
private void onUidStateChanged(int uid, int state, boolean foregroundModeMayChange) {
synchronized (this) {
+ if (state == UID_STATE_NONEXISTENT) {
+ onUidProcessDeathLocked(uid);
+ }
UidState uidState = getUidStateLocked(uid, false);
boolean hasForegroundWatchers = false;
@@ -1508,6 +1512,11 @@
}
}
+ if (state == UID_STATE_NONEXISTENT) {
+ // For UID_STATE_NONEXISTENT, we don't call onUidStateChanged for AttributedOps
+ return;
+ }
+
if (uidState != null) {
int numPkgs = uidState.pkgOps.size();
for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
@@ -1532,6 +1541,81 @@
}
}
+ @GuardedBy("this")
+ private void onUidProcessDeathLocked(int uid) {
+ if (!mUidStates.contains(uid) || !Flags.finishRunningOpsForKilledPackages()) {
+ return;
+ }
+ final SparseLongArray chainsToFinish = new SparseLongArray();
+ doForAllAttributedOpsInUidLocked(uid, (attributedOp) -> {
+ attributedOp.doForAllInProgressStartOpEvents((event) -> {
+ int chainId = event.getAttributionChainId();
+ if (chainId != ATTRIBUTION_CHAIN_ID_NONE) {
+ long currentEarliestStartTime =
+ chainsToFinish.get(chainId, Long.MAX_VALUE);
+ if (event.getStartTime() < currentEarliestStartTime) {
+ // Store the earliest chain link we're finishing, so that we can go back
+ // and finish any links in the chain that started after this one
+ chainsToFinish.put(chainId, event.getStartTime());
+ }
+ }
+ attributedOp.finished(event.getClientId());
+ });
+ });
+ finishChainsLocked(chainsToFinish);
+ }
+
+ @GuardedBy("this")
+ private void finishChainsLocked(SparseLongArray chainsToFinish) {
+ doForAllAttributedOpsLocked((attributedOp) -> {
+ attributedOp.doForAllInProgressStartOpEvents((event) -> {
+ int chainId = event.getAttributionChainId();
+ // If this event is part of a chain, and this event started after the event in the
+ // chain we already finished, then finish this event, too
+ long earliestEventStart = chainsToFinish.get(chainId, Long.MAX_VALUE);
+ if (chainId != ATTRIBUTION_CHAIN_ID_NONE
+ && event.getStartTime() >= earliestEventStart) {
+ attributedOp.finished(event.getClientId());
+ }
+ });
+ });
+ }
+
+ @GuardedBy("this")
+ private void doForAllAttributedOpsLocked(Consumer<AttributedOp> action) {
+ int numUids = mUidStates.size();
+ for (int uidNum = 0; uidNum < numUids; uidNum++) {
+ int uid = mUidStates.keyAt(uidNum);
+ doForAllAttributedOpsInUidLocked(uid, action);
+ }
+ }
+
+ @GuardedBy("this")
+ private void doForAllAttributedOpsInUidLocked(int uid, Consumer<AttributedOp> action) {
+ UidState uidState = mUidStates.get(uid);
+ if (uidState == null) {
+ return;
+ }
+
+ int numPkgs = uidState.pkgOps.size();
+ for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
+ Ops ops = uidState.pkgOps.valueAt(pkgNum);
+ int numOps = ops.size();
+ for (int opNum = 0; opNum < numOps; opNum++) {
+ Op op = ops.valueAt(opNum);
+ int numDevices = op.mDeviceAttributedOps.size();
+ for (int deviceNum = 0; deviceNum < numDevices; deviceNum++) {
+ ArrayMap<String, AttributedOp> attrOps =
+ op.mDeviceAttributedOps.valueAt(deviceNum);
+ int numAttributions = attrOps.size();
+ for (int attrNum = 0; attrNum < numAttributions; attrNum++) {
+ action.accept(attrOps.valueAt(attrNum));
+ }
+ }
+ }
+ }
+ }
+
/**
* Notify the proc state or capability has changed for a certain UID.
*/
@@ -2702,7 +2786,7 @@
* have information on them.
*/
private static boolean isOpAllowedForUid(int uid) {
- return runtimePermissionAppopsMappingEnabled()
+ return Flags.runtimePermissionAppopsMappingEnabled()
&& (uid == Process.ROOT_UID || uid == Process.SYSTEM_UID);
}
@@ -4775,8 +4859,8 @@
if ((code == OP_CAMERA) && isAutomotive()) {
final long identity = Binder.clearCallingIdentity();
try {
- if ((Flags.cameraPrivacyAllowlist())
- && (mSensorPrivacyManager.isCameraPrivacyEnabled(packageName))) {
+ if (com.android.internal.camera.flags.Flags.cameraPrivacyAllowlist()
+ && mSensorPrivacyManager.isCameraPrivacyEnabled(packageName)) {
return true;
}
} finally {
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java b/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java
index 18ea8cf..268b286 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java
@@ -68,6 +68,7 @@
return UID_STATE_BACKGROUND;
}
+ // UID_STATE_NONEXISTENT is deliberately excluded here
return UID_STATE_CACHED;
}
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
index bc6ef20..03c8156 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
@@ -34,7 +34,9 @@
import static android.app.AppOpsManager.OP_TAKE_AUDIO_FOCUS;
import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE;
import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED;
+import static android.app.AppOpsManager.UID_STATE_NONEXISTENT;
import static android.app.AppOpsManager.UID_STATE_TOP;
+import static android.permission.flags.Flags.finishRunningOpsForKilledPackages;
import static com.android.server.appop.AppOpsUidStateTracker.processStateToUidState;
@@ -343,13 +345,14 @@
int capability = mCapability.get(uid, PROCESS_CAPABILITY_NONE);
boolean appWidgetVisible = mAppWidgetVisible.get(uid, false);
+ boolean foregroundChange = uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
+ != pendingUidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
+ || capability != pendingCapability
+ || appWidgetVisible != pendingAppWidgetVisible;
+
if (uidState != pendingUidState
|| capability != pendingCapability
|| appWidgetVisible != pendingAppWidgetVisible) {
- boolean foregroundChange = uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
- != pendingUidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
- || capability != pendingCapability
- || appWidgetVisible != pendingAppWidgetVisible;
if (foregroundChange) {
// To save on memory usage, log only interesting changes.
@@ -372,6 +375,16 @@
mCapability.delete(uid);
mAppWidgetVisible.delete(uid);
mPendingGone.delete(uid);
+ if (finishRunningOpsForKilledPackages()) {
+ for (int i = 0; i < mUidStateChangedCallbacks.size(); i++) {
+ UidStateChangedCallback cb = mUidStateChangedCallbacks.keyAt(i);
+ Executor executor = mUidStateChangedCallbacks.valueAt(i);
+
+ executor.execute(PooledLambda.obtainRunnable(
+ UidStateChangedCallback::onUidStateChanged, cb, uid,
+ UID_STATE_NONEXISTENT, foregroundChange));
+ }
+ }
} else {
mUidStates.put(uid, pendingUidState);
mCapability.put(uid, pendingCapability);
diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java
index 2760ccf..02fc993 100644
--- a/services/core/java/com/android/server/appop/AttributedOp.java
+++ b/services/core/java/com/android/server/appop/AttributedOp.java
@@ -38,6 +38,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
+import java.util.function.Consumer;
final class AttributedOp {
private final @NonNull AppOpsService mAppOpsService;
@@ -256,6 +257,19 @@
}
}
+ public void doForAllInProgressStartOpEvents(Consumer<InProgressStartOpEvent> action) {
+ ArrayMap<IBinder, AttributedOp.InProgressStartOpEvent> events = isPaused()
+ ? mPausedInProgressEvents : mInProgressEvents;
+ if (events == null) {
+ return;
+ }
+
+ int numStartedOps = events.size();
+ for (int i = 0; i < numStartedOps; i++) {
+ action.accept(events.valueAt(i));
+ }
+ }
+
/**
* Update state when finishOp was called. Will finish started ops, and delete paused ops.
*
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index c310822..15c5c10 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -4440,7 +4440,8 @@
|| usage == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING) {
voiceActive = true;
}
- if (usage == AudioAttributes.USAGE_MEDIA || usage == AudioAttributes.USAGE_GAME) {
+ if (usage == AudioAttributes.USAGE_MEDIA || usage == AudioAttributes.USAGE_GAME
+ || usage == AudioAttributes.USAGE_UNKNOWN) {
mediaActive = true;
}
}
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index e2c4b46..cae1695 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -347,9 +347,6 @@
//------------------------------------------------------
// routing monitoring
synchronized void onRoutingUpdated() {
- if (!mFeatureEnabled) {
- return;
- }
switch (mState) {
case STATE_UNINITIALIZED:
case STATE_NOT_SUPPORTED:
@@ -393,7 +390,7 @@
setDispatchAvailableState(false);
}
- boolean enabled = able && enabledAvailable.first;
+ boolean enabled = mFeatureEnabled && able && enabledAvailable.first;
if (enabled) {
loglogi("Enabling Spatial Audio since enabled for media device:"
+ currentDevice);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 848f74e..67df992 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -485,16 +485,6 @@
return userData.mBindingController.getSelectedMethodId();
}
- /**
- * The current binding sequence number, incremented every time there is
- * a new bind performed.
- */
- @GuardedBy("ImfLock.class")
- private int getSequenceNumberLocked() {
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- return userData.mBindingController.getSequenceNumber();
- }
-
@GuardedBy("ImfLock.class")
@Nullable
InputMethodInfo queryInputMethodForCurrentUserLocked(@NonNull String imeId) {
@@ -1915,7 +1905,6 @@
return mClientController.getClient(client.asBinder());
}
- // TODO(b/314150112): Move this to ClientController.
@GuardedBy("ImfLock.class")
void unbindCurrentClientLocked(@UnbindReason int unbindClientReason) {
if (mCurClient != null) {
@@ -1935,7 +1924,13 @@
// all accessibility too. That means, when input method get disconnected (including
// switching ime), we also unbind accessibility
mCurClient.mClient.setActive(false /* active */, false /* fullscreen */);
- mCurClient.mClient.onUnbindMethod(getSequenceNumberLocked(), unbindClientReason);
+
+ // TODO(b/325515685): make binding controller user independent. Before this change, the
+ // following dependencies also need to be user independent: mCurClient, mBoundToMethod,
+ // getCurMethodLocked(), and mMenuController.
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ mCurClient.mClient.onUnbindMethod(userData.mBindingController.getSequenceNumber(),
+ unbindClientReason);
mCurClient.mSessionRequested = false;
mCurClient.mSessionRequestedForAccessibility = false;
mCurClient = null;
@@ -2013,12 +2008,14 @@
final boolean restarting = !initial;
final Binder startInputToken = new Binder();
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
final StartInputInfo info = new StartInputInfo(mCurrentUserId,
getCurTokenLocked(),
mCurTokenDisplayId, getCurIdLocked(), startInputReason, restarting,
UserHandle.getUserId(mCurClient.mUid),
mCurClient.mSelfReportedDisplayId, mImeBindingState.mFocusedWindow, mCurEditorInfo,
- mImeBindingState.mFocusedWindowSoftInputMode, getSequenceNumberLocked());
+ mImeBindingState.mFocusedWindowSoftInputMode,
+ userData.mBindingController.getSequenceNumber());
mImeTargetWindowMap.put(startInputToken, mImeBindingState.mFocusedWindow);
mStartInputHistory.addEntry(info);
@@ -2051,21 +2048,20 @@
null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT);
}
- String curId = getCurIdLocked();
+ final var curId = getCurIdLocked();
final InputMethodInfo curInputMethodInfo = InputMethodSettingsRepository.get(mCurrentUserId)
.getMethodMap().get(curId);
final boolean suppressesSpellChecker =
curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker();
final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions =
createAccessibilityInputMethodSessions(mCurClient.mAccessibilitySessions);
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
if (userData.mBindingController.supportsStylusHandwriting() && hasSupportedStylusLocked()) {
mHwController.setInkWindowInitializer(new InkWindowInitializer());
}
return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
session.mSession, accessibilityInputMethodSessions,
(session.mChannel != null ? session.mChannel.dup() : null),
- curId, getSequenceNumberLocked(), suppressesSpellChecker);
+ curId, userData.mBindingController.getSequenceNumber(), suppressesSpellChecker);
}
@GuardedBy("ImfLock.class")
@@ -2341,7 +2337,8 @@
requestClientSessionForAccessibilityLocked(cs);
return new InputBindResult(
InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION,
- null, null, null, getCurIdLocked(), getSequenceNumberLocked(), false);
+ null, null, null, getCurIdLocked(),
+ userData.mBindingController.getSequenceNumber(), false);
} else {
final long lastBindTime = userData.mBindingController.getLastBindTime();
long bindingDuration = SystemClock.uptimeMillis() - lastBindTime;
@@ -2355,7 +2352,8 @@
// to see if we can get back in touch with the service.
return new InputBindResult(
InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
- null, null, null, getCurIdLocked(), getSequenceNumberLocked(), false);
+ null, null, null, getCurIdLocked(),
+ userData.mBindingController.getSequenceNumber(), false);
} else {
EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
getSelectedMethodIdLocked(), bindingDuration, 0);
@@ -3567,12 +3565,14 @@
"InputMethodManagerService#startInputOrWindowGainedFocus", mDumper);
final InputBindResult result;
synchronized (ImfLock.class) {
+ final var userData = mUserDataRepository.getOrCreate(userId);
// If the system is not yet ready, we shouldn't be running third party code.
if (!mSystemReady) {
return new InputBindResult(
InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY,
null /* method */, null /* accessibilitySessions */, null /* channel */,
- getSelectedMethodIdLocked(), getSequenceNumberLocked(),
+ getSelectedMethodIdLocked(),
+ userData.mBindingController.getSequenceNumber(),
false /* isInputMethodSuppressingSpellChecker */);
}
final ClientState cs = mClientController.getClient(client.asBinder());
@@ -3664,7 +3664,7 @@
result = startInputOrWindowGainedFocusInternalLocked(startInputReason,
client, windowToken, startInputFlags, softInputMode, windowFlags,
editorInfo, inputConnection, remoteAccessibilityInputConnection,
- unverifiedTargetSdkVersion, userId, imeDispatcher, cs);
+ unverifiedTargetSdkVersion, userData, imeDispatcher, cs);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -3692,7 +3692,7 @@
@SoftInputModeFlags int softInputMode, int windowFlags, EditorInfo editorInfo,
IRemoteInputConnection inputContext,
@Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
- int unverifiedTargetSdkVersion, @UserIdInt int userId,
+ int unverifiedTargetSdkVersion, @NonNull UserDataRepository.UserData userData,
@NonNull ImeOnBackInvokedDispatcher imeDispatcher, @NonNull ClientState cs) {
if (DEBUG) {
Slog.v(TAG, "startInputOrWindowGainedFocusInternalLocked: reason="
@@ -3705,7 +3705,7 @@
+ " softInputMode=" + InputMethodDebug.softInputModeToString(softInputMode)
+ " windowFlags=#" + Integer.toHexString(windowFlags)
+ " unverifiedTargetSdkVersion=" + unverifiedTargetSdkVersion
- + " userId=" + userId
+ + " userData=" + userData
+ " imeDispatcher=" + imeDispatcher
+ " cs=" + cs);
}
@@ -3724,7 +3724,6 @@
startInputByWinGainedFocus, toolType);
mVisibilityStateComputer.setWindowState(windowToken, windowState);
- final var userData = mUserDataRepository.getOrCreate(userId);
if (sameWindowFocused && isTextEditor) {
if (DEBUG) {
Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client
@@ -4429,7 +4428,7 @@
final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
final long token = proto.start(fieldId);
proto.write(CUR_METHOD_ID, getSelectedMethodIdLocked());
- proto.write(CUR_SEQ, getSequenceNumberLocked());
+ proto.write(CUR_SEQ, userData.mBindingController.getSequenceNumber());
proto.write(CUR_CLIENT, Objects.toString(mCurClient));
mImeBindingState.dumpDebug(proto, mWindowManagerInternal);
proto.write(LAST_IME_TARGET_WINDOW_NAME,
@@ -5626,6 +5625,7 @@
public void onSessionForAccessibilityCreated(int accessibilityConnectionId,
IAccessibilityInputMethodSession session, @UserIdInt int userId) {
synchronized (ImfLock.class) {
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
// TODO(b/305829876): Implement user ID verification
if (mCurClient != null) {
clearClientSessionForAccessibilityLocked(mCurClient, accessibilityConnectionId);
@@ -5647,8 +5647,10 @@
mCurClient.mAccessibilitySessions);
final InputBindResult res = new InputBindResult(
InputBindResult.ResultCode.SUCCESS_WITH_ACCESSIBILITY_SESSION,
- imeSession, accessibilityInputMethodSessions, null, getCurIdLocked(),
- getSequenceNumberLocked(), false);
+ imeSession, accessibilityInputMethodSessions, /* channel= */ null,
+ getCurIdLocked(),
+ userData.mBindingController.getSequenceNumber(),
+ /* isInputMethodSuppressingSpellChecker= */ false);
mCurClient.mClient.onBindAccessibilityService(res, accessibilityConnectionId);
}
}
@@ -5658,6 +5660,7 @@
public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId,
@UserIdInt int userId) {
synchronized (ImfLock.class) {
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
// TODO(b/305829876): Implement user ID verification
if (mCurClient != null) {
if (DEBUG) {
@@ -5667,7 +5670,7 @@
// A11yManagerService unbinds the disabled accessibility service. We don't need
// to do it here.
mCurClient.mClient.onUnbindAccessibilityService(
- getSequenceNumberLocked(),
+ userData.mBindingController.getSequenceNumber(),
accessibilityConnectionId);
}
// We only have sessions when we bound to an input method. Remove this session
@@ -5877,13 +5880,11 @@
@SuppressWarnings("GuardedBy") Consumer<ClientState> clientControllerDump = c -> {
p.println(" " + c + ":");
p.println(" client=" + c.mClient);
-
p.println(" fallbackInputConnection="
+ c.mFallbackInputConnection);
p.println(" sessionRequested="
+ c.mSessionRequested);
- p.println(
- " sessionRequestedForAccessibility="
+ p.println(" sessionRequestedForAccessibility="
+ c.mSessionRequestedForAccessibility);
p.println(" curSession=" + c.mCurSession);
p.println(" selfReportedDisplayId=" + c.mSelfReportedDisplayId);
@@ -5891,13 +5892,15 @@
p.println(" pid=" + c.mPid);
};
mClientController.forAllClients(clientControllerDump);
+ final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
p.println(" mCurrentUserId=" + mCurrentUserId);
p.println(" mCurMethodId=" + getSelectedMethodIdLocked());
client = mCurClient;
- p.println(" mCurClient=" + client + " mCurSeq=" + getSequenceNumberLocked());
+ p.println(" mCurClient=" + client + " mCurSeq="
+ + userData.mBindingController.getSequenceNumber());
p.println(" mFocusedWindowPerceptible=" + mFocusedWindowPerceptible);
mImeBindingState.dump(/* prefix= */ " ", p);
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+
p.println(" mCurId=" + getCurIdLocked()
+ " mHaveConnection=" + userData.mBindingController.hasMainConnection()
+ " mBoundToMethod=" + mBoundToMethod + " mVisibleBound="
diff --git a/services/core/java/com/android/server/inputmethod/UserDataRepository.java b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
index 825cfcb..2b19d3e 100644
--- a/services/core/java/com/android/server/inputmethod/UserDataRepository.java
+++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
@@ -96,5 +96,10 @@
mUserId = userId;
mBindingController = bindingController;
}
+
+ @Override
+ public String toString() {
+ return "UserData{" + "mUserId=" + mUserId + '}';
+ }
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 61054a9..4f87c83 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -593,6 +593,8 @@
static final long NOTIFICATION_TTL = Duration.ofDays(3).toMillis();
+ static final long NOTIFICATION_MAX_AGE_AT_POST = Duration.ofDays(14).toMillis();
+
private IActivityManager mAm;
private ActivityTaskManagerInternal mAtm;
private ActivityManager mActivityManager;
@@ -2637,27 +2639,48 @@
* Cleanup broadcast receivers change listeners.
*/
public void onDestroy() {
- getContext().unregisterReceiver(mIntentReceiver);
- getContext().unregisterReceiver(mPackageIntentReceiver);
- if (Flags.allNotifsNeedTtl()) {
- mTtlHelper.destroy();
- } else {
- getContext().unregisterReceiver(mNotificationTimeoutReceiver);
+ if (mIntentReceiver != null) {
+ getContext().unregisterReceiver(mIntentReceiver);
}
- getContext().unregisterReceiver(mRestoreReceiver);
- getContext().unregisterReceiver(mLocaleChangeReceiver);
-
- mSettingsObserver.destroy();
- mRoleObserver.destroy();
+ if (mPackageIntentReceiver != null) {
+ getContext().unregisterReceiver(mPackageIntentReceiver);
+ }
+ if (Flags.allNotifsNeedTtl()) {
+ if (mTtlHelper != null) {
+ mTtlHelper.destroy();
+ }
+ } else {
+ if (mNotificationTimeoutReceiver != null) {
+ getContext().unregisterReceiver(mNotificationTimeoutReceiver);
+ }
+ }
+ if (mRestoreReceiver != null) {
+ getContext().unregisterReceiver(mRestoreReceiver);
+ }
+ if (mLocaleChangeReceiver != null) {
+ getContext().unregisterReceiver(mLocaleChangeReceiver);
+ }
+ if (mSettingsObserver != null) {
+ mSettingsObserver.destroy();
+ }
+ if (mRoleObserver != null) {
+ mRoleObserver.destroy();
+ }
if (mShortcutHelper != null) {
mShortcutHelper.destroy();
}
- mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_PREFERENCES);
- mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES);
- mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES);
- mStatsManager.clearPullAtomCallback(DND_MODE_RULE);
- mAppOps.stopWatchingMode(mAppOpsListener);
- mAlarmManager.cancelAll();
+ if (mStatsManager != null) {
+ mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_PREFERENCES);
+ mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES);
+ mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES);
+ mStatsManager.clearPullAtomCallback(DND_MODE_RULE);
+ }
+ if (mAppOps != null) {
+ mAppOps.stopWatchingMode(mAppOpsListener);
+ }
+ if (mAlarmManager != null) {
+ mAlarmManager.cancelAll();
+ }
}
protected String[] getStringArrayResource(int key) {
@@ -7722,6 +7745,9 @@
return true;
}
// Check if an app has been given system exemption
+ if (ai.uid == Process.SYSTEM_UID) {
+ return false;
+ }
return mAppOps.checkOpNoThrow(
AppOpsManager.OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS, ai.uid,
ai.packageName) == MODE_ALLOWED;
@@ -8016,6 +8042,13 @@
return false;
}
+ if (Flags.rejectOldNotifications() && n.hasAppProvidedWhen() && n.getWhen() > 0
+ && (System.currentTimeMillis() - n.getWhen()) > NOTIFICATION_MAX_AGE_AT_POST) {
+ Slog.d(TAG, "Ignored enqueue for old " + n.getWhen() + " notification " + r.getKey());
+ mUsageStats.registerTooOldBlocked(r);
+ return false;
+ }
+
return true;
}
diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java
index e960f4b..c09077e 100644
--- a/services/core/java/com/android/server/notification/NotificationUsageStats.java
+++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java
@@ -257,6 +257,14 @@
}
}
+ public synchronized void registerTooOldBlocked(NotificationRecord notification) {
+ AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(notification);
+ for (AggregatedStats stats : aggregatedStatsArray) {
+ stats.numTooOld++;
+ }
+ releaseAggregatedStatsLocked(aggregatedStatsArray);
+ }
+
@GuardedBy("this")
private AggregatedStats[] getAggregatedStatsLocked(NotificationRecord record) {
return getAggregatedStatsLocked(record.getSbn().getPackageName());
@@ -405,6 +413,7 @@
public int numUndecoratedRemoteViews;
public long mLastAccessTime;
public int numImagesRemoved;
+ public int numTooOld;
public AggregatedStats(Context context, String key) {
this.key = key;
@@ -535,6 +544,7 @@
maybeCount("note_over_alert_rate", (numAlertViolations - previous.numAlertViolations));
maybeCount("note_over_quota", (numQuotaViolations - previous.numQuotaViolations));
maybeCount("note_images_removed", (numImagesRemoved - previous.numImagesRemoved));
+ maybeCount("not_too_old", (numTooOld - previous.numTooOld));
noisyImportance.maybeCount(previous.noisyImportance);
quietImportance.maybeCount(previous.quietImportance);
finalImportance.maybeCount(previous.finalImportance);
@@ -570,6 +580,7 @@
previous.numAlertViolations = numAlertViolations;
previous.numQuotaViolations = numQuotaViolations;
previous.numImagesRemoved = numImagesRemoved;
+ previous.numTooOld = numTooOld;
noisyImportance.update(previous.noisyImportance);
quietImportance.update(previous.quietImportance);
finalImportance.update(previous.finalImportance);
@@ -679,6 +690,8 @@
output.append("numQuotaViolations=").append(numQuotaViolations).append("\n");
output.append(indentPlusTwo);
output.append("numImagesRemoved=").append(numImagesRemoved).append("\n");
+ output.append(indentPlusTwo);
+ output.append("numTooOld=").append(numTooOld).append("\n");
output.append(indentPlusTwo).append(noisyImportance.toString()).append("\n");
output.append(indentPlusTwo).append(quietImportance.toString()).append("\n");
output.append(indentPlusTwo).append(finalImportance.toString()).append("\n");
@@ -725,6 +738,7 @@
maybePut(dump, "notificationEnqueueRate", getEnqueueRate());
maybePut(dump, "numAlertViolations", numAlertViolations);
maybePut(dump, "numImagesRemoved", numImagesRemoved);
+ maybePut(dump, "numTooOld", numTooOld);
noisyImportance.maybePut(dump, previous.noisyImportance);
quietImportance.maybePut(dump, previous.quietImportance);
finalImportance.maybePut(dump, previous.finalImportance);
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index 9dcca49..bf6b652 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -135,3 +135,10 @@
description: "This flag controls which signal is used to handle a user switch system event"
bug: "337077643"
}
+
+flag {
+ name: "reject_old_notifications"
+ namespace: "systemui"
+ description: "This flag does not allow notifications older than 2 weeks old to be posted"
+ bug: "339833083"
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
index 99401a1..235e3cd 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
@@ -16,6 +16,10 @@
package com.android.server.ondeviceintelligence;
+import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_LOADED_BUNDLE_KEY;
+import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_UNLOADED_BUNDLE_KEY;
+import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.REGISTER_MODEL_UPDATE_CALLBACK_BUNDLE_KEY;
+
import static com.android.server.ondeviceintelligence.BundleUtil.sanitizeInferenceParams;
import static com.android.server.ondeviceintelligence.BundleUtil.validatePfdReadOnly;
import static com.android.server.ondeviceintelligence.BundleUtil.sanitizeStateParams;
@@ -41,6 +45,7 @@
import android.app.ondeviceintelligence.OnDeviceIntelligenceException;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
@@ -105,12 +110,20 @@
/** Handler message to {@link #resetTemporaryServices()} */
private static final int MSG_RESET_TEMPORARY_SERVICE = 0;
+ /** Handler message to clean up temporary broadcast keys. */
+ private static final int MSG_RESET_BROADCAST_KEYS = 1;
+
/** Default value in absence of {@link DeviceConfig} override. */
private static final boolean DEFAULT_SERVICE_ENABLED = true;
private static final String NAMESPACE_ON_DEVICE_INTELLIGENCE = "ondeviceintelligence";
+ private static final String SYSTEM_PACKAGE = "android";
+
+
private final Executor resourceClosingExecutor = Executors.newCachedThreadPool();
private final Executor callbackExecutor = Executors.newCachedThreadPool();
+ private final Executor broadcastExecutor = Executors.newCachedThreadPool();
+
private final Context mContext;
protected final Object mLock = new Object();
@@ -123,10 +136,14 @@
@GuardedBy("mLock")
private String[] mTemporaryServiceNames;
+ @GuardedBy("mLock")
+ private String[] mTemporaryBroadcastKeys;
+ @GuardedBy("mLock")
+ private String mBroadcastPackageName;
+
/**
* Handler used to reset the temporary service names.
*/
- @GuardedBy("mLock")
private Handler mTemporaryHandler;
public OnDeviceIntelligenceManagerService(Context context) {
@@ -482,6 +499,8 @@
ensureRemoteIntelligenceServiceInitialized();
mRemoteOnDeviceIntelligenceService.run(
IOnDeviceIntelligenceService::notifyInferenceServiceConnected);
+ broadcastExecutor.execute(
+ () -> registerModelLoadingBroadcasts(service));
service.registerRemoteStorageService(
getIRemoteStorageService());
} catch (RemoteException ex) {
@@ -493,6 +512,56 @@
}
}
+ private void registerModelLoadingBroadcasts(IOnDeviceSandboxedInferenceService service) {
+ String[] modelBroadcastKeys;
+ try {
+ modelBroadcastKeys = getBroadcastKeys();
+ } catch (Resources.NotFoundException e) {
+ Slog.d(TAG, "Skipping model broadcasts as broadcast intents configured.");
+ return;
+ }
+
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(REGISTER_MODEL_UPDATE_CALLBACK_BUNDLE_KEY, true);
+ try {
+ service.updateProcessingState(bundle, new IProcessingUpdateStatusCallback.Stub() {
+ @Override
+ public void onSuccess(PersistableBundle statusParams) {
+ Binder.clearCallingIdentity();
+ synchronized (mLock) {
+ if (statusParams.containsKey(MODEL_LOADED_BUNDLE_KEY)) {
+ String modelLoadedBroadcastKey = modelBroadcastKeys[0];
+ if (modelLoadedBroadcastKey != null
+ && !modelLoadedBroadcastKey.isEmpty()) {
+ final Intent intent = new Intent(modelLoadedBroadcastKey);
+ intent.setPackage(mBroadcastPackageName);
+ mContext.sendBroadcast(intent,
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE);
+ }
+ } else if (statusParams.containsKey(MODEL_UNLOADED_BUNDLE_KEY)) {
+ String modelUnloadedBroadcastKey = modelBroadcastKeys[1];
+ if (modelUnloadedBroadcastKey != null
+ && !modelUnloadedBroadcastKey.isEmpty()) {
+ final Intent intent = new Intent(modelUnloadedBroadcastKey);
+ intent.setPackage(mBroadcastPackageName);
+ mContext.sendBroadcast(intent,
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onFailure(int errorCode, String errorMessage) {
+ Slog.e(TAG, "Failed to register model loading callback with status code",
+ new OnDeviceIntelligenceException(errorCode, errorMessage));
+ }
+ });
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to register model loading callback with status code", e);
+ }
+ }
+
@NonNull
private IRemoteStorageService.Stub getIRemoteStorageService() {
return new IRemoteStorageService.Stub() {
@@ -629,6 +698,20 @@
R.string.config_defaultOnDeviceSandboxedInferenceService)};
}
+ protected String[] getBroadcastKeys() throws Resources.NotFoundException {
+ // TODO 329240495 : Consider a small class with explicit field names for the two services
+ synchronized (mLock) {
+ if (mTemporaryBroadcastKeys != null && mTemporaryBroadcastKeys.length == 2) {
+ return mTemporaryBroadcastKeys;
+ }
+ }
+
+ return new String[]{mContext.getResources().getString(
+ R.string.config_onDeviceIntelligenceModelLoadedBroadcastKey),
+ mContext.getResources().getString(
+ R.string.config_onDeviceIntelligenceModelUnloadedBroadcastKey)};
+ }
+
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
public void setTemporaryServices(@NonNull String[] componentNames, int durationMs) {
Objects.requireNonNull(componentNames);
@@ -645,25 +728,26 @@
mRemoteOnDeviceIntelligenceService.unbind();
mRemoteOnDeviceIntelligenceService = null;
}
- if (mTemporaryHandler == null) {
- mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) {
- @Override
- public void handleMessage(Message msg) {
- if (msg.what == MSG_RESET_TEMPORARY_SERVICE) {
- synchronized (mLock) {
- resetTemporaryServices();
- }
- } else {
- Slog.wtf(TAG, "invalid handler msg: " + msg);
- }
- }
- };
- } else {
- mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
- }
if (durationMs != -1) {
- mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE, durationMs);
+ getTemporaryHandler().sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE,
+ durationMs);
+ }
+ }
+ }
+
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public void setModelBroadcastKeys(@NonNull String[] broadcastKeys, String receiverPackageName,
+ int durationMs) {
+ Objects.requireNonNull(broadcastKeys);
+ enforceShellOnly(Binder.getCallingUid(), "setModelBroadcastKeys");
+ mContext.enforceCallingPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ synchronized (mLock) {
+ mTemporaryBroadcastKeys = broadcastKeys;
+ mBroadcastPackageName = receiverPackageName;
+ if (durationMs != -1) {
+ getTemporaryHandler().sendEmptyMessageDelayed(MSG_RESET_BROADCAST_KEYS, durationMs);
}
}
}
@@ -751,4 +835,28 @@
}
}
}
+
+ private synchronized Handler getTemporaryHandler() {
+ if (mTemporaryHandler == null) {
+ mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_RESET_TEMPORARY_SERVICE) {
+ synchronized (mLock) {
+ resetTemporaryServices();
+ }
+ } else if (msg.what == MSG_RESET_BROADCAST_KEYS) {
+ synchronized (mLock) {
+ mTemporaryBroadcastKeys = null;
+ mBroadcastPackageName = SYSTEM_PACKAGE;
+ }
+ } else {
+ Slog.wtf(TAG, "invalid handler msg: " + msg);
+ }
+ }
+ };
+ }
+
+ return mTemporaryHandler;
+ }
}
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
index a76d8a3..5744b5c 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
@@ -43,6 +43,8 @@
return setTemporaryServices();
case "get-services":
return getConfiguredServices();
+ case "set-model-broadcasts":
+ return setBroadcastKeys();
default:
return handleDefaultCommands(cmd);
}
@@ -62,12 +64,18 @@
pw.println(" To reset, call without any arguments.");
pw.println(" get-services To get the names of services that are currently being used.");
+ pw.println(
+ " set-model-broadcasts [ModelLoadedBroadcastKey] [ModelUnloadedBroadcastKey] "
+ + "[ReceiverPackageName] "
+ + "[DURATION] To set the names of broadcast intent keys that are to be "
+ + "emitted for cts tests.");
}
private int setTemporaryServices() {
final PrintWriter out = getOutPrintWriter();
final String intelligenceServiceName = getNextArg();
final String inferenceServiceName = getNextArg();
+
if (getRemainingArgsCount() == 0 && intelligenceServiceName == null
&& inferenceServiceName == null) {
mService.resetTemporaryServices();
@@ -79,7 +87,8 @@
Objects.requireNonNull(inferenceServiceName);
final int duration = Integer.parseInt(getNextArgRequired());
mService.setTemporaryServices(
- new String[]{intelligenceServiceName, inferenceServiceName}, duration);
+ new String[]{intelligenceServiceName, inferenceServiceName},
+ duration);
out.println("OnDeviceIntelligenceService temporarily set to " + intelligenceServiceName
+ " \n and \n OnDeviceTrustedInferenceService set to " + inferenceServiceName
+ " for " + duration + "ms");
@@ -93,4 +102,22 @@
+ " \n and \n OnDeviceTrustedInferenceService set to : " + services[1]);
return 0;
}
+
+ private int setBroadcastKeys() {
+ final PrintWriter out = getOutPrintWriter();
+ final String modelLoadedKey = getNextArgRequired();
+ final String modelUnloadedKey = getNextArgRequired();
+ final String receiverPackageName = getNextArg();
+
+ final int duration = Integer.parseInt(getNextArgRequired());
+ mService.setModelBroadcastKeys(
+ new String[]{modelLoadedKey, modelUnloadedKey}, receiverPackageName, duration);
+ out.println("OnDeviceIntelligence Model Loading broadcast keys temporarily set to "
+ + modelLoadedKey
+ + " \n and \n OnDeviceTrustedInferenceService set to " + modelUnloadedKey
+ + "\n and Package name set to : " + receiverPackageName
+ + " for " + duration + "ms");
+ return 0;
+ }
+
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index 9ba88aa..fe774aa 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -504,9 +504,12 @@
} else {
storageFlags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
}
- List<String> deferPackages = reconcileAppsDataLI(StorageManager.UUID_PRIVATE_INTERNAL,
- UserHandle.USER_SYSTEM, storageFlags, true /* migrateAppData */,
- true /* onlyCoreApps */);
+ final List<String> deferPackages;
+ synchronized (mPm.mInstallLock) {
+ deferPackages = reconcileAppsDataLI(StorageManager.UUID_PRIVATE_INTERNAL,
+ UserHandle.USER_SYSTEM, storageFlags, true /* migrateAppData */,
+ true /* onlyCoreApps */);
+ }
Future<?> prepareAppDataFuture = SystemServerInitThreadPool.submit(() -> {
TimingsTraceLog traceLog = new TimingsTraceLog("SystemServerTimingAsync",
Trace.TRACE_TAG_PACKAGE_MANAGER);
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 19a0ba7..908b47d 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -985,13 +985,13 @@
}
void installPackagesTraced(List<InstallRequest> requests) {
- synchronized (mPm.mInstallLock) {
- try {
- Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackages");
- installPackagesLI(requests);
- } finally {
- Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
- }
+ mPm.mInstallLock.lock();
+ try {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackages");
+ installPackagesLI(requests);
+ } finally {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ mPm.mInstallLock.unlock();
}
}
@@ -2590,22 +2590,30 @@
final boolean performDexopt = DexOptHelper.shouldPerformDexopt(installRequest,
dexoptOptions, mContext);
if (performDexopt) {
- Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
+ // dexopt can take long, and ArtService doesn't require installd, so we release
+ // the lock here and re-acquire the lock after dexopt is finished.
+ mPm.mInstallLock.unlock();
+ try {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
- // This mirrors logic from commitReconciledScanResultLocked, where the library files
- // needed for dexopt are assigned.
- PackageSetting realPkgSetting = installRequest.getRealPackageSetting();
+ // This mirrors logic from commitReconciledScanResultLocked, where the library
+ // files needed for dexopt are assigned.
+ PackageSetting realPkgSetting = installRequest.getRealPackageSetting();
- // Unfortunately, the updated system app flag is only tracked on this PackageSetting
- boolean isUpdatedSystemApp =
- installRequest.getScannedPackageSetting().isUpdatedSystemApp();
+ // Unfortunately, the updated system app flag is only tracked on this
+ // PackageSetting
+ boolean isUpdatedSystemApp =
+ installRequest.getScannedPackageSetting().isUpdatedSystemApp();
- realPkgSetting.getPkgState().setUpdatedSystemApp(isUpdatedSystemApp);
+ realPkgSetting.getPkgState().setUpdatedSystemApp(isUpdatedSystemApp);
- DexoptResult dexOptResult =
- DexOptHelper.dexoptPackageUsingArtService(installRequest, dexoptOptions);
- installRequest.onDexoptFinished(dexOptResult);
- Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ DexoptResult dexOptResult = DexOptHelper.dexoptPackageUsingArtService(
+ installRequest, dexoptOptions);
+ installRequest.onDexoptFinished(dexOptResult);
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ } finally {
+ mPm.mInstallLock.lock();
+ }
}
}
PackageManagerServiceUtils.waitForNativeBinariesExtractionForIncremental(
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ae485ed..121cf3f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -626,7 +626,7 @@
// Lock for state used when installing and doing other long running
// operations. Methods that must be called with this lock held have
// the suffix "LI".
- final Object mInstallLock;
+ final PackageManagerTracedLock mInstallLock;
// ----------------------------------------------------------------
@@ -1692,8 +1692,8 @@
final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing",
Trace.TRACE_TAG_PACKAGE_MANAGER);
t.traceBegin("create package manager");
- final PackageManagerTracedLock lock = new PackageManagerTracedLock();
- final Object installLock = new Object();
+ final PackageManagerTracedLock lock = new PackageManagerTracedLock("mLock");
+ final PackageManagerTracedLock installLock = new PackageManagerTracedLock("mInstallLock");
HandlerThread backgroundThread = new ServiceThread("PackageManagerBg",
Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
index 83f3b16..ae2eaeb 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
@@ -86,7 +86,7 @@
private final Context mContext;
private final PackageManagerTracedLock mLock;
private final Installer mInstaller;
- private final Object mInstallLock;
+ private final PackageManagerTracedLock mInstallLock;
private final Handler mBackgroundHandler;
private final Executor mBackgroundExecutor;
private final List<ScanPartition> mSystemPartitions;
@@ -144,7 +144,7 @@
private final Singleton<PackageMonitorCallbackHelper> mPackageMonitorCallbackHelper;
PackageManagerServiceInjector(Context context, PackageManagerTracedLock lock,
- Installer installer, Object installLock, PackageAbiHelper abiHelper,
+ Installer installer, PackageManagerTracedLock installLock, PackageAbiHelper abiHelper,
Handler backgroundHandler,
List<ScanPartition> systemPartitions,
Producer<ComponentResolver> componentResolverProducer,
@@ -254,7 +254,7 @@
return mAbiHelper;
}
- public Object getInstallLock() {
+ public PackageManagerTracedLock getInstallLock() {
return mInstallLock;
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerTracedLock.java b/services/core/java/com/android/server/pm/PackageManagerTracedLock.java
index 75e1803f..303b8b9 100644
--- a/services/core/java/com/android/server/pm/PackageManagerTracedLock.java
+++ b/services/core/java/com/android/server/pm/PackageManagerTracedLock.java
@@ -16,6 +16,9 @@
package com.android.server.pm;
+import android.annotation.Nullable;
+import android.util.Slog;
+
import java.util.concurrent.locks.ReentrantLock;
/**
@@ -23,4 +26,31 @@
* injection, similar to {@link ActivityManagerGlobalLock}.
*/
public class PackageManagerTracedLock extends ReentrantLock {
+ private static final String TAG = "PackageManagerTracedLock";
+ private static final boolean DEBUG = false;
+ @Nullable private final String mLockName;
+
+ public PackageManagerTracedLock(@Nullable String lockName) {
+ mLockName = lockName;
+ }
+
+ public PackageManagerTracedLock() {
+ this(null);
+ }
+
+ @Override
+ public void lock() {
+ super.lock();
+ if (DEBUG && mLockName != null) {
+ Slog.i(TAG, "locked " + mLockName);
+ }
+ }
+
+ @Override
+ public void unlock() {
+ super.unlock();
+ if (DEBUG && mLockName != null) {
+ Slog.i(TAG, "unlocked " + mLockName);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java
index 1d41401..ef32485 100644
--- a/services/core/java/com/android/server/pm/UserDataPreparer.java
+++ b/services/core/java/com/android/server/pm/UserDataPreparer.java
@@ -52,11 +52,11 @@
private static final String TAG = "UserDataPreparer";
private static final String XATTR_SERIAL = "user.serial";
- private final Object mInstallLock;
+ private final PackageManagerTracedLock mInstallLock;
private final Context mContext;
private final Installer mInstaller;
- UserDataPreparer(Installer installer, Object installLock, Context context) {
+ UserDataPreparer(Installer installer, PackageManagerTracedLock installLock, Context context) {
mInstallLock = installLock;
mContext = context;
mInstaller = installer;
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index cdd456c..4264e91 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -950,7 +950,7 @@
if (mBar != null) {
try {
- mBar.togglePanel();
+ mBar.toggleNotificationsPanel();
} catch (RemoteException ex) {
}
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 9e16b8a..57827c5 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -989,6 +989,10 @@
}
}
+ boolean isLetterboxEducationEnabled() {
+ return mLetterboxConfiguration.getIsEducationEnabled();
+ }
+
/**
* Whether we use split screen aspect ratio for the activity when camera compat treatment
* is active because the corresponding config is enabled and activity supports resizing.
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 6dec712..72f592b 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -64,7 +64,6 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemClock;
-import android.os.SystemProperties;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArraySet;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 8bd7b5f..8defec3 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3448,6 +3448,8 @@
// Whether the direct top activity is eligible for letterbox education.
appCompatTaskInfo.topActivityEligibleForLetterboxEducation = isTopActivityResumed
&& top.isEligibleForLetterboxEducation();
+ appCompatTaskInfo.isLetterboxEducationEnabled = top != null
+ && top.mLetterboxUiController.isLetterboxEducationEnabled();
// Whether the direct top activity requested showing camera compat control.
appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState = isTopActivityResumed
? top.getCameraCompatControlState()
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f75803f..2b93d21 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -339,6 +339,7 @@
import android.app.admin.ManagedSubscriptionsPolicy;
import android.app.admin.NetworkEvent;
import android.app.admin.PackagePolicy;
+import android.app.admin.PackageSetPolicyValue;
import android.app.admin.ParcelableGranteeMap;
import android.app.admin.ParcelableResource;
import android.app.admin.PasswordMetrics;
@@ -349,7 +350,6 @@
import android.app.admin.PreferentialNetworkServiceConfig;
import android.app.admin.SecurityLog;
import android.app.admin.SecurityLog.SecurityEvent;
-import android.app.admin.PackageSetPolicyValue;
import android.app.admin.StartInstallingUpdateCallback;
import android.app.admin.SystemUpdateInfo;
import android.app.admin.SystemUpdatePolicy;
@@ -2718,6 +2718,7 @@
mDevicePolicyEngine.getResolvedPolicy(
PolicyDefinition.SECURITY_LOGGING, UserHandle.USER_ALL));
setLoggingConfiguration(securityLoggingEnabled, auditLoggingEnabled);
+ mInjector.runCryptoSelfTest();
} else {
synchronized (getLockObject()) {
mSecurityLogMonitor.start(getSecurityLoggingEnabledUser());
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index 9e4f821..d307200 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -1276,7 +1276,23 @@
packageName,
permissionName
)
- else -> permissionAllowlist.getSignatureAppAllowlistState(packageName, permissionName)
+ else ->
+ permissionAllowlist.getProductSignatureAppAllowlistState(
+ packageName,
+ permissionName
+ )
+ ?: permissionAllowlist.getVendorSignatureAppAllowlistState(
+ packageName,
+ permissionName
+ )
+ ?: permissionAllowlist.getSystemExtSignatureAppAllowlistState(
+ packageName,
+ permissionName
+ )
+ ?: permissionAllowlist.getSignatureAppAllowlistState(
+ packageName,
+ permissionName
+ )
}
}
diff --git a/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java b/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java
index 6a82f16..3e87c6f 100644
--- a/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java
@@ -28,6 +28,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -543,6 +544,18 @@
return future.get();
}
+ @Test
+ public void onBindingDied_referenceLost_doesNotThrow() {
+ TransportConnection.TransportConnectionMonitor transportConnectionMonitor =
+ new TransportConnection.TransportConnectionMonitor(
+ mContext, /* transportConnection= */ null);
+ doThrow(new IllegalArgumentException("Service not registered")).when(
+ mContext).unbindService(any());
+
+ // Test no exception is thrown
+ transportConnectionMonitor.onBindingDied(mTransportComponent);
+ }
+
private ServiceConnection verifyBindServiceAsUserAndCaptureServiceConnection(Context context) {
ArgumentCaptor<ServiceConnection> connectionCaptor =
ArgumentCaptor.forClass(ServiceConnection.class);
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
index 9e11fa2..e545a49 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
@@ -71,7 +71,7 @@
@Mock
private Installer mInstaller;
- private Object mInstallLock;
+ private PackageManagerTracedLock mInstallLock;
@Before
public void setup() {
@@ -79,7 +79,7 @@
TEST_USER.serialNumber = TEST_USER_SERIAL;
Context ctx = InstrumentationRegistry.getContext();
FileUtils.deleteContents(ctx.getCacheDir());
- mInstallLock = new Object();
+ mInstallLock = new PackageManagerTracedLock();
MockitoAnnotations.initMocks(this);
mUserDataPreparer = new TestUserDataPreparer(mInstaller, mInstallLock, mContextMock,
ctx.getCacheDir());
@@ -238,8 +238,8 @@
private static class TestUserDataPreparer extends UserDataPreparer {
File testDir;
- TestUserDataPreparer(Installer installer, Object installLock, Context context,
- File testDir) {
+ TestUserDataPreparer(Installer installer, PackageManagerTracedLock installLock,
+ Context context, File testDir) {
super(installer, installLock, context);
this.testDir = testDir;
}
diff --git a/services/tests/apexsystemservices/OWNERS b/services/tests/apexsystemservices/OWNERS
index 0295b9e..8b6675a 100644
--- a/services/tests/apexsystemservices/OWNERS
+++ b/services/tests/apexsystemservices/OWNERS
@@ -1,4 +1 @@
-omakoto@google.com
-satayev@google.com
-
include platform/packages/modules/common:/OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index c9aab53..396edae 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -186,7 +186,7 @@
class Mocks {
val lock = PackageManagerTracedLock()
- val installLock = Any()
+ val installLock = PackageManagerTracedLock()
val injector: PackageManagerServiceInjector = mock()
val systemWrapper: PackageManagerServiceInjector.SystemWrapper = mock()
val context: Context = mock()
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 2d672b8..200952c 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -112,6 +112,7 @@
import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER;
import static com.android.server.notification.Flags.FLAG_ALL_NOTIFS_NEED_TTL;
+import static com.android.server.notification.Flags.FLAG_REJECT_OLD_NOTIFICATIONS;
import static com.android.server.notification.NotificationManagerService.BITMAP_DURATION;
import static com.android.server.notification.NotificationManagerService.DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE;
import static com.android.server.notification.NotificationManagerService.NOTIFICATION_TTL;
@@ -339,6 +340,7 @@
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -909,7 +911,9 @@
}
mService.clearNotifications();
- TestableLooper.get(this).processAllMessages();
+ if (mTestableLooper != null) {
+ mTestableLooper.processAllMessages();
+ }
try {
mService.onDestroy();
@@ -920,14 +924,16 @@
InstrumentationRegistry.getInstrumentation()
.getUiAutomation().dropShellPermissionIdentity();
- // Remove scheduled messages that would be processed when the test is already done, and
- // could cause issues, for example, messages that remove/cancel shown toasts (this causes
- // problematic interactions with mocks when they're no longer working as expected).
- mWorkerHandler.removeCallbacksAndMessages(null);
+ if (mWorkerHandler != null) {
+ // Remove scheduled messages that would be processed when the test is already done, and
+ // could cause issues, for example, messages that remove/cancel shown toasts (this causes
+ // problematic interactions with mocks when they're no longer working as expected).
+ mWorkerHandler.removeCallbacksAndMessages(null);
+ }
- if (TestableLooper.get(this) != null) {
+ if (mTestableLooper != null) {
// Must remove static reference to this test object to prevent leak (b/261039202)
- TestableLooper.remove(this);
+ mTestableLooper.remove(this);
}
}
@@ -1009,7 +1015,9 @@
}
public void waitForIdle() {
- mTestableLooper.processAllMessages();
+ if (mTestableLooper != null) {
+ mTestableLooper.processAllMessages();
+ }
}
private void setUpPrefsForBubbles(String pkg, int uid, boolean globalEnabled,
@@ -1302,6 +1310,106 @@
return nrSummary;
}
+ private NotificationRecord createAndPostCallStyleNotification(String packageName,
+ UserHandle userHandle, String testName) throws Exception {
+ Person person = new Person.Builder().setName("caller").build();
+ Notification.Builder nb = new Notification.Builder(mContext,
+ mTestNotificationChannel.getId())
+ .setFlag(FLAG_USER_INITIATED_JOB, true)
+ .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent))
+ .setSmallIcon(android.R.drawable.sym_def_app_icon);
+ StatusBarNotification sbn = new StatusBarNotification(packageName, packageName, 1,
+ testName, mUid, 0, nb.build(), userHandle, null, 0);
+ NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ mService.addEnqueuedNotification(r);
+ mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null)).run();
+ waitForIdle();
+
+ return mService.findNotificationLocked(
+ packageName, r.getSbn().getTag(), r.getSbn().getId(), r.getSbn().getUserId());
+ }
+
+ private NotificationRecord createAndPostNotification(Notification.Builder nb, String testName)
+ throws RemoteException {
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1, testName, mUid, 0,
+ nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ mBinderService.enqueueNotificationWithTag(mPkg, mPkg, sbn.getTag(),
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+
+ return mService.findNotificationLocked(
+ mPkg, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId());
+ }
+
+ private static <T extends Parcelable> T parcelAndUnparcel(T source,
+ Parcelable.Creator<T> creator) {
+ Parcel parcel = Parcel.obtain();
+ source.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ return creator.createFromParcel(parcel);
+ }
+
+ private PendingIntent createPendingIntent(String action) {
+ return PendingIntent.getActivity(mContext, 0,
+ new Intent(action).setPackage(mContext.getPackageName()),
+ PendingIntent.FLAG_MUTABLE);
+ }
+
+ private void allowTestPackageToToast() throws Exception {
+ assertWithMessage("toast queue").that(mService.mToastQueue).isEmpty();
+ mService.isSystemUid = false;
+ mService.isSystemAppId = false;
+ setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(TEST_PACKAGE, false);
+ // package is not suspended
+ when(mPackageManager.isPackageSuspendedForUser(TEST_PACKAGE, mUserId))
+ .thenReturn(false);
+ }
+
+ private boolean enqueueToast(String testPackage, ITransientNotification callback)
+ throws RemoteException {
+ return enqueueToast((INotificationManager) mService.mService, testPackage, new Binder(),
+ callback);
+ }
+
+ private boolean enqueueToast(INotificationManager service, String testPackage,
+ IBinder token, ITransientNotification callback) throws RemoteException {
+ return service.enqueueToast(testPackage, token, callback, TOAST_DURATION, /* isUiContext= */
+ true, DEFAULT_DISPLAY);
+ }
+
+ private boolean enqueueTextToast(String testPackage, CharSequence text) throws RemoteException {
+ return enqueueTextToast(testPackage, text, /* isUiContext= */ true, DEFAULT_DISPLAY);
+ }
+
+ private boolean enqueueTextToast(String testPackage, CharSequence text, boolean isUiContext,
+ int displayId) throws RemoteException {
+ return ((INotificationManager) mService.mService).enqueueTextToast(testPackage,
+ new Binder(), text, TOAST_DURATION, isUiContext, displayId,
+ /* textCallback= */ null);
+ }
+
+ private void mockIsVisibleBackgroundUsersSupported(boolean supported) {
+ when(mUm.isVisibleBackgroundUsersSupported()).thenReturn(supported);
+ }
+
+ private void mockIsUserVisible(int displayId, boolean visible) {
+ when(mUmInternal.isUserVisible(mUserId, displayId)).thenReturn(visible);
+ }
+
+ private void mockDisplayAssignedToUser(int displayId) {
+ when(mUmInternal.getMainDisplayAssignedToUser(mUserId)).thenReturn(displayId);
+ }
+
+ private void verifyToastShownForTestPackage(String text, int displayId) {
+ verify(mStatusBar).showToast(eq(mUid), eq(TEST_PACKAGE), any(), eq(text), any(),
+ eq(TOAST_DURATION), any(), eq(displayId));
+ }
+
@Test
@DisableFlags(FLAG_ALL_NOTIFS_NEED_TTL)
public void testLimitTimeOutBroadcast() {
@@ -14069,11 +14177,12 @@
public void enqueueUpdate_whenBelowMaxEnqueueRate_accepts() throws Exception {
// Post the first version.
Notification original = generateNotificationRecord(null).getNotification();
- original.when = 111;
+ original.when = System.currentTimeMillis();
mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 0, original, mUserId);
waitForIdle();
assertThat(mService.mNotificationList).hasSize(1);
- assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111);
+ assertThat(mService.mNotificationList.get(0).getNotification().when)
+ .isEqualTo(original.when);
reset(mUsageStats);
when(mUsageStats.getAppEnqueueRate(eq(mPkg)))
@@ -14081,7 +14190,7 @@
// Post the update.
Notification update = generateNotificationRecord(null).getNotification();
- update.when = 222;
+ update.when = System.currentTimeMillis() + 111;
mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 0, update, mUserId);
waitForIdle();
@@ -14090,18 +14199,19 @@
verify(mUsageStats, never()).registerPostedByApp(any());
verify(mUsageStats).registerUpdatedByApp(any(), any());
assertThat(mService.mNotificationList).hasSize(1);
- assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(222);
+ assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(update.when);
}
@Test
public void enqueueUpdate_whenAboveMaxEnqueueRate_rejects() throws Exception {
// Post the first version.
Notification original = generateNotificationRecord(null).getNotification();
- original.when = 111;
+ original.when = System.currentTimeMillis();
mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 0, original, mUserId);
waitForIdle();
assertThat(mService.mNotificationList).hasSize(1);
- assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111);
+ assertThat(mService.mNotificationList.get(0).getNotification().when)
+ .isEqualTo(original.when);
reset(mUsageStats);
when(mUsageStats.getAppEnqueueRate(eq(mPkg)))
@@ -14109,7 +14219,7 @@
// Post the update.
Notification update = generateNotificationRecord(null).getNotification();
- update.when = 222;
+ update.when = System.currentTimeMillis() + 111;
mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 0, update, mUserId);
waitForIdle();
@@ -14118,7 +14228,8 @@
verify(mUsageStats, never()).registerPostedByApp(any());
verify(mUsageStats, never()).registerUpdatedByApp(any(), any());
assertThat(mService.mNotificationList).hasSize(1);
- assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111); // old
+ assertThat(mService.mNotificationList.get(0).getNotification().when)
+ .isEqualTo(original.when); // old
}
@Test
@@ -15483,103 +15594,48 @@
assertThat(n.getTimeoutAfter()).isEqualTo(20);
}
- private NotificationRecord createAndPostCallStyleNotification(String packageName,
- UserHandle userHandle, String testName) throws Exception {
- Person person = new Person.Builder().setName("caller").build();
- Notification.Builder nb = new Notification.Builder(mContext,
- mTestNotificationChannel.getId())
- .setFlag(FLAG_USER_INITIATED_JOB, true)
- .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent))
- .setSmallIcon(android.R.drawable.sym_def_app_icon);
- StatusBarNotification sbn = new StatusBarNotification(packageName, packageName, 1,
- testName, mUid, 0, nb.build(), userHandle, null, 0);
+ @Test
+ @EnableFlags(FLAG_REJECT_OLD_NOTIFICATIONS)
+ public void testRejectOldNotification_oldWhen() throws Exception {
+ Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setWhen(System.currentTimeMillis() - Duration.ofDays(15).toMillis())
+ .build();
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, null, mUid, 0,
+ n, UserHandle.getUserHandleForUid(mUid), null, 0);
NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
- mService.addEnqueuedNotification(r);
- mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker(null)).run();
- waitForIdle();
-
- return mService.findNotificationLocked(
- packageName, r.getSbn().getTag(), r.getSbn().getId(), r.getSbn().getUserId());
+ assertThat(mService.checkDisqualifyingFeatures(mUserId, mUid, 0, null, r, false, false))
+ .isFalse();
}
- private NotificationRecord createAndPostNotification(Notification.Builder nb, String testName)
- throws RemoteException {
- StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1, testName, mUid, 0,
- nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
- NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+ @Test
+ @EnableFlags(FLAG_REJECT_OLD_NOTIFICATIONS)
+ public void testRejectOldNotification_mediumOldWhen() throws Exception {
+ Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setWhen(System.currentTimeMillis() - Duration.ofDays(13).toMillis())
+ .build();
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, null, mUid, 0,
+ n, UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
- mBinderService.enqueueNotificationWithTag(mPkg, mPkg, sbn.getTag(),
- nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
- waitForIdle();
-
- return mService.findNotificationLocked(
- mPkg, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId());
+ assertThat(mService.checkDisqualifyingFeatures(mUserId, mUid, 0, null, r, false, false))
+ .isTrue();
}
- private static <T extends Parcelable> T parcelAndUnparcel(T source,
- Parcelable.Creator<T> creator) {
- Parcel parcel = Parcel.obtain();
- source.writeToParcel(parcel, 0);
- parcel.setDataPosition(0);
- return creator.createFromParcel(parcel);
- }
+ @Test
+ @EnableFlags(FLAG_REJECT_OLD_NOTIFICATIONS)
+ public void testRejectOldNotification_zeroWhen() throws Exception {
+ Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setWhen(0)
+ .build();
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, null, mUid, 0,
+ n, UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
- private PendingIntent createPendingIntent(String action) {
- return PendingIntent.getActivity(mContext, 0,
- new Intent(action).setPackage(mContext.getPackageName()),
- PendingIntent.FLAG_MUTABLE);
- }
-
- private void allowTestPackageToToast() throws Exception {
- assertWithMessage("toast queue").that(mService.mToastQueue).isEmpty();
- mService.isSystemUid = false;
- mService.isSystemAppId = false;
- setToastRateIsWithinQuota(true);
- setIfPackageHasPermissionToAvoidToastRateLimiting(TEST_PACKAGE, false);
- // package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(TEST_PACKAGE, mUserId))
- .thenReturn(false);
- }
-
- private boolean enqueueToast(String testPackage, ITransientNotification callback)
- throws RemoteException {
- return enqueueToast((INotificationManager) mService.mService, testPackage, new Binder(),
- callback);
- }
-
- private boolean enqueueToast(INotificationManager service, String testPackage,
- IBinder token, ITransientNotification callback) throws RemoteException {
- return service.enqueueToast(testPackage, token, callback, TOAST_DURATION, /* isUiContext= */
- true, DEFAULT_DISPLAY);
- }
-
- private boolean enqueueTextToast(String testPackage, CharSequence text) throws RemoteException {
- return enqueueTextToast(testPackage, text, /* isUiContext= */ true, DEFAULT_DISPLAY);
- }
-
- private boolean enqueueTextToast(String testPackage, CharSequence text, boolean isUiContext,
- int displayId) throws RemoteException {
- return ((INotificationManager) mService.mService).enqueueTextToast(testPackage,
- new Binder(), text, TOAST_DURATION, isUiContext, displayId,
- /* textCallback= */ null);
- }
-
- private void mockIsVisibleBackgroundUsersSupported(boolean supported) {
- when(mUm.isVisibleBackgroundUsersSupported()).thenReturn(supported);
- }
-
- private void mockIsUserVisible(int displayId, boolean visible) {
- when(mUmInternal.isUserVisible(mUserId, displayId)).thenReturn(visible);
- }
-
- private void mockDisplayAssignedToUser(int displayId) {
- when(mUmInternal.getMainDisplayAssignedToUser(mUserId)).thenReturn(displayId);
- }
-
- private void verifyToastShownForTestPackage(String text, int displayId) {
- verify(mStatusBar).showToast(eq(mUid), eq(TEST_PACKAGE), any(), eq(text), any(),
- eq(TOAST_DURATION), any(), eq(displayId));
+ assertThat(mService.checkDisqualifyingFeatures(mUserId, mUid, 0, null, r, false, false))
+ .isTrue();
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index a60d243..1195c93 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -1623,6 +1623,12 @@
assertTrue(mController.allowHorizontalReachabilityForThinLetterbox());
}
+ @Test
+ public void testIsLetterboxEducationEnabled() {
+ mController.isLetterboxEducationEnabled();
+ verify(mLetterboxConfiguration).getIsEducationEnabled();
+ }
+
private void mockThatProperty(String propertyName, boolean value) throws Exception {
Property property = new Property(propertyName, /* value */ value, /* packageName */ "",
/* className */ "");
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 03ba8fa..dbe4f27 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -13114,39 +13114,41 @@
})
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
public @Nullable ServiceState getServiceState(@IncludeLocationData int includeLocationData) {
- return getServiceStateForSubscriber(getSubId(),
+ return getServiceStateForSlot(SubscriptionManager.getSlotIndex(getSubId()),
includeLocationData != INCLUDE_LOCATION_DATA_FINE,
includeLocationData == INCLUDE_LOCATION_DATA_NONE);
}
/**
- * Returns the service state information on specified subscription. Callers require
- * either READ_PRIVILEGED_PHONE_STATE or READ_PHONE_STATE to retrieve the information.
+ * Returns the service state information on specified SIM slot.
*
- * May return {@code null} when the subscription is inactive or when there was an error
+ * May return {@code null} when the {@code slotIndex} is invalid or when there was an error
* communicating with the phone process.
+ *
+ * @param slotIndex of phone whose service state is returned
* @param renounceFineLocationAccess Set this to true if the caller would not like to receive
* location related information which will be sent if the caller already possess
* {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and do not renounce the permission
* @param renounceCoarseLocationAccess Set this to true if the caller would not like to
* receive location related information which will be sent if the caller already possess
* {@link Manifest.permission#ACCESS_COARSE_LOCATION} and do not renounce the permissions.
+ * @return Service state on specified SIM slot.
*/
- private ServiceState getServiceStateForSubscriber(int subId,
- boolean renounceFineLocationAccess,
+ private ServiceState getServiceStateForSlot(int slotIndex, boolean renounceFineLocationAccess,
boolean renounceCoarseLocationAccess) {
try {
ITelephony service = getITelephony();
if (service != null) {
- return service.getServiceStateForSubscriber(subId, renounceFineLocationAccess,
- renounceCoarseLocationAccess, getOpPackageName(), getAttributionTag());
+ return service.getServiceStateForSlot(slotIndex,
+ renounceFineLocationAccess, renounceCoarseLocationAccess,
+ getOpPackageName(), getAttributionTag());
}
} catch (RemoteException e) {
- Log.e(TAG, "Error calling ITelephony#getServiceStateForSubscriber", e);
+ Log.e(TAG, "Error calling ITelephony#getServiceStateForSlot", e);
} catch (NullPointerException e) {
AnomalyReporter.reportAnomaly(
UUID.fromString("e2bed88e-def9-476e-bd71-3e572a8de6d1"),
- "getServiceStateForSubscriber " + subId + " NPE");
+ "getServiceStateForSlot " + slotIndex + " NPE");
}
return null;
}
@@ -13161,7 +13163,35 @@
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public ServiceState getServiceStateForSubscriber(int subId) {
- return getServiceStateForSubscriber(subId, false, false);
+ return getServiceStateForSlot(
+ SubscriptionManager.getSlotIndex(subId), false, false);
+ }
+
+ /**
+ * Returns the service state information on specified SIM slot.
+ *
+ * If you want continuous updates of service state info, register a {@link TelephonyCallback}
+ * that implements {@link TelephonyCallback.ServiceStateListener} through
+ * {@link #registerTelephonyCallback}.
+ *
+ * May return {@code null} when the {@code slotIndex} is invalid or when there was an error
+ * communicating with the phone process
+ *
+ * See {@link #getActiveModemCount()} to get the total number of slots
+ * that are active on the device.
+ *
+ * @param slotIndex of phone whose service state is returned
+ * @return ServiceState on specified SIM slot.
+ *
+ * @hide
+ */
+ @RequiresPermission(allOf = {
+ Manifest.permission.READ_PHONE_STATE,
+ Manifest.permission.ACCESS_COARSE_LOCATION
+ })
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
+ public @Nullable ServiceState getServiceStateForSlot(int slotIndex) {
+ return getServiceStateForSlot(slotIndex, false, false);
}
/**
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 0bb5fd5..47f53f3 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -1015,13 +1015,27 @@
* @hide
*/
public static final int DATAGRAM_TYPE_KEEP_ALIVE = 3;
+ /**
+ * Datagram type indicating that the datagram to be sent or received is of type SOS message and
+ * is the last message to emergency service provider indicating still needs help.
+ * @hide
+ */
+ public static final int DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP = 4;
+ /**
+ * Datagram type indicating that the datagram to be sent or received is of type SOS message and
+ * is the last message to emergency service provider indicating no more help is needed.
+ * @hide
+ */
+ public static final int DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED = 5;
/** @hide */
@IntDef(prefix = "DATAGRAM_TYPE_", value = {
DATAGRAM_TYPE_UNKNOWN,
DATAGRAM_TYPE_SOS_MESSAGE,
DATAGRAM_TYPE_LOCATION_SHARING,
- DATAGRAM_TYPE_KEEP_ALIVE
+ DATAGRAM_TYPE_KEEP_ALIVE,
+ DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP,
+ DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED
})
@Retention(RetentionPolicy.SOURCE)
public @interface DatagramType {}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index d4da736..65de7e4 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1399,19 +1399,18 @@
oneway void requestModemActivityInfo(in ResultReceiver result);
/**
- * Get the service state on specified subscription
- * @param subId Subscription id
+ * Get the service state on specified SIM slot.
+ * @param slotIndex of phone whose service state is returned
* @param renounceFineLocationAccess Set this to true if the caller would not like to
* receive fine location related information
* @param renounceCoarseLocationAccess Set this to true if the caller would not like to
* receive coarse location related information
* @param callingPackage The package making the call
* @param callingFeatureId The feature in the package
- * @return Service state on specified subscription.
+ * @return Service state on specified SIM slot.
*/
- ServiceState getServiceStateForSubscriber(int subId, boolean renounceFineLocationAccess,
- boolean renounceCoarseLocationAccess,
- String callingPackage, String callingFeatureId);
+ ServiceState getServiceStateForSlot(int slotIndex, boolean renounceFineLocationAccess,
+ boolean renounceCoarseLocationAccess, String callingPackage, String callingFeatureId);
/**
* Returns the URI for the per-account voicemail ringtone set in Phone settings.