Merge "Unable to drag and edit the Accessibility shortcut" into main
diff --git a/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto b/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto
index b75d545..eddf1d2 100644
--- a/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto
+++ b/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto
@@ -47,4 +47,5 @@
optional int32 ime_window_visibility = 22;
optional bool show_ime_with_hard_keyboard = 23;
optional bool accessibility_requesting_no_soft_keyboard = 24;
-}
\ No newline at end of file
+ optional bool concurrent_multi_user_mode_enabled = 25;
+}
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 3ff40e0..56ea7c2 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -113,16 +113,6 @@
}
flag {
- name: "animate_bubble_size_change"
- namespace: "multitasking"
- description: "Turns on the animation for bubble bar icons size change"
- bug: "335575529"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "enable_taskbar_on_phones"
namespace: "multitasking"
description: "Enables taskbar on phones"
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
index 419d5c0..864f7cd 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -19,6 +19,10 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="@dimen/desktop_mode_handle_menu_width"
android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:paddingBottom="@dimen/desktop_mode_handle_menu_pill_elevation"
+ android:paddingRight="@dimen/desktop_mode_handle_menu_pill_elevation"
android:orientation="vertical">
<LinearLayout
@@ -27,7 +31,7 @@
android:layout_height="@dimen/desktop_mode_handle_menu_app_info_pill_height"
android:layout_marginTop="@dimen/desktop_mode_handle_menu_margin_top"
android:layout_marginStart="1dp"
- android:elevation="1dp"
+ android:elevation="@dimen/desktop_mode_handle_menu_pill_elevation"
android:orientation="horizontal"
android:background="@drawable/desktop_mode_decor_handle_menu_background"
android:gravity="center_vertical">
@@ -73,7 +77,7 @@
android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
android:layout_marginStart="1dp"
android:orientation="horizontal"
- android:elevation="1dp"
+ android:elevation="@dimen/desktop_mode_handle_menu_pill_elevation"
android:background="@drawable/desktop_mode_decor_handle_menu_background"
android:gravity="center_vertical">
@@ -124,7 +128,7 @@
android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
android:layout_marginStart="1dp"
android:orientation="vertical"
- android:elevation="1dp"
+ android:elevation="@dimen/desktop_mode_handle_menu_pill_elevation"
android:background="@drawable/desktop_mode_decor_handle_menu_background">
<Button
@@ -143,7 +147,7 @@
android:layout_marginTop="@dimen/desktop_mode_handle_menu_pill_spacing_margin"
android:layout_marginStart="1dp"
android:orientation="vertical"
- android:elevation="1dp"
+ android:elevation="@dimen/desktop_mode_handle_menu_pill_elevation"
android:background="@drawable/desktop_mode_decor_handle_menu_background">
<Button
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index d143263..269a586 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -495,9 +495,16 @@
<!-- The radius of the Maximize menu shadow. -->
<dimen name="desktop_mode_maximize_menu_shadow_radius">8dp</dimen>
- <!-- The width of the handle menu in desktop mode. -->
+ <!-- The width of the handle menu in desktop mode. -->
<dimen name="desktop_mode_handle_menu_width">216dp</dimen>
+ <!-- The maximum height of the handle menu in desktop mode. Four pills (52dp each) plus 2dp
+ spacing between them plus 4dp top padding. -->
+ <dimen name="desktop_mode_handle_menu_height">218dp</dimen>
+
+ <!-- The elevation set on the handle menu pills. -->
+ <dimen name="desktop_mode_handle_menu_pill_elevation">1dp</dimen>
+
<!-- The height of the handle menu's "App Info" pill in desktop mode. -->
<dimen name="desktop_mode_handle_menu_app_info_pill_height">52dp</dimen>
@@ -510,8 +517,8 @@
<!-- The height of the handle menu's "Open in browser" pill in desktop mode. -->
<dimen name="desktop_mode_handle_menu_open_in_browser_pill_height">52dp</dimen>
- <!-- The height of the handle menu in desktop mode. -->
- <dimen name="desktop_mode_handle_menu_height">380dp</dimen>
+ <!-- The margin between pills of the handle menu in desktop mode. -->
+ <dimen name="desktop_mode_handle_menu_pill_spacing_margin">2dp</dimen>
<!-- The top margin of the handle menu in desktop mode. -->
<dimen name="desktop_mode_handle_menu_margin_top">4dp</dimen>
@@ -519,9 +526,6 @@
<!-- The start margin of the handle menu in desktop mode. -->
<dimen name="desktop_mode_handle_menu_margin_start">6dp</dimen>
- <!-- The margin between pills of the handle menu in desktop mode. -->
- <dimen name="desktop_mode_handle_menu_pill_spacing_margin">2dp</dimen>
-
<!-- The radius of the caption menu corners. -->
<dimen name="desktop_mode_handle_menu_corner_radius">26dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index ece0271..8467e97 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -422,6 +422,12 @@
@VisibleForTesting
public void onThresholdCrossed() {
mThresholdCrossed = true;
+ // There was no focus window when calling startBackNavigation, still pilfer pointers so
+ // the next focus window won't receive motion events.
+ if (mBackNavigationInfo == null) {
+ tryPilferPointers();
+ return;
+ }
// Dispatch onBackStarted, only to app callbacks.
// System callbacks will receive onBackStarted when the remote animation starts.
final boolean shouldDispatchToAnimator = shouldDispatchToAnimator();
@@ -542,6 +548,7 @@
if (backNavigationInfo == null) {
ProtoLog.e(WM_SHELL_BACK_PREVIEW, "Received BackNavigationInfo is null.");
cancelLatencyTracking();
+ tryPilferPointers();
return;
}
final int backType = backNavigationInfo.getType();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index 240cf3b..037fbb2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -38,12 +38,10 @@
import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
-import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.onehanded.OneHandedController;
-import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
@@ -79,7 +77,7 @@
public abstract class Pip1Module {
@WMSingleton
@Provides
- static Optional<Pip> providePip1(Context context,
+ static Optional<PipController.PipImpl> providePip1(Context context,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
ShellController shellController,
@@ -104,20 +102,16 @@
TabletopModeController pipTabletopController,
Optional<OneHandedController> oneHandedController,
@ShellMainThread ShellExecutor mainExecutor) {
- if (PipUtils.isPip2ExperimentEnabled()) {
- return Optional.empty();
- } else {
- return Optional.ofNullable(PipController.create(
- context, shellInit, shellCommandHandler, shellController,
- displayController, pipAnimationController, pipAppOpsListener,
- pipBoundsAlgorithm,
- pipKeepClearAlgorithm, pipBoundsState, pipDisplayLayoutState,
- pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
- pipTransitionState, pipTouchHandler, pipTransitionController,
- windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
- displayInsetsController, pipTabletopController, oneHandedController,
- mainExecutor));
- }
+ return Optional.ofNullable(PipController.create(
+ context, shellInit, shellCommandHandler, shellController,
+ displayController, pipAnimationController, pipAppOpsListener,
+ pipBoundsAlgorithm,
+ pipKeepClearAlgorithm, pipBoundsState, pipDisplayLayoutState,
+ pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
+ pipTransitionState, pipTouchHandler, pipTransitionController,
+ windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
+ displayInsetsController, pipTabletopController, oneHandedController,
+ mainExecutor));
}
// Handler is used by Icon.loadDrawableAsync
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 6968317..ea7e968 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -81,6 +81,16 @@
@WMSingleton
@Provides
+ static Optional<PipController.PipImpl> providePip2(Optional<PipController> pipController) {
+ if (pipController.isEmpty()) {
+ return Optional.empty();
+ } else {
+ return Optional.ofNullable(pipController.get().getPipImpl());
+ }
+ }
+
+ @WMSingleton
+ @Provides
static Optional<PipController> providePipController(Context context,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
index f2631ef..a3afe78 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
@@ -18,12 +18,16 @@
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip2.phone.PipController;
import com.android.wm.shell.pip2.phone.PipTransition;
import dagger.Module;
import dagger.Provides;
+import java.util.Optional;
+
/**
* Provides dependencies for external components / modules reference PiP and extracts away the
* selection of legacy and new PiP implementation.
@@ -44,4 +48,17 @@
return legacyPipTransition;
}
}
+
+ @WMSingleton
+ @Provides
+ static Optional<Pip> providePip(
+ Optional<com.android.wm.shell.pip.phone.PipController.PipImpl> pip1,
+ Optional<PipController.PipImpl> pip2) {
+ if (PipUtils.isPip2ExperimentEnabled()) {
+ return Optional.ofNullable(pip2.orElse(null));
+
+ } else {
+ return Optional.ofNullable(pip1.orElse(null));
+ }
+ }
}
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 dc3e2d0..6315e69 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
@@ -217,6 +217,15 @@
dragToDesktopTransitionHandler.setSplitScreenController(controller)
}
+ /** Returns the transition type for the given remote transition. */
+ private fun transitionType(remoteTransition: RemoteTransition?): Int {
+ if (remoteTransition == null) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: remoteTransition is null")
+ return TRANSIT_NONE
+ }
+ return TRANSIT_TO_FRONT
+ }
+
/** Show all tasks, that are part of the desktop, on top of launcher */
fun showDesktopApps(displayId: Int, remoteTransition: RemoteTransition? = null) {
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: showDesktopApps")
@@ -224,8 +233,7 @@
bringDesktopAppsToFront(displayId, wct)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- // TODO(b/309014605): ensure remote transition is supplied once state is introduced
- val transitionType = if (remoteTransition == null) TRANSIT_NONE else TRANSIT_TO_FRONT
+ val transitionType = transitionType(remoteTransition)
val handler =
remoteTransition?.let {
OneShotRemoteHandler(transitions.mainExecutor, remoteTransition)
@@ -764,15 +772,13 @@
newTaskIdInFront ?: "null"
)
+ // Move home to front, ensures that we go back home when all desktop windows are closed
+ moveHomeTask(wct, toTop = true)
+
// Currently, we only handle the desktop on the default display really.
- if (displayId == DEFAULT_DISPLAY) {
- if (Flags.enableDesktopWindowingWallpaperActivity()) {
- // Add translucent wallpaper activity to show the wallpaper underneath
- addWallpaperActivity(wct)
- } else {
- // Move home to front
- moveHomeTask(wct, toTop = true)
- }
+ if (displayId == DEFAULT_DISPLAY && Flags.enableDesktopWindowingWallpaperActivity()) {
+ // Add translucent wallpaper activity to show the wallpaper underneath
+ addWallpaperActivity(wct)
}
val nonMinimizedTasksOrderedFrontToBack =
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 d1d8275..0cb7e17 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
@@ -374,7 +374,7 @@
* Instantiates {@link PipController}, returns {@code null} if the feature not supported.
*/
@Nullable
- public static Pip create(Context context,
+ public static PipImpl create(Context context,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
ShellController shellController,
@@ -1177,7 +1177,7 @@
/**
* The interface for calls from outside the Shell, within the host process.
*/
- private class PipImpl implements Pip {
+ public class PipImpl implements Pip {
@Override
public void expandPip() {
mMainExecutor.execute(() -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 06adad6..c12219c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -55,6 +55,8 @@
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -62,6 +64,8 @@
import com.android.wm.shell.sysui.ShellInit;
import java.io.PrintWriter;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* Manages the picture-in-picture (PIP) UI and states for Phones.
@@ -86,6 +90,8 @@
private final ShellTaskOrganizer mShellTaskOrganizer;
private final PipTransitionState mPipTransitionState;
private final ShellExecutor mMainExecutor;
+ private final PipImpl mImpl;
+ private Consumer<Boolean> mOnIsInPipStateChangedListener;
// Wrapper for making Binder calls into PiP animation listener hosted in launcher's Recents.
private PipAnimationListener mPipRecentsAnimationListener;
@@ -140,6 +146,7 @@
mPipTransitionState = pipTransitionState;
mPipTransitionState.addPipTransitionStateChangedListener(this);
mMainExecutor = mainExecutor;
+ mImpl = new PipImpl();
if (PipUtils.isPip2ExperimentEnabled()) {
shellInit.addInitCallback(this::onInit, this);
@@ -174,6 +181,10 @@
pipTransitionState, mainExecutor);
}
+ public PipImpl getPipImpl() {
+ return mImpl;
+ }
+
private void onInit() {
mShellCommandHandler.addDumpCallback(this::dump, this);
// Ensure that we have the display info in case we get calls to update the bounds before the
@@ -310,22 +321,29 @@
@Override
public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
@PipTransitionState.TransitionState int newState, @Nullable Bundle extra) {
- if (newState == PipTransitionState.SWIPING_TO_PIP) {
- Preconditions.checkState(extra != null,
- "No extra bundle for " + mPipTransitionState);
+ switch (newState) {
+ case PipTransitionState.SWIPING_TO_PIP:
+ Preconditions.checkState(extra != null,
+ "No extra bundle for " + mPipTransitionState);
- SurfaceControl overlay = extra.getParcelable(
- SWIPE_TO_PIP_OVERLAY, SurfaceControl.class);
- Rect appBounds = extra.getParcelable(
- SWIPE_TO_PIP_APP_BOUNDS, Rect.class);
+ SurfaceControl overlay = extra.getParcelable(
+ SWIPE_TO_PIP_OVERLAY, SurfaceControl.class);
+ Rect appBounds = extra.getParcelable(
+ SWIPE_TO_PIP_APP_BOUNDS, Rect.class);
- Preconditions.checkState(appBounds != null,
- "App bounds can't be null for " + mPipTransitionState);
- mPipTransitionState.setSwipePipToHomeState(overlay, appBounds);
- } else if (newState == PipTransitionState.ENTERED_PIP) {
- if (mPipTransitionState.isInSwipePipToHomeTransition()) {
- mPipTransitionState.resetSwipePipToHomeState();
- }
+ Preconditions.checkState(appBounds != null,
+ "App bounds can't be null for " + mPipTransitionState);
+ mPipTransitionState.setSwipePipToHomeState(overlay, appBounds);
+ break;
+ case PipTransitionState.ENTERED_PIP:
+ if (mPipTransitionState.isInSwipePipToHomeTransition()) {
+ mPipTransitionState.resetSwipePipToHomeState();
+ }
+ mOnIsInPipStateChangedListener.accept(true /* inPip */);
+ break;
+ case PipTransitionState.EXITED_PIP:
+ mOnIsInPipStateChangedListener.accept(false /* inPip */);
+ break;
}
}
@@ -355,6 +373,53 @@
mPipTransitionState.dump(pw, innerPrefix);
}
+ private void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
+ mOnIsInPipStateChangedListener = callback;
+ if (mOnIsInPipStateChangedListener != null) {
+ callback.accept(mPipTransitionState.isInPip());
+ }
+ }
+
+ /**
+ * The interface for calls from outside the Shell, within the host process.
+ */
+ public class PipImpl implements Pip {
+ @Override
+ public void expandPip() {}
+
+ @Override
+ public void onSystemUiStateChanged(boolean isSysUiStateValid, long flag) {}
+
+ @Override
+ public void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
+ mMainExecutor.execute(() -> {
+ PipController.this.setOnIsInPipStateChangedListener(callback);
+ });
+ }
+
+ @Override
+ public void addPipExclusionBoundsChangeListener(Consumer<Rect> listener) {
+ mMainExecutor.execute(() -> {
+ mPipBoundsState.addPipExclusionBoundsChangeCallback(listener);
+ });
+ }
+
+ @Override
+ public void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) {
+ mMainExecutor.execute(() -> {
+ mPipBoundsState.removePipExclusionBoundsChangeCallback(listener);
+ });
+ }
+
+ @Override
+ public void showPictureInPictureMenu() {}
+
+ @Override
+ public void registerPipTransitionCallback(
+ PipTransitionController.PipTransitionCallback callback,
+ Executor executor) {}
+ }
+
/**
* The interface for calls from outside the host process.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index f33a573..7784784 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -41,6 +41,7 @@
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
import static android.view.WindowManager.TRANSIT_RELAUNCH;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL;
import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL;
import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
@@ -63,6 +64,7 @@
import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow;
import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionTypeFromInfo;
+import static com.android.wm.shell.transition.TransitionAnimationHelper.isCoveredByOpaqueFullscreenChange;
import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
import android.animation.Animator;
@@ -353,6 +355,7 @@
continue;
}
final boolean isTask = change.getTaskInfo() != null;
+ final boolean isFreeform = isTask && change.getTaskInfo().isFreeform();
final int mode = change.getMode();
boolean isSeamlessDisplayChange = false;
@@ -459,6 +462,16 @@
final int layer = zSplitLine + numChanges - i;
startTransaction.setLayer(change.getLeash(), layer);
}
+ } else if (!isCoveredByOpaqueFullscreenChange(info, change)
+ && isFreeform
+ && TransitionUtil.isOpeningMode(type)
+ && change.getMode() == TRANSIT_TO_BACK) {
+ // Reparent the minimize-change to the root task so the minimizing Task
+ // isn't shown in front of other Tasks.
+ mRootTDAOrganizer.reparentToDisplayArea(
+ change.getTaskInfo().displayId,
+ change.getLeash(),
+ startTransaction);
} else if (isOnlyTranslucent && TransitionUtil.isOpeningType(info.getType())
&& TransitionUtil.isClosingType(mode)) {
// If there is a closing translucent task in an OPENING transition, we will
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index a5f071a..75e7ddf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -36,6 +36,7 @@
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.WindowConfiguration;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -72,6 +73,9 @@
final int changeFlags = change.getFlags();
final boolean enter = TransitionUtil.isOpeningType(changeMode);
final boolean isTask = change.getTaskInfo() != null;
+ final boolean isFreeform = isTask && change.getTaskInfo().isFreeform();
+ final boolean isCoveredByOpaqueFullscreenChange =
+ isCoveredByOpaqueFullscreenChange(info, change);
final TransitionInfo.AnimationOptions options;
if (Flags.moveAnimationOptionsToChange()) {
options = change.getAnimationOptions();
@@ -107,6 +111,24 @@
animAttr = enter
? R.styleable.WindowAnimation_wallpaperCloseEnterAnimation
: R.styleable.WindowAnimation_wallpaperCloseExitAnimation;
+ } else if (!isCoveredByOpaqueFullscreenChange
+ && isFreeform
+ && TransitionUtil.isOpeningMode(type)
+ && change.getMode() == TRANSIT_TO_BACK) {
+ // Set translucent here so TransitionAnimation loads the appropriate animations for
+ // translucent activities and tasks later
+ translucent = (changeFlags & FLAG_TRANSLUCENT) != 0;
+ // The main Task is launching or being brought to front, this Task is being minimized
+ animAttr = R.styleable.WindowAnimation_activityCloseExitAnimation;
+ } else if (!isCoveredByOpaqueFullscreenChange
+ && isFreeform
+ && type == TRANSIT_TO_FRONT
+ && change.getMode() == TRANSIT_TO_FRONT) {
+ // Set translucent here so TransitionAnimation loads the appropriate animations for
+ // translucent activities and tasks later
+ translucent = (changeFlags & FLAG_TRANSLUCENT) != 0;
+ // Bring the minimized Task back to front
+ animAttr = R.styleable.WindowAnimation_activityOpenEnterAnimation;
} else if (type == TRANSIT_OPEN) {
// We will translucent open animation for translucent activities and tasks. Choose
// WindowAnimation_activityOpenEnterAnimation and set translucent here, then
@@ -417,4 +439,25 @@
return edgeExtensionLayer;
}
+
+ /**
+ * Returns whether there is an opaque fullscreen Change positioned in front of the given Change
+ * in the given TransitionInfo.
+ */
+ static boolean isCoveredByOpaqueFullscreenChange(
+ TransitionInfo info, TransitionInfo.Change change) {
+ // TransitionInfo#getChanges() are ordered from front to back
+ for (TransitionInfo.Change coveringChange : info.getChanges()) {
+ if (coveringChange == change) {
+ return false;
+ }
+ if ((coveringChange.getFlags() & FLAG_TRANSLUCENT) == 0
+ && coveringChange.getTaskInfo() != null
+ && coveringChange.getTaskInfo().getWindowingMode()
+ == WindowConfiguration.WINDOWING_MODE_FULLSCREEN) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index 39595cf..bce233f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -15,6 +15,7 @@
*/
package com.android.wm.shell.windowdecor
+import android.annotation.DimenRes
import android.app.ActivityManager
import android.app.WindowConfiguration
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
@@ -75,10 +76,17 @@
private val isViewAboveStatusBar: Boolean
get() = (Flags.enableAdditionalWindowsAboveStatusBar() && !taskInfo.isFreeform)
- private var marginMenuTop = 0
- private var marginMenuStart = 0
- private var menuHeight = 0
- private var menuWidth = 0
+ private val pillElevation: Int = loadDimensionPixelSize(
+ R.dimen.desktop_mode_handle_menu_pill_elevation)
+ private val pillTopMargin: Int = loadDimensionPixelSize(
+ R.dimen.desktop_mode_handle_menu_pill_spacing_margin)
+ private val menuWidth = loadDimensionPixelSize(
+ R.dimen.desktop_mode_handle_menu_width) + pillElevation
+ private val menuHeight = getHandleMenuHeight()
+ private val marginMenuTop = loadDimensionPixelSize(R.dimen.desktop_mode_handle_menu_margin_top)
+ private val marginMenuStart = loadDimensionPixelSize(
+ R.dimen.desktop_mode_handle_menu_margin_start)
+
private var handleMenuAnimator: HandleMenuAnimator? = null
@VisibleForTesting
@@ -120,7 +128,6 @@
}
init {
- loadHandleMenuDimensions()
updateHandleMenuPillPositions()
}
@@ -426,49 +433,35 @@
*/
private fun viewsLaidOut(): Boolean = handleMenuViewContainer?.view?.isLaidOut ?: false
- private fun loadHandleMenuDimensions() {
- val resources = context.resources
- menuWidth = loadDimensionPixelSize(resources, R.dimen.desktop_mode_handle_menu_width)
- menuHeight = getHandleMenuHeight(resources)
- marginMenuTop = loadDimensionPixelSize(
- resources,
- R.dimen.desktop_mode_handle_menu_margin_top
- )
- marginMenuStart = loadDimensionPixelSize(
- resources,
- R.dimen.desktop_mode_handle_menu_margin_start
- )
- }
-
/**
- * Determines handle menu height based on if windowing pill should be shown.
+ * Determines handle menu height based the max size and the visibility of pills.
*/
- private fun getHandleMenuHeight(resources: Resources): Int {
- var menuHeight = loadDimensionPixelSize(resources, R.dimen.desktop_mode_handle_menu_height)
+ private fun getHandleMenuHeight(): Int {
+ var menuHeight = loadDimensionPixelSize(
+ R.dimen.desktop_mode_handle_menu_height) + pillElevation
if (!shouldShowWindowingPill) {
menuHeight -= loadDimensionPixelSize(
- resources,
- R.dimen.desktop_mode_handle_menu_windowing_pill_height
- )
+ R.dimen.desktop_mode_handle_menu_windowing_pill_height)
+ menuHeight -= pillTopMargin
}
if (!SHOULD_SHOW_MORE_ACTIONS_PILL) {
menuHeight -= loadDimensionPixelSize(
- resources,
- R.dimen.desktop_mode_handle_menu_more_actions_pill_height
- )
+ R.dimen.desktop_mode_handle_menu_more_actions_pill_height)
+ menuHeight -= pillTopMargin
}
if (!shouldShowBrowserPill) {
- menuHeight -= loadDimensionPixelSize(resources,
+ menuHeight -= loadDimensionPixelSize(
R.dimen.desktop_mode_handle_menu_open_in_browser_pill_height)
+ menuHeight -= pillTopMargin
}
return menuHeight
}
- private fun loadDimensionPixelSize(resources: Resources, resourceId: Int): Int {
+ private fun loadDimensionPixelSize(@DimenRes resourceId: Int): Int {
if (resourceId == Resources.ID_NULL) {
return 0
}
- return resources.getDimensionPixelSize(resourceId)
+ return context.resources.getDimensionPixelSize(resourceId)
}
fun close() {
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ExitDesktopWithDragToTopDragZone.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ExitDesktopWithDragToTopDragZone.kt
new file mode 100644
index 0000000..0b6c9af
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ExitDesktopWithDragToTopDragZone.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.scenarios
+
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.flicker.service.common.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+
+@Ignore("Base Test Class")
+abstract class ExitDesktopWithDragToTopDragZone
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+ testApp.enterDesktopWithDrag(wmHelper, device)
+ }
+
+ @Test
+ open fun exitDesktopWithDragToTopDragZone() {
+ testApp.exitDesktopWithDragToTopDragZone(wmHelper, device)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
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 4d1b6ba..f670434 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
@@ -313,6 +313,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_shouldNotShowWallpaper() {
+ val homeTask = setUpHomeTask(SECOND_DISPLAY)
val task1 = setUpFreeformTask(SECOND_DISPLAY)
val task2 = setUpFreeformTask(SECOND_DISPLAY)
markTaskHidden(task1)
@@ -321,10 +322,11 @@
controller.showDesktopApps(SECOND_DISPLAY, RemoteTransition(TestRemoteTransition()))
val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(2)
- // Expect order to be from bottom: task1, task2 (no wallpaper intent)
- wct.assertReorderAt(index = 0, task1)
- wct.assertReorderAt(index = 1, task2)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: home, task1, task2 (no wallpaper intent)
+ wct.assertReorderAt(index = 0, homeTask)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
}
@Test
@@ -349,6 +351,7 @@
@Test
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_onSecondaryDisplay_desktopWallpaperDisabled_shouldNotMoveLauncher() {
+ val homeTask = setUpHomeTask(SECOND_DISPLAY)
val task1 = setUpFreeformTask(SECOND_DISPLAY)
val task2 = setUpFreeformTask(SECOND_DISPLAY)
markTaskHidden(task1)
@@ -357,9 +360,11 @@
controller.showDesktopApps(SECOND_DISPLAY, RemoteTransition(TestRemoteTransition()))
val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(2)
- wct.assertReorderAt(index = 0, task1)
- wct.assertReorderAt(index = 1, task2)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: home, task1, task2
+ wct.assertReorderAt(index = 0, homeTask)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
}
@Test
@@ -460,6 +465,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperEnabled() {
+ val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY)
setUpHomeTask(SECOND_DISPLAY)
val taskSecondDisplay = setUpFreeformTask(SECOND_DISPLAY)
@@ -469,10 +475,13 @@
controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(2)
- // Expect order to be from bottom: wallpaper intent, task
- wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
- wct.assertReorderAt(index = 1, taskDefaultDisplay)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Move home to front
+ wct.assertReorderAt(index = 0, homeTaskDefaultDisplay)
+ // Add desktop wallpaper activity
+ wct.assertPendingIntentAt(index = 1, desktopWallpaperIntent)
+ // Move freeform task to front
+ wct.assertReorderAt(index = 2, taskDefaultDisplay)
}
@Test
@@ -497,7 +506,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_desktopWallpaperEnabled_dontReorderMinimizedTask() {
- setUpHomeTask()
+ val homeTask = setUpHomeTask()
val freeformTask = setUpFreeformTask()
val minimizedTask = setUpFreeformTask()
@@ -507,11 +516,13 @@
controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(2)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Move home to front
+ wct.assertReorderAt(index = 0, homeTask, toTop = true)
// Add desktop wallpaper activity
- wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ wct.assertPendingIntentAt(index = 1, desktopWallpaperIntent)
// Reorder freeform task to top, don't reorder the minimized task
- wct.assertReorderAt(index = 1, freeformTask, toTop = true)
+ wct.assertReorderAt(index = 2, freeformTask, toTop = true)
}
@Test
@@ -894,16 +905,19 @@
val taskLimit = desktopTasksLimiter.getMaxTaskLimit()
val freeformTasks = (1..taskLimit).map { _ -> setUpFreeformTask() }
val newTask = setUpFullscreenTask()
- setUpHomeTask()
+ val homeTask = setUpHomeTask()
controller.moveToDesktop(newTask, transitionSource = UNKNOWN)
val wct = getLatestEnterDesktopWct()
- assertThat(wct.hierarchyOps.size).isEqualTo(taskLimit + 1) // visible tasks + wallpaper
+ assertThat(wct.hierarchyOps.size).isEqualTo(taskLimit + 2) // tasks + home + wallpaper
+ // Move home to front
+ wct.assertReorderAt(0, homeTask)
// Add desktop wallpaper activity
- wct.assertPendingIntentAt(0, desktopWallpaperIntent)
+ wct.assertPendingIntentAt(1, desktopWallpaperIntent)
+ // Bring freeform tasks to front
wct.assertReorderSequenceInRange(
- range = 1..<(taskLimit + 1),
+ range = 2..<(taskLimit + 2),
*freeformTasks.drop(1).toTypedArray(), // Skipping freeformTasks[0]
newTask
)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
index 754a173..6bc7e49 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.transition;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_SLEEP;
@@ -185,6 +186,73 @@
verify(startT, never()).setColor(any(), any());
}
+ @Test
+ public void startAnimation_freeformOpenChange_doesntReparentTask() {
+ final TransitionInfo.Change openChange = new ChangeBuilder(TRANSIT_OPEN)
+ .setTask(createTaskInfo(
+ /* taskId= */ 1, /* windowingMode= */ WINDOWING_MODE_FULLSCREEN))
+ .build();
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(openChange)
+ .build();
+ final IBinder token = new Binder();
+ final SurfaceControl.Transaction startT = MockTransactionPool.create();
+ final SurfaceControl.Transaction finishT = MockTransactionPool.create();
+
+ mTransitionHandler.startAnimation(token, info, startT, finishT,
+ mock(Transitions.TransitionFinishCallback.class));
+
+ verify(startT, never()).reparent(any(), any());
+ }
+
+ @Test
+ public void startAnimation_freeformMinimizeChange_underFullscreenChange_doesntReparentTask() {
+ final TransitionInfo.Change openChange = new ChangeBuilder(TRANSIT_OPEN)
+ .setTask(createTaskInfo(
+ /* taskId= */ 1, /* windowingMode= */ WINDOWING_MODE_FULLSCREEN))
+ .build();
+ final TransitionInfo.Change toBackChange = new ChangeBuilder(TRANSIT_TO_BACK)
+ .setTask(createTaskInfo(
+ /* taskId= */ 2, /* windowingMode= */ WINDOWING_MODE_FREEFORM))
+ .build();
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(openChange)
+ .addChange(toBackChange)
+ .build();
+ final IBinder token = new Binder();
+ final SurfaceControl.Transaction startT = MockTransactionPool.create();
+ final SurfaceControl.Transaction finishT = MockTransactionPool.create();
+
+ mTransitionHandler.startAnimation(token, info, startT, finishT,
+ mock(Transitions.TransitionFinishCallback.class));
+
+ verify(startT, never()).reparent(any(), any());
+ }
+
+ @Test
+ public void startAnimation_freeform_minimizeAnimation_reparentsTask() {
+ final TransitionInfo.Change openChange = new ChangeBuilder(TRANSIT_OPEN)
+ .setTask(createTaskInfo(
+ /* taskId= */ 1, /* windowingMode= */ WINDOWING_MODE_FREEFORM))
+ .build();
+ final TransitionInfo.Change toBackChange = new ChangeBuilder(TRANSIT_TO_BACK)
+ .setTask(createTaskInfo(
+ /* taskId= */ 2, /* windowingMode= */ WINDOWING_MODE_FREEFORM))
+ .build();
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(openChange)
+ .addChange(toBackChange)
+ .build();
+ final IBinder token = new Binder();
+ final SurfaceControl.Transaction startT = MockTransactionPool.create();
+ final SurfaceControl.Transaction finishT = MockTransactionPool.create();
+
+ mTransitionHandler.startAnimation(token, info, startT, finishT,
+ mock(Transitions.TransitionFinishCallback.class));
+
+ verify(startT).reparent(any(), any());
+ }
+
private static void mergeSync(Transitions.TransitionHandler handler, IBinder token) {
handler.mergeAnimation(
new Binder(),
@@ -195,10 +263,14 @@
}
private static RunningTaskInfo createTaskInfo(int taskId) {
+ return createTaskInfo(taskId, WINDOWING_MODE_FULLSCREEN);
+ }
+
+ private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = taskId;
taskInfo.topActivityType = ACTIVITY_TYPE_STANDARD;
- taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
taskInfo.configuration.windowConfiguration.setActivityType(taskInfo.topActivityType);
taskInfo.token = mock(WindowContainerToken.class);
return taskInfo;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionAnimationHelperTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionAnimationHelperTest.kt
new file mode 100644
index 0000000..bad14bb
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionAnimationHelperTest.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration
+import android.view.WindowManager
+import android.window.TransitionInfo
+import android.window.TransitionInfo.FLAG_TRANSLUCENT
+import com.android.internal.R
+import com.android.internal.policy.TransitionAnimation
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+import org.junit.Test
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+
+class TransitionAnimationHelperTest : ShellTestCase() {
+
+ @Mock
+ lateinit var transitionAnimation: TransitionAnimation
+
+ @Test
+ fun loadAttributeAnimation_freeform_taskOpen_taskToBackChange_returnsMinimizeAnim() {
+ val openChange = ChangeBuilder(WindowManager.TRANSIT_OPEN)
+ .setTask(createTaskInfo(WindowConfiguration.WINDOWING_MODE_FREEFORM))
+ .build()
+ val toBackChange = ChangeBuilder(WindowManager.TRANSIT_TO_BACK)
+ .setTask(createTaskInfo(WindowConfiguration.WINDOWING_MODE_FREEFORM))
+ .build()
+ val info = TransitionInfoBuilder(WindowManager.TRANSIT_OPEN)
+ .addChange(openChange)
+ .addChange(toBackChange)
+ .build()
+
+ loadAttributeAnimation(WindowManager.TRANSIT_OPEN, info, toBackChange)
+
+ verify(transitionAnimation).loadDefaultAnimationAttr(
+ eq(R.styleable.WindowAnimation_activityCloseExitAnimation), anyBoolean())
+ }
+
+ @Test
+ fun loadAttributeAnimation_freeform_taskToFront_taskToFrontChange_returnsUnminimizeAnim() {
+ val toFrontChange = ChangeBuilder(WindowManager.TRANSIT_TO_FRONT)
+ .setTask(createTaskInfo(WindowConfiguration.WINDOWING_MODE_FREEFORM))
+ .build()
+ val info = TransitionInfoBuilder(WindowManager.TRANSIT_TO_FRONT)
+ .addChange(toFrontChange)
+ .build()
+
+ loadAttributeAnimation(WindowManager.TRANSIT_TO_FRONT, info, toFrontChange)
+
+ verify(transitionAnimation).loadDefaultAnimationAttr(
+ eq(R.styleable.WindowAnimation_activityOpenEnterAnimation),
+ /* translucent= */ anyBoolean())
+ }
+
+ @Test
+ fun loadAttributeAnimation_fullscreen_taskOpen_returnsTaskOpenEnterAnim() {
+ val openChange = ChangeBuilder(WindowManager.TRANSIT_OPEN)
+ .setTask(createTaskInfo(WindowConfiguration.WINDOWING_MODE_FULLSCREEN))
+ .build()
+ val info = TransitionInfoBuilder(WindowManager.TRANSIT_OPEN).addChange(openChange).build()
+
+ loadAttributeAnimation(WindowManager.TRANSIT_OPEN, info, openChange)
+
+ verify(transitionAnimation).loadDefaultAnimationAttr(
+ eq(R.styleable.WindowAnimation_taskOpenEnterAnimation),
+ /* translucent= */ anyBoolean())
+ }
+
+ @Test
+ fun loadAttributeAnimation_freeform_taskOpen_taskToBackChange_passesTranslucent() {
+ val openChange = ChangeBuilder(WindowManager.TRANSIT_OPEN)
+ .setTask(createTaskInfo(WindowConfiguration.WINDOWING_MODE_FREEFORM))
+ .build()
+ val toBackChange = ChangeBuilder(WindowManager.TRANSIT_TO_BACK)
+ .setTask(createTaskInfo(WindowConfiguration.WINDOWING_MODE_FREEFORM))
+ .setFlags(FLAG_TRANSLUCENT)
+ .build()
+ val info = TransitionInfoBuilder(WindowManager.TRANSIT_OPEN)
+ .addChange(openChange)
+ .addChange(toBackChange)
+ .build()
+
+ loadAttributeAnimation(WindowManager.TRANSIT_OPEN, info, toBackChange)
+
+ verify(transitionAnimation).loadDefaultAnimationAttr(
+ eq(R.styleable.WindowAnimation_activityCloseExitAnimation),
+ /* translucent= */ eq(true))
+ }
+
+ private fun loadAttributeAnimation(
+ @WindowManager.TransitionType type: Int,
+ info: TransitionInfo,
+ change: TransitionInfo.Change,
+ wallpaperTransit: Int = TransitionAnimation.WALLPAPER_TRANSITION_NONE,
+ isDreamTransition: Boolean = false,
+ ) {
+ TransitionAnimationHelper.loadAttributeAnimation(
+ type, info, change, wallpaperTransit, transitionAnimation, isDreamTransition)
+ }
+
+ private fun createTaskInfo(windowingMode: Int): RunningTaskInfo {
+ val taskInfo = TestRunningTaskInfoBuilder()
+ .setWindowingMode(windowingMode)
+ .build()
+ return taskInfo
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
index e0e603ff..adda9a6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -94,6 +94,8 @@
private lateinit var handleMenu: HandleMenu
+ private val menuWidthWithElevation = MENU_WIDTH + MENU_PILL_ELEVATION
+
@Before
fun setUp() {
val mockAdditionalViewHostViewContainer = AdditionalViewHostViewContainer(
@@ -117,6 +119,9 @@
addOverride(R.dimen.desktop_mode_handle_menu_height, MENU_HEIGHT)
addOverride(R.dimen.desktop_mode_handle_menu_margin_top, MENU_TOP_MARGIN)
addOverride(R.dimen.desktop_mode_handle_menu_margin_start, MENU_START_MARGIN)
+ addOverride(R.dimen.desktop_mode_handle_menu_pill_elevation, MENU_PILL_ELEVATION)
+ addOverride(
+ R.dimen.desktop_mode_handle_menu_pill_spacing_margin, MENU_PILL_SPACING_MARGIN)
}
mockDesktopWindowDecoration.mDecorWindowContext = mContext
}
@@ -129,7 +134,7 @@
assertTrue(handleMenu.handleMenuViewContainer is AdditionalSystemViewContainer)
// Verify menu is created at coordinates that, when added to WindowManager,
// show at the top-center of display.
- val expected = Point(DISPLAY_BOUNDS.centerX() - MENU_WIDTH / 2, MENU_TOP_MARGIN)
+ val expected = Point(DISPLAY_BOUNDS.centerX() - menuWidthWithElevation / 2, MENU_TOP_MARGIN)
assertEquals(expected.toPointF(), handleMenu.handleMenuPosition)
}
@@ -152,7 +157,10 @@
assertTrue(handleMenu.handleMenuViewContainer is AdditionalSystemViewContainer)
// Verify menu is created at coordinates that, when added to WindowManager,
// show at the top-center of split left task.
- val expected = Point(SPLIT_LEFT_BOUNDS.centerX() - MENU_WIDTH / 2, MENU_TOP_MARGIN)
+ val expected = Point(
+ SPLIT_LEFT_BOUNDS.centerX() - menuWidthWithElevation / 2,
+ MENU_TOP_MARGIN
+ )
assertEquals(expected.toPointF(), handleMenu.handleMenuPosition)
}
@@ -164,7 +172,10 @@
assertTrue(handleMenu.handleMenuViewContainer is AdditionalSystemViewContainer)
// Verify menu is created at coordinates that, when added to WindowManager,
// show at the top-center of split right task.
- val expected = Point(SPLIT_RIGHT_BOUNDS.centerX() - MENU_WIDTH / 2, MENU_TOP_MARGIN)
+ val expected = Point(
+ SPLIT_RIGHT_BOUNDS.centerX() - menuWidthWithElevation / 2,
+ MENU_TOP_MARGIN
+ )
assertEquals(expected.toPointF(), handleMenu.handleMenuPosition)
}
@@ -220,5 +231,7 @@
private const val MENU_HEIGHT = 400
private const val MENU_TOP_MARGIN = 10
private const val MENU_START_MARGIN = 20
+ private const val MENU_PILL_ELEVATION = 2
+ private const val MENU_PILL_SPACING_MARGIN = 4
}
}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 8b33afe..db71d72 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -669,6 +669,7 @@
],
asset_dirs: [
"tests/goldens",
+ "schemas",
],
static_libs: [
"SystemUI-res",
@@ -708,6 +709,7 @@
"androidx-constraintlayout_constraintlayout",
"androidx.exifinterface_exifinterface",
"androidx.room_room-runtime",
+ "androidx.room_room-testing",
"androidx.room_room-ktx",
"androidx.datastore_datastore-preferences",
"device_state_flags_lib",
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt
index 166aa70..f9e2252 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt
@@ -28,7 +28,6 @@
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
-import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.height
import com.android.keyguard.dagger.KeyguardStatusBarViewComponent
@@ -83,29 +82,20 @@
componentFactory.build(view, provider).keyguardStatusBarViewController
}
- MovableElement(
- key = StatusBarElementKey,
- modifier = modifier,
- ) {
- content {
- AndroidView(
- factory = {
- notificationPanelView.get().findViewById<View>(R.id.keyguard_header)?.let {
- (it.parent as ViewGroup).removeView(it)
- }
+ AndroidView(
+ factory = {
+ notificationPanelView.get().findViewById<View>(R.id.keyguard_header)?.let {
+ (it.parent as ViewGroup).removeView(it)
+ }
- viewController.init()
- view
- },
- modifier =
- Modifier.fillMaxWidth().padding(horizontal = 16.dp).height {
- Utils.getStatusBarHeaderHeightKeyguard(context)
- },
- update = { viewController.setDisplayCutout(viewDisplayCutout) }
- )
- }
- }
+ viewController.init()
+ view
+ },
+ modifier =
+ Modifier.fillMaxWidth().padding(horizontal = 16.dp).height {
+ Utils.getStatusBarHeaderHeightKeyguard(context)
+ },
+ update = { viewController.setDisplayCutout(viewDisplayCutout) }
+ )
}
}
-
-private val StatusBarElementKey = ElementKey("StatusBar")
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalBackupRestoreStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalBackupRestoreStartableTest.kt
index 722eb2b..60aea92 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalBackupRestoreStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalBackupRestoreStartableTest.kt
@@ -20,6 +20,9 @@
import android.content.Context
import android.content.Intent
import android.content.mockedContext
+import android.os.Handler
+import android.os.fakeExecutorHandler
+import android.provider.Settings.Secure.USER_SETUP_COMPLETE
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -27,11 +30,13 @@
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.widgets.CommunalWidgetModule
+import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -41,6 +46,8 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -50,10 +57,13 @@
@Mock private lateinit var communalInteractor: CommunalInteractor
- private val mapCaptor = kotlinArgumentCaptor<Map<Int, Int>>()
+ private val mapCaptor = argumentCaptor<Map<Int, Int>>()
private lateinit var context: Context
private lateinit var broadcastDispatcher: FakeBroadcastDispatcher
+ private lateinit var secureSettings: SecureSettings
+ private lateinit var handler: Handler
+ private lateinit var fakeExecutor: FakeExecutor
private lateinit var underTest: CommunalBackupRestoreStartable
@Before
@@ -62,18 +72,28 @@
context = kosmos.mockedContext
broadcastDispatcher = kosmos.broadcastDispatcher
+ secureSettings = kosmos.fakeSettings
+ handler = kosmos.fakeExecutorHandler
+ fakeExecutor = kosmos.fakeExecutor
+
+ secureSettings.putInt(USER_SETUP_COMPLETE, 0)
underTest =
CommunalBackupRestoreStartable(
broadcastDispatcher,
communalInteractor,
logcatLogBuffer("CommunalBackupRestoreStartable"),
+ secureSettings,
+ handler,
)
}
@Test
- fun testRestoreWidgetsUponHostRestored() =
+ fun restoreWidgets_userSetUpComplete_performRestore() =
testScope.runTest {
+ // User set up complete
+ secureSettings.putInt(USER_SETUP_COMPLETE, 1)
+
underTest.start()
// Verify restore widgets not called
@@ -94,7 +114,7 @@
// Verify restore widgets called
verify(communalInteractor).restoreWidgets(mapCaptor.capture())
- val oldToNewWidgetIdMap = mapCaptor.value
+ val oldToNewWidgetIdMap = mapCaptor.firstValue
assertThat(oldToNewWidgetIdMap)
.containsExactlyEntriesIn(
mapOf(
@@ -106,10 +126,54 @@
}
@Test
- fun testDoNotRestoreWidgetsIfNotForCommunalWidgetHost() =
+ fun restoreWidgets_userSetUpNotComplete_restoreWhenUserSetupComplete() =
testScope.runTest {
underTest.start()
+ // Verify restore widgets not called
+ verify(communalInteractor, never()).restoreWidgets(any())
+
+ // Trigger app widget host restored
+ val intent =
+ Intent().apply {
+ action = AppWidgetManager.ACTION_APPWIDGET_HOST_RESTORED
+ putExtra(
+ AppWidgetManager.EXTRA_HOST_ID,
+ CommunalWidgetModule.APP_WIDGET_HOST_ID
+ )
+ putExtra(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS, intArrayOf(1, 2, 3))
+ putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, intArrayOf(7, 8, 9))
+ }
+ broadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
+
+ // Verify restore widgets not called because user setup not complete
+ verify(communalInteractor, never()).restoreWidgets(any())
+
+ // User setup complete
+ secureSettings.putInt(USER_SETUP_COMPLETE, 1)
+ fakeExecutor.runAllReady()
+
+ // Verify restore widgets called
+ verify(communalInteractor).restoreWidgets(mapCaptor.capture())
+ val oldToNewWidgetIdMap = mapCaptor.firstValue
+ assertThat(oldToNewWidgetIdMap)
+ .containsExactlyEntriesIn(
+ mapOf(
+ Pair(1, 7),
+ Pair(2, 8),
+ Pair(3, 9),
+ )
+ )
+ }
+
+ @Test
+ fun restoreWidgets_broadcastNotForCommunalWidgetHost_doNotPerformRestore() =
+ testScope.runTest {
+ // User set up complete
+ secureSettings.putInt(USER_SETUP_COMPLETE, 1)
+
+ underTest.start()
+
// Trigger app widget host restored, but for another host
val hostId = CommunalWidgetModule.APP_WIDGET_HOST_ID + 1
val intent =
@@ -126,8 +190,11 @@
}
@Test
- fun testAbortRestoreWidgetsIfOldToNewIdsMappingInvalid() =
+ fun restoreWidgets_oldToNewIdsMappingInvalid_abortRestore() =
testScope.runTest {
+ // User set up complete
+ secureSettings.putInt(USER_SETUP_COMPLETE, 1)
+
underTest.start()
// Trigger app widget host restored, but new ids list is one too many for old ids
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index cf14547..0de0369 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -27,6 +27,9 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.dock.dockManager
import com.android.systemui.dock.fakeDockManager
+import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.flags.featureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -66,6 +69,7 @@
fun setUp() {
with(kosmos) {
fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT)
+ kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
underTest =
CommunalSceneStartable(
@@ -76,6 +80,7 @@
keyguardInteractor = keyguardInteractor,
systemSettings = fakeSettings,
notificationShadeWindowController = notificationShadeWindowController,
+ featureFlagsClassic = kosmos.fakeFeatureFlagsClassic,
applicationScope = applicationCoroutineScope,
bgScope = applicationCoroutineScope,
mainDispatcher = testDispatcher,
@@ -451,6 +456,24 @@
}
}
+ @Test
+ fun transitionFromDozingToGlanceableHub_forcesCommunal() =
+ with(kosmos) {
+ testScope.runTest {
+ val scene by collectLastValue(communalSceneInteractor.currentScene)
+ communalSceneInteractor.changeScene(CommunalScenes.Blank)
+ assertThat(scene).isEqualTo(CommunalScenes.Blank)
+
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope = this
+ )
+
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ }
+ }
+
private fun TestScope.updateDocked(docked: Boolean) =
with(kosmos) {
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index 6ce6cdb..17234a90 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -24,6 +24,7 @@
import android.content.applicationContext
import android.graphics.Bitmap
import android.os.UserHandle
+import android.os.userManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -47,10 +48,6 @@
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.res.R
import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -59,11 +56,16 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.eq
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -77,6 +79,9 @@
@Mock private lateinit var communalWidgetDao: CommunalWidgetDao
@Mock private lateinit var backupManager: BackupManager
+ private val communalHubStateCaptor = argumentCaptor<CommunalHubState>()
+ private val componentNameCaptor = argumentCaptor<ComponentName>()
+
private lateinit var backupUtils: CommunalBackupUtils
private lateinit var logBuffer: LogBuffer
private lateinit var fakeWidgets: MutableStateFlow<Map<CommunalItemRank, CommunalWidgetItem>>
@@ -85,6 +90,10 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val packageChangeRepository = kosmos.fakePackageChangeRepository
+ private val userManager = kosmos.userManager
+
+ private val mainUser = UserHandle(0)
+ private val workProfile = UserHandle(10)
private val fakeAllowlist =
listOf(
@@ -109,6 +118,9 @@
whenever(communalWidgetDao.getWidgets()).thenReturn(fakeWidgets)
whenever(communalWidgetHost.appWidgetProviders).thenReturn(fakeProviders)
+ whenever(userManager.mainUser).thenReturn(mainUser)
+
+ restoreUser(mainUser)
underTest =
CommunalWidgetRepositoryImpl(
@@ -121,6 +133,7 @@
backupManager,
backupUtils,
packageChangeRepository,
+ userManager,
)
}
@@ -128,7 +141,7 @@
fun communalWidgets_queryWidgetsFromDb() =
testScope.runTest {
val communalItemRankEntry = CommunalItemRank(uid = 1L, rank = 1)
- val communalWidgetItemEntry = CommunalWidgetItem(uid = 1L, 1, "pk_name/cls_name", 1L)
+ val communalWidgetItemEntry = CommunalWidgetItem(uid = 1L, 1, "pk_name/cls_name", 1L, 0)
fakeWidgets.value = mapOf(communalItemRankEntry to communalWidgetItemEntry)
fakeProviders.value = mapOf(1 to providerInfoA)
@@ -154,13 +167,13 @@
fakeWidgets.value =
mapOf(
CommunalItemRank(uid = 1L, rank = 1) to
- CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L),
+ CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0),
CommunalItemRank(uid = 2L, rank = 2) to
- CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L),
+ CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L, 0),
CommunalItemRank(uid = 3L, rank = 3) to
- CommunalWidgetItem(uid = 3L, 3, "pk_3/cls_3", 3L),
+ CommunalWidgetItem(uid = 3L, 3, "pk_3/cls_3", 3L, 0),
CommunalItemRank(uid = 4L, rank = 4) to
- CommunalWidgetItem(uid = 4L, 4, "pk_4/cls_4", 4L),
+ CommunalWidgetItem(uid = 4L, 4, "pk_4/cls_4", 4L, 0),
)
fakeProviders.value =
mapOf(
@@ -192,9 +205,9 @@
fakeWidgets.value =
mapOf(
CommunalItemRank(uid = 1L, rank = 1) to
- CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L),
+ CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0),
CommunalItemRank(uid = 2L, rank = 2) to
- CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L),
+ CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L, 0),
)
fakeProviders.value =
mapOf(
@@ -249,7 +262,6 @@
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
val priority = 1
- val user = UserHandle(0)
whenever(communalWidgetHost.getAppWidgetInfo(id))
.thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
whenever(
@@ -259,11 +271,12 @@
)
)
.thenReturn(id)
- underTest.addWidget(provider, user, priority, kosmos.widgetConfiguratorSuccess)
+ underTest.addWidget(provider, mainUser, priority, kosmos.widgetConfiguratorSuccess)
runCurrent()
- verify(communalWidgetHost).allocateIdAndBindWidget(provider, user)
- verify(communalWidgetDao).addWidget(id, provider, priority)
+ verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
+ verify(communalWidgetDao)
+ .addWidget(id, provider, priority, testUserSerialNumber(mainUser))
// Verify backup requested
verify(backupManager).dataChanged()
@@ -275,7 +288,6 @@
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
val priority = 1
- val user = UserHandle(0)
whenever(communalWidgetHost.getAppWidgetInfo(id))
.thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
whenever(
@@ -285,11 +297,12 @@
)
)
.thenReturn(id)
- underTest.addWidget(provider, user, priority, kosmos.widgetConfiguratorFail)
+ underTest.addWidget(provider, mainUser, priority, kosmos.widgetConfiguratorFail)
runCurrent()
- verify(communalWidgetHost).allocateIdAndBindWidget(provider, user)
- verify(communalWidgetDao, never()).addWidget(id, provider, priority)
+ verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
+ verify(communalWidgetDao, never())
+ .addWidget(anyInt(), any<ComponentName>(), anyInt(), anyInt())
verify(appWidgetHost).deleteAppWidgetId(id)
// Verify backup not requested
@@ -302,7 +315,6 @@
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
val priority = 1
- val user = UserHandle(0)
whenever(communalWidgetHost.getAppWidgetInfo(id))
.thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
whenever(
@@ -312,13 +324,14 @@
)
)
.thenReturn(id)
- underTest.addWidget(provider, user, priority) {
+ underTest.addWidget(provider, mainUser, priority) {
throw IllegalStateException("some error")
}
runCurrent()
- verify(communalWidgetHost).allocateIdAndBindWidget(provider, user)
- verify(communalWidgetDao, never()).addWidget(id, provider, priority)
+ verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
+ verify(communalWidgetDao, never())
+ .addWidget(anyInt(), any<ComponentName>(), anyInt(), anyInt())
verify(appWidgetHost).deleteAppWidgetId(id)
// Verify backup not requested
@@ -331,7 +344,6 @@
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
val priority = 1
- val user = UserHandle(0)
whenever(communalWidgetHost.getAppWidgetInfo(id))
.thenReturn(PROVIDER_INFO_CONFIGURATION_OPTIONAL)
whenever(
@@ -341,11 +353,12 @@
)
)
.thenReturn(id)
- underTest.addWidget(provider, user, priority, kosmos.widgetConfiguratorFail)
+ underTest.addWidget(provider, mainUser, priority, kosmos.widgetConfiguratorFail)
runCurrent()
- verify(communalWidgetHost).allocateIdAndBindWidget(provider, user)
- verify(communalWidgetDao).addWidget(id, provider, priority)
+ verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
+ verify(communalWidgetDao)
+ .addWidget(id, provider, priority, testUserSerialNumber(mainUser))
// Verify backup requested
verify(backupManager).dataChanged()
@@ -444,11 +457,8 @@
runCurrent()
// Verify state restored, and widget 2 skipped
- val restoredState =
- withArgCaptor<CommunalHubState> {
- verify(communalWidgetDao).restoreCommunalHubState(capture())
- }
- val restoredWidgets = restoredState.widgets.toList()
+ verify(communalWidgetDao).restoreCommunalHubState(communalHubStateCaptor.capture())
+ val restoredWidgets = communalHubStateCaptor.firstValue.widgets.toList()
assertThat(restoredWidgets).hasSize(1)
val restoredWidget = restoredWidgets.first()
@@ -474,11 +484,8 @@
runCurrent()
// Verify widget 1 and 2 are restored, and are now 11 and 12.
- val restoredState =
- withArgCaptor<CommunalHubState> {
- verify(communalWidgetDao).restoreCommunalHubState(capture())
- }
- val restoredWidgets = restoredState.widgets.toList()
+ verify(communalWidgetDao).restoreCommunalHubState(communalHubStateCaptor.capture())
+ val restoredWidgets = communalHubStateCaptor.firstValue.widgets.toList()
assertThat(restoredWidgets).hasSize(2)
val restoredWidget1 = restoredWidgets[0]
@@ -512,11 +519,8 @@
runCurrent()
// Verify widget 1 and 2 are restored, and are now 1 and 12.
- val restoredState =
- withArgCaptor<CommunalHubState> {
- verify(communalWidgetDao).restoreCommunalHubState(capture())
- }
- val restoredWidgets = restoredState.widgets.toList()
+ verify(communalWidgetDao).restoreCommunalHubState(communalHubStateCaptor.capture())
+ val restoredWidgets = communalHubStateCaptor.firstValue.widgets.toList()
assertThat(restoredWidgets).hasSize(2)
val restoredWidget1 = restoredWidgets[0]
@@ -533,14 +537,134 @@
}
@Test
+ fun restoreWidgets_undefinedUser_restoredAsMain() =
+ testScope.runTest {
+ // Write two widgets to file, both of which have user serial number undefined.
+ val fakeState =
+ CommunalHubState().apply {
+ widgets =
+ listOf(
+ CommunalHubState.CommunalWidgetItem().apply {
+ widgetId = 1
+ componentName = "pk_name/fake_widget_1"
+ rank = 1
+ userSerialNumber =
+ CommunalWidgetItem.USER_SERIAL_NUMBER_UNDEFINED
+ },
+ CommunalHubState.CommunalWidgetItem().apply {
+ widgetId = 2
+ componentName = "pk_name/fake_widget_2"
+ rank = 2
+ userSerialNumber =
+ CommunalWidgetItem.USER_SERIAL_NUMBER_UNDEFINED
+ },
+ )
+ .toTypedArray()
+ }
+ backupUtils.writeBytesToDisk(fakeState.toByteArray())
+
+ // Set up app widget host with widget ids.
+ setAppWidgetIds(listOf(11, 12))
+
+ // Restore widgets.
+ underTest.restoreWidgets(mapOf(Pair(1, 11), Pair(2, 12)))
+ runCurrent()
+
+ // Verify widget 1 and 2 are restored with the main user.
+ verify(communalWidgetDao).restoreCommunalHubState(communalHubStateCaptor.capture())
+ val restoredWidgets = communalHubStateCaptor.firstValue.widgets.toList()
+ assertThat(restoredWidgets).hasSize(2)
+
+ val restoredWidget1 = restoredWidgets[0]
+ assertThat(restoredWidget1.widgetId).isEqualTo(11)
+ assertThat(restoredWidget1.userSerialNumber).isEqualTo(testUserSerialNumber(mainUser))
+
+ val restoredWidget2 = restoredWidgets[1]
+ assertThat(restoredWidget2.widgetId).isEqualTo(12)
+ assertThat(restoredWidget2.userSerialNumber).isEqualTo(testUserSerialNumber(mainUser))
+ }
+
+ @Test
+ fun restoreWidgets_workProfileNotRestored_widgetSkipped() =
+ testScope.runTest {
+ // Write fake state to file
+ backupUtils.writeBytesToDisk(fakeStateWithWorkProfile.toByteArray())
+
+ // Set up app widget host with widget ids.
+ // (b/349852237) It's possible that the platform restores widgets even though their user
+ // is not restored.
+ setAppWidgetIds(listOf(11, 12))
+
+ // Restore widgets.
+ underTest.restoreWidgets(mapOf(Pair(1, 11), Pair(2, 12)))
+ runCurrent()
+
+ // Verify only widget 1 is restored. Widget 2 is skipped because it belongs to a work
+ // profile, which is not restored.
+ verify(communalWidgetDao).restoreCommunalHubState(communalHubStateCaptor.capture())
+ val restoredWidgets = communalHubStateCaptor.firstValue.widgets.toList()
+ assertThat(restoredWidgets).hasSize(1)
+
+ val restoredWidget = restoredWidgets[0]
+ assertThat(restoredWidget.widgetId).isEqualTo(11)
+ assertThat(restoredWidget.userSerialNumber).isEqualTo(testUserSerialNumber(mainUser))
+ }
+
+ @Test
+ fun restoreWidgets_workProfileRestored_manuallyBindWidget() =
+ testScope.runTest {
+ // Write fake state to file
+ backupUtils.writeBytesToDisk(fakeStateWithWorkProfile.toByteArray())
+
+ // Set up app widget host with widget ids.
+ // (b/349852237) It's possible that the platform restores widgets even though their user
+ // is not restored.
+ setAppWidgetIds(listOf(11, 12))
+
+ // Restore work profile.
+ restoreUser(workProfile)
+
+ val newWidgetId = 13
+ whenever(communalWidgetHost.allocateIdAndBindWidget(any(), any()))
+ .thenReturn(newWidgetId)
+
+ // Restore widgets.
+ underTest.restoreWidgets(mapOf(Pair(1, 11), Pair(2, 12)))
+ runCurrent()
+
+ // Verify widget 1 is restored.
+ verify(communalWidgetDao).restoreCommunalHubState(communalHubStateCaptor.capture())
+ val restoredWidgets = communalHubStateCaptor.firstValue.widgets.toList()
+ assertThat(restoredWidgets).hasSize(1)
+
+ val restoredWidget = restoredWidgets[0]
+ assertThat(restoredWidget.widgetId).isEqualTo(11)
+ assertThat(restoredWidget.userSerialNumber).isEqualTo(testUserSerialNumber(mainUser))
+
+ // Verify widget 2 (now 12) is removed from platform
+ verify(appWidgetHost).deleteAppWidgetId(12)
+
+ // Verify work profile widget is manually bound
+ verify(communalWidgetDao)
+ .addWidget(
+ eq(newWidgetId),
+ componentNameCaptor.capture(),
+ eq(2),
+ eq(testUserSerialNumber(workProfile))
+ )
+ assertThat(componentNameCaptor.firstValue)
+ .isEqualTo(ComponentName("pk_name", "fake_widget_2"))
+ }
+
+ @Test
fun pendingWidgets() =
testScope.runTest {
fakeWidgets.value =
mapOf(
CommunalItemRank(uid = 1L, rank = 1) to
- CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L),
+ CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0),
CommunalItemRank(uid = 2L, rank = 2) to
- CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L),
+ CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L, 0),
)
// Widget 1 is installed
@@ -554,7 +678,7 @@
sessionId = 1,
packageName = "pk_2",
icon = fakeIcon,
- user = UserHandle.CURRENT,
+ user = mainUser,
)
)
)
@@ -572,7 +696,7 @@
priority = 2,
packageName = "pk_2",
icon = fakeIcon,
- user = UserHandle.CURRENT,
+ user = mainUser,
),
)
}
@@ -583,7 +707,7 @@
fakeWidgets.value =
mapOf(
CommunalItemRank(uid = 1L, rank = 1) to
- CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L),
+ CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L, 0),
)
// Widget 1 is pending install
@@ -594,7 +718,7 @@
sessionId = 1,
packageName = "pk_1",
icon = fakeIcon,
- user = UserHandle.CURRENT,
+ user = mainUser,
)
)
)
@@ -607,7 +731,7 @@
priority = 1,
packageName = "pk_1",
icon = fakeIcon,
- user = UserHandle.CURRENT,
+ user = mainUser,
),
)
@@ -633,6 +757,20 @@
whenever(appWidgetHost.appWidgetIds).thenReturn(ids.toIntArray())
}
+ // Commonly the user id and user serial number are the same, but for testing purposes use a
+ // simple algorithm to map a user id to a different user serial number to make sure the correct
+ // value is used.
+ private fun testUserSerialNumber(user: UserHandle): Int {
+ return user.identifier + 100
+ }
+
+ private fun restoreUser(user: UserHandle) {
+ whenever(backupManager.getUserForAncestralSerialNumber(user.identifier.toLong()))
+ .thenReturn(user)
+ whenever(userManager.getUserSerialNumber(user.identifier))
+ .thenReturn(testUserSerialNumber(user))
+ }
+
private companion object {
val PROVIDER_INFO_REQUIRES_CONFIGURATION =
AppWidgetProviderInfo().apply { configure = ComponentName("test.pkg", "test.cmp") }
@@ -650,11 +788,32 @@
widgetId = 1
componentName = "pk_name/fake_widget_1"
rank = 1
+ userSerialNumber = 0
},
CommunalHubState.CommunalWidgetItem().apply {
widgetId = 2
componentName = "pk_name/fake_widget_2"
rank = 2
+ userSerialNumber = 0
+ },
+ )
+ .toTypedArray()
+ }
+ val fakeStateWithWorkProfile =
+ CommunalHubState().apply {
+ widgets =
+ listOf(
+ CommunalHubState.CommunalWidgetItem().apply {
+ widgetId = 1
+ componentName = "pk_name/fake_widget_1"
+ rank = 1
+ userSerialNumber = 0
+ },
+ CommunalHubState.CommunalWidgetItem().apply {
+ widgetId = 2
+ componentName = "pk_name/fake_widget_2"
+ rank = 2
+ userSerialNumber = 10
},
)
.toTypedArray()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
index 612f2e7..ec4fd79 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -34,12 +34,14 @@
import android.os.PowerManager
import android.platform.test.annotations.EnableFlags
+import android.service.dream.dreamManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
+import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -64,8 +66,10 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.reset
import org.mockito.Mockito.spy
+import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -120,6 +124,66 @@
@Test
@EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToLockscreen_onPowerButtonPress_canDream_glanceableHubAvailable() =
+ testScope.runTest {
+ whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+ kosmos.setCommunalAvailable(true)
+ runCurrent()
+
+ powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON)
+ runCurrent()
+
+ // If dreaming is possible and communal is available, then we should transition to
+ // GLANCEABLE_HUB when waking up due to power button press.
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.GLANCEABLE_HUB,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToLockscreen_onPowerButtonPress_canNotDream_glanceableHubAvailable() =
+ testScope.runTest {
+ whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(false)
+ kosmos.setCommunalAvailable(true)
+ runCurrent()
+
+ powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON)
+ runCurrent()
+
+ // If dreaming is NOT possible but communal is available, then we should transition to
+ // LOCKSCREEN when waking up due to power button press.
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToLockscreen_onPowerButtonPress_canNDream_glanceableHubNotAvailable() =
+ testScope.runTest {
+ whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+ kosmos.setCommunalAvailable(false)
+ runCurrent()
+
+ powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON)
+ runCurrent()
+
+ // If dreaming is possible but communal is NOT available, then we should transition to
+ // LOCKSCREEN when waking up due to power button press.
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testTransitionToGlanceableHub_onWakeup_ifIdleOnCommunal_noOccludingActivity() =
testScope.runTest {
kosmos.fakeCommunalSceneRepository.setTransitionState(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
deleted file mode 100644
index 3d3438e..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
+++ /dev/null
@@ -1,243 +0,0 @@
-/*
- * Copyright (C) 2018 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.policy;
-
-import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
-import static com.android.systemui.util.concurrency.MockExecutorHandlerKt.mockExecutorHandler;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.os.Handler;
-import android.platform.test.flag.junit.FlagsParameterization;
-import android.testing.TestableLooper;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.res.R;
-import com.android.systemui.shade.domain.interactor.ShadeInteractor;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
-import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
-import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
-import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
-import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.kotlin.JavaAdapter;
-import com.android.systemui.util.settings.GlobalSettings;
-import com.android.systemui.util.time.SystemClock;
-
-import kotlinx.coroutines.flow.StateFlowKt;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
-import java.util.List;
-
-@SmallTest
-@RunWith(ParameterizedAndroidJunit4.class)
-@TestableLooper.RunWithLooper
-public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest {
- @Rule public MockitoRule rule = MockitoJUnit.rule();
-
- private final HeadsUpManagerLogger mHeadsUpManagerLogger = new HeadsUpManagerLogger(
- logcatLogBuffer());
- @Mock private GroupMembershipManager mGroupManager;
- @Mock private VisualStabilityProvider mVSProvider;
- @Mock private StatusBarStateController mStatusBarStateController;
- @Mock private KeyguardBypassController mBypassController;
- @Mock private ConfigurationControllerImpl mConfigurationController;
- @Mock private AccessibilityManagerWrapper mAccessibilityManagerWrapper;
- @Mock private UiEventLogger mUiEventLogger;
- @Mock private JavaAdapter mJavaAdapter;
- @Mock private ShadeInteractor mShadeInteractor;
- @Mock private DumpManager dumpManager;
- private AvalancheController mAvalancheController;
-
- @Mock private Handler mBgHandler;
-
- private static final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone {
- TestableHeadsUpManagerPhone(
- Context context,
- HeadsUpManagerLogger headsUpManagerLogger,
- GroupMembershipManager groupManager,
- VisualStabilityProvider visualStabilityProvider,
- StatusBarStateController statusBarStateController,
- KeyguardBypassController keyguardBypassController,
- ConfigurationController configurationController,
- GlobalSettings globalSettings,
- SystemClock systemClock,
- DelayableExecutor executor,
- AccessibilityManagerWrapper accessibilityManagerWrapper,
- UiEventLogger uiEventLogger,
- JavaAdapter javaAdapter,
- ShadeInteractor shadeInteractor,
- AvalancheController avalancheController
- ) {
- super(
- context,
- headsUpManagerLogger,
- statusBarStateController,
- keyguardBypassController,
- groupManager,
- visualStabilityProvider,
- configurationController,
- mockExecutorHandler(executor),
- globalSettings,
- systemClock,
- executor,
- accessibilityManagerWrapper,
- uiEventLogger,
- javaAdapter,
- shadeInteractor,
- avalancheController
- );
- mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
- mAutoDismissTime = TEST_AUTO_DISMISS_TIME;
- }
- }
-
- private HeadsUpManagerPhone createHeadsUpManagerPhone() {
- return new TestableHeadsUpManagerPhone(
- mContext,
- mHeadsUpManagerLogger,
- mGroupManager,
- mVSProvider,
- mStatusBarStateController,
- mBypassController,
- mConfigurationController,
- mGlobalSettings,
- mSystemClock,
- mExecutor,
- mAccessibilityManagerWrapper,
- mUiEventLogger,
- mJavaAdapter,
- mShadeInteractor,
- mAvalancheController
- );
- }
-
- @Parameters(name = "{0}")
- public static List<FlagsParameterization> getFlags() {
- return FlagsParameterization.allCombinationsOf(NotificationThrottleHun.FLAG_NAME);
- }
-
- public HeadsUpManagerPhoneTest(FlagsParameterization flags) {
- super(flags);
- }
-
- @Before
- public void setUp() {
- when(mShadeInteractor.isAnyExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false));
- final AccessibilityManagerWrapper accessibilityMgr =
- mDependency.injectMockDependency(AccessibilityManagerWrapper.class);
- when(accessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt()))
- .thenReturn(TEST_AUTO_DISMISS_TIME);
- when(mVSProvider.isReorderingAllowed()).thenReturn(true);
- mDependency.injectMockDependency(NotificationShadeWindowController.class);
- mContext.getOrCreateTestableResources().addOverride(
- R.integer.ambient_notification_extension_time, 500);
-
- mAvalancheController = new AvalancheController(dumpManager, mUiEventLogger, mBgHandler);
- }
-
- @Test
- public void testSnooze() {
- final HeadsUpManager hmp = createHeadsUpManagerPhone();
- final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-
- hmp.showNotification(entry);
- hmp.snooze();
-
- assertTrue(hmp.isSnoozed(entry.getSbn().getPackageName()));
- }
-
- @Test
- public void testSwipedOutNotification() {
- final HeadsUpManager hmp = createHeadsUpManagerPhone();
- final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-
- hmp.showNotification(entry);
- hmp.addSwipedOutNotification(entry.getKey());
-
- // Remove should succeed because the notification is swiped out
- final boolean removedImmediately = hmp.removeNotification(entry.getKey(),
- /* releaseImmediately = */ false);
-
- assertTrue(removedImmediately);
- assertFalse(hmp.isHeadsUpEntry(entry.getKey()));
- }
-
- @Test
- public void testCanRemoveImmediately_swipedOut() {
- final HeadsUpManager hmp = createHeadsUpManagerPhone();
- final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-
- hmp.showNotification(entry);
- hmp.addSwipedOutNotification(entry.getKey());
-
- // Notification is swiped so it can be immediately removed.
- assertTrue(hmp.canRemoveImmediately(entry.getKey()));
- }
-
- @Ignore("b/141538055")
- @Test
- public void testCanRemoveImmediately_notTopEntry() {
- final HeadsUpManager hmp = createHeadsUpManagerPhone();
- final NotificationEntry earlierEntry =
- HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
- final NotificationEntry laterEntry =
- HeadsUpManagerTestUtil.createEntry(/* id = */ 1, mContext);
- laterEntry.setRow(mRow);
-
- hmp.showNotification(earlierEntry);
- hmp.showNotification(laterEntry);
-
- // Notification is "behind" a higher priority notification so we can remove it immediately.
- assertTrue(hmp.canRemoveImmediately(earlierEntry.getKey()));
- }
-
- @Test
- public void testExtendHeadsUp() {
- final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
- final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-
- hmp.showNotification(entry);
- hmp.extendHeadsUp();
- mSystemClock.advanceTime(TEST_AUTO_DISMISS_TIME + hmp.mExtensionTime / 2);
-
- assertTrue(hmp.isHeadsUpEntry(entry.getKey()));
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
new file mode 100644
index 0000000..663cf1c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2018 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.policy
+
+import android.content.Context
+import android.os.Handler
+import android.platform.test.flag.junit.FlagsParameterization
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
+import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.concurrency.mockExecutorHandler
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.time.SystemClock
+import junit.framework.Assert
+import kotlinx.coroutines.flow.MutableStateFlow
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.kotlin.whenever
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+
+@SmallTest
+@RunWith(ParameterizedAndroidJunit4::class)
+@RunWithLooper
+class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManagerTest(flags) {
+
+ private val mHeadsUpManagerLogger = HeadsUpManagerLogger(logcatLogBuffer())
+
+ @Mock private lateinit var mGroupManager: GroupMembershipManager
+
+ @Mock private lateinit var mVSProvider: VisualStabilityProvider
+
+ @Mock private lateinit var mStatusBarStateController: StatusBarStateController
+
+ @Mock private lateinit var mBypassController: KeyguardBypassController
+
+ @Mock private lateinit var mConfigurationController: ConfigurationControllerImpl
+
+ @Mock private lateinit var mAccessibilityManagerWrapper: AccessibilityManagerWrapper
+
+ @Mock private lateinit var mUiEventLogger: UiEventLogger
+
+ @Mock private lateinit var mJavaAdapter: JavaAdapter
+
+ @Mock private lateinit var mShadeInteractor: ShadeInteractor
+
+ @Mock private lateinit var dumpManager: DumpManager
+ private lateinit var mAvalancheController: AvalancheController
+
+ @Mock private lateinit var mBgHandler: Handler
+
+ private class TestableHeadsUpManagerPhone(
+ context: Context,
+ headsUpManagerLogger: HeadsUpManagerLogger,
+ groupManager: GroupMembershipManager,
+ visualStabilityProvider: VisualStabilityProvider,
+ statusBarStateController: StatusBarStateController,
+ keyguardBypassController: KeyguardBypassController,
+ configurationController: ConfigurationController,
+ globalSettings: GlobalSettings,
+ systemClock: SystemClock,
+ executor: DelayableExecutor,
+ accessibilityManagerWrapper: AccessibilityManagerWrapper,
+ uiEventLogger: UiEventLogger,
+ javaAdapter: JavaAdapter,
+ shadeInteractor: ShadeInteractor,
+ avalancheController: AvalancheController
+ ) :
+ HeadsUpManagerPhone(
+ context,
+ headsUpManagerLogger,
+ statusBarStateController,
+ keyguardBypassController,
+ groupManager,
+ visualStabilityProvider,
+ configurationController,
+ mockExecutorHandler(executor),
+ globalSettings,
+ systemClock,
+ executor,
+ accessibilityManagerWrapper,
+ uiEventLogger,
+ javaAdapter,
+ shadeInteractor,
+ avalancheController
+ ) {
+ init {
+ mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME
+ mAutoDismissTime = TEST_AUTO_DISMISS_TIME
+ }
+ }
+
+ private fun createHeadsUpManagerPhone(): HeadsUpManagerPhone {
+ return TestableHeadsUpManagerPhone(
+ mContext,
+ mHeadsUpManagerLogger,
+ mGroupManager,
+ mVSProvider,
+ mStatusBarStateController,
+ mBypassController,
+ mConfigurationController,
+ mGlobalSettings,
+ mSystemClock,
+ mExecutor,
+ mAccessibilityManagerWrapper,
+ mUiEventLogger,
+ mJavaAdapter,
+ mShadeInteractor,
+ mAvalancheController
+ )
+ }
+
+ @Before
+ fun setUp() {
+ whenever(mShadeInteractor.isAnyExpanded).thenReturn(MutableStateFlow(false))
+ whenever(mVSProvider.isReorderingAllowed).thenReturn(true)
+ val accessibilityMgr =
+ mDependency.injectMockDependency(AccessibilityManagerWrapper::class.java)
+ whenever(
+ accessibilityMgr.getRecommendedTimeoutMillis(
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyInt()
+ )
+ )
+ .thenReturn(TEST_AUTO_DISMISS_TIME)
+ mDependency.injectMockDependency(NotificationShadeWindowController::class.java)
+ mContext
+ .getOrCreateTestableResources()
+ .addOverride(R.integer.ambient_notification_extension_time, 500)
+ mAvalancheController = AvalancheController(dumpManager, mUiEventLogger, mBgHandler)
+ }
+
+ @Test
+ fun testSnooze() {
+ val hmp: HeadsUpManager = createHeadsUpManagerPhone()
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ hmp.showNotification(entry)
+ hmp.snooze()
+ Assert.assertTrue(hmp.isSnoozed(entry.sbn.packageName))
+ }
+
+ @Test
+ fun testSwipedOutNotification() {
+ val hmp: HeadsUpManager = createHeadsUpManagerPhone()
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ hmp.showNotification(entry)
+ hmp.addSwipedOutNotification(entry.key)
+
+ // Remove should succeed because the notification is swiped out
+ val removedImmediately = hmp.removeNotification(entry.key, /* releaseImmediately= */ false)
+ Assert.assertTrue(removedImmediately)
+ Assert.assertFalse(hmp.isHeadsUpEntry(entry.key))
+ }
+
+ @Test
+ fun testCanRemoveImmediately_swipedOut() {
+ val hmp: HeadsUpManager = createHeadsUpManagerPhone()
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ hmp.showNotification(entry)
+ hmp.addSwipedOutNotification(entry.key)
+
+ // Notification is swiped so it can be immediately removed.
+ Assert.assertTrue(hmp.canRemoveImmediately(entry.key))
+ }
+
+ @Ignore("b/141538055")
+ @Test
+ fun testCanRemoveImmediately_notTopEntry() {
+ val hmp: HeadsUpManager = createHeadsUpManagerPhone()
+ val earlierEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ val laterEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 1, mContext)
+ laterEntry.row = mRow
+ hmp.showNotification(earlierEntry)
+ hmp.showNotification(laterEntry)
+
+ // Notification is "behind" a higher priority notification so we can remove it immediately.
+ Assert.assertTrue(hmp.canRemoveImmediately(earlierEntry.key))
+ }
+
+ @Test
+ fun testExtendHeadsUp() {
+ val hmp = createHeadsUpManagerPhone()
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ hmp.showNotification(entry)
+ hmp.extendHeadsUp()
+ mSystemClock.advanceTime((TEST_AUTO_DISMISS_TIME + hmp.mExtensionTime / 2).toLong())
+ Assert.assertTrue(hmp.isHeadsUpEntry(entry.key))
+ }
+
+ companion object {
+ @get:Parameters(name = "{0}")
+ val flags: List<FlagsParameterization>
+ get() = buildList {
+ addAll(FlagsParameterization.allCombinationsOf(NotificationThrottleHun.FLAG_NAME))
+ addAll(
+ FlagsParameterization.allCombinationsOf(NotificationsHeadsUpRefactor.FLAG_NAME)
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/res/layout/screenshot.xml b/packages/SystemUI/res/layout/screenshot.xml
deleted file mode 100644
index c134c8e..0000000
--- a/packages/SystemUI/res/layout/screenshot.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2011 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<com.android.systemui.screenshot.ScreenshotView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/screenshot_frame"
- android:theme="@style/FloatingOverlay"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:importantForAccessibility="no">
- <ImageView
- android:id="@+id/screenshot_scrolling_scrim"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="gone"
- android:clickable="true"
- android:importantForAccessibility="no"/>
- <ImageView
- android:id="@+id/screenshot_flash"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="gone"
- android:elevation="7dp"
- android:src="@android:color/white"/>
- <include layout="@layout/screenshot_static"
- android:id="@+id/screenshot_static"/>
-</com.android.systemui.screenshot.ScreenshotView>
diff --git a/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/2.json b/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/2.json
new file mode 100644
index 0000000..f10d92a
--- /dev/null
+++ b/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/2.json
@@ -0,0 +1,81 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 2,
+ "identityHash": "02e2da2d36e6955200edd5fb49e63c72",
+ "entities": [
+ {
+ "tableName": "communal_widget_table",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `widget_id` INTEGER NOT NULL, `component_name` TEXT NOT NULL, `item_id` INTEGER NOT NULL, `user_serial_number` INTEGER NOT NULL DEFAULT -1)",
+ "fields": [
+ {
+ "fieldPath": "uid",
+ "columnName": "uid",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "widgetId",
+ "columnName": "widget_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "componentName",
+ "columnName": "component_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "itemId",
+ "columnName": "item_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "userSerialNumber",
+ "columnName": "user_serial_number",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "-1"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "uid"
+ ]
+ }
+ },
+ {
+ "tableName": "communal_item_rank_table",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `rank` INTEGER NOT NULL DEFAULT 0)",
+ "fields": [
+ {
+ "fieldPath": "uid",
+ "columnName": "uid",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "rank",
+ "columnName": "rank",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "uid"
+ ]
+ }
+ }
+ ],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '02e2da2d36e6955200edd5fb49e63c72')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalBackupRestoreStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalBackupRestoreStartable.kt
index cdeeb6f..7abad14 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalBackupRestoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalBackupRestoreStartable.kt
@@ -21,6 +21,9 @@
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.database.ContentObserver
+import android.os.Handler
+import android.provider.Settings.Secure.USER_SETUP_COMPLETE
import com.android.systemui.CoreStartable
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.communal.domain.interactor.CommunalInteractor
@@ -29,6 +32,7 @@
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.util.settings.SecureSettings
import javax.inject.Inject
@SysUISingleton
@@ -38,10 +42,15 @@
private val broadcastDispatcher: BroadcastDispatcher,
private val communalInteractor: CommunalInteractor,
@CommunalLog logBuffer: LogBuffer,
+ private val secureSettings: SecureSettings,
+ handler: Handler,
) : CoreStartable, BroadcastReceiver() {
private val logger = Logger(logBuffer, TAG)
+ private var oldToNewWidgetIdMap = emptyMap<Int, Int>()
+ private var userSetupComplete = false
+
override fun start() {
broadcastDispatcher.registerReceiver(
receiver = this,
@@ -73,8 +82,53 @@
return
}
- val oldToNewWidgetIdMap = oldIds.zip(newIds).toMap()
- communalInteractor.restoreWidgets(oldToNewWidgetIdMap)
+ oldToNewWidgetIdMap = oldIds.zip(newIds).toMap()
+
+ logger.i({ "On old to new widget ids mapping updated: $str1" }) {
+ str1 = oldToNewWidgetIdMap.toString()
+ }
+
+ maybeRestoreWidgets()
+
+ // Start observing if user setup is not complete
+ if (!userSetupComplete) {
+ startObservingUserSetupComplete()
+ }
+ }
+
+ private val userSetupObserver =
+ object : ContentObserver(handler) {
+ override fun onChange(selfChange: Boolean) {
+ maybeRestoreWidgets()
+
+ // Stop observing once user setup is complete
+ if (userSetupComplete) {
+ stopObservingUserSetupComplete()
+ }
+ }
+ }
+
+ private fun maybeRestoreWidgets() {
+ val newValue = secureSettings.getInt(USER_SETUP_COMPLETE) > 0
+
+ if (userSetupComplete != newValue) {
+ userSetupComplete = newValue
+ logger.i({ "User setup complete: $bool1" }) { bool1 = userSetupComplete }
+ }
+
+ if (userSetupComplete && oldToNewWidgetIdMap.isNotEmpty()) {
+ logger.i("Starting to restore widgets")
+ communalInteractor.restoreWidgets(oldToNewWidgetIdMap.toMap())
+ oldToNewWidgetIdMap = emptyMap()
+ }
+ }
+
+ private fun startObservingUserSetupComplete() {
+ secureSettings.registerContentObserverSync(USER_SETUP_COMPLETE, userSetupObserver)
+ }
+
+ private fun stopObservingUserSetupComplete() {
+ secureSettings.unregisterContentObserverSync(userSetupObserver)
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index 88c3f9f6..bde6f42 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -18,7 +18,9 @@
import android.provider.Settings
import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionKey
import com.android.systemui.CoreStartable
+import com.android.systemui.Flags.communalHub
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
@@ -28,6 +30,8 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dock.DockManager
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -74,6 +78,7 @@
private val systemSettings: SystemSettings,
centralSurfacesOpt: Optional<CentralSurfaces>,
private val notificationShadeWindowController: NotificationShadeWindowController,
+ private val featureFlagsClassic: FeatureFlagsClassic,
@Application private val applicationScope: CoroutineScope,
@Background private val bgScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
@@ -86,13 +91,21 @@
private val centralSurfaces: CentralSurfaces? by centralSurfacesOpt
+ private val flagEnabled: Boolean by lazy {
+ featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub()
+ }
+
override fun start() {
+ if (!flagEnabled) {
+ return
+ }
+
// Handle automatically switching based on keyguard state.
keyguardTransitionInteractor.startedKeyguardTransitionStep
.mapLatest(::determineSceneAfterTransition)
.filterNotNull()
- .onEach { nextScene ->
- communalSceneInteractor.changeScene(nextScene, CommunalTransitionKeys.SimpleFade)
+ .onEach { (nextScene, nextTransition) ->
+ communalSceneInteractor.changeScene(nextScene, nextTransition)
}
.launchIn(applicationScope)
@@ -188,7 +201,7 @@
private suspend fun determineSceneAfterTransition(
lastStartedTransition: TransitionStep,
- ): SceneKey? {
+ ): Pair<SceneKey, TransitionKey>? {
val to = lastStartedTransition.to
val from = lastStartedTransition.from
val docked = dockManager.isDocked
@@ -201,22 +214,27 @@
// underneath the hub is shown. When launching activities over lockscreen, we only
// change scenes once the activity launch animation is finished, so avoid
// changing the scene here.
- CommunalScenes.Blank
+ Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade)
}
to == KeyguardState.GLANCEABLE_HUB && from == KeyguardState.OCCLUDED -> {
// When transitioning to the hub from an occluded state, fade out the hub without
// doing any translation.
- CommunalScenes.Communal
+ Pair(CommunalScenes.Communal, CommunalTransitionKeys.SimpleFade)
}
// Transitioning to Blank scene when entering the edit mode will be handled separately
// with custom animations.
to == KeyguardState.GONE && !communalInteractor.editModeOpen.value ->
- CommunalScenes.Blank
+ Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade)
!docked && !KeyguardState.deviceIsAwakeInState(to) -> {
// If the user taps the screen and wakes the device within this timeout, we don't
// want to dismiss the hub
delay(AWAKE_DEBOUNCE_DELAY)
- CommunalScenes.Blank
+ Pair(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade)
+ }
+ from == KeyguardState.DOZING && to == KeyguardState.GLANCEABLE_HUB -> {
+ // Make sure the communal hub is showing (immediately, not fading in) when
+ // transitioning from dozing to hub.
+ Pair(CommunalScenes.Communal, CommunalTransitionKeys.Immediately)
}
else -> null
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/backup/CommunalBackupUtils.kt b/packages/SystemUI/src/com/android/systemui/communal/data/backup/CommunalBackupUtils.kt
index a8e5174..c3d2683 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/backup/CommunalBackupUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/backup/CommunalBackupUtils.kt
@@ -43,11 +43,13 @@
val widgetsFromDb = runBlocking { database.communalWidgetDao().getWidgets().first() }
val widgetsState = mutableListOf<CommunalHubState.CommunalWidgetItem>()
widgetsFromDb.keys.forEach { rankItem ->
+ val widget = widgetsFromDb[rankItem]!!
widgetsState.add(
CommunalHubState.CommunalWidgetItem().apply {
rank = rankItem.rank
- widgetId = widgetsFromDb[rankItem]!!.widgetId
- componentName = widgetsFromDb[rankItem]?.componentName
+ widgetId = widget.widgetId
+ componentName = widget.componentName
+ userSerialNumber = widget.userSerialNumber
}
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
index 3ce8109..dff6352 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
@@ -17,17 +17,21 @@
package com.android.systemui.communal.data.db
import android.content.Context
+import android.util.Log
import androidx.annotation.VisibleForTesting
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
+import androidx.room.migration.Migration
+import androidx.sqlite.db.SupportSQLiteDatabase
import com.android.systemui.res.R
-@Database(entities = [CommunalWidgetItem::class, CommunalItemRank::class], version = 1)
+@Database(entities = [CommunalWidgetItem::class, CommunalItemRank::class], version = 2)
abstract class CommunalDatabase : RoomDatabase() {
abstract fun communalWidgetDao(): CommunalWidgetDao
companion object {
+ private const val TAG = "CommunalDatabase"
private var instance: CommunalDatabase? = null
/**
@@ -51,7 +55,8 @@
context.resources.getString(R.string.config_communalDatabase)
)
.also { builder ->
- builder.fallbackToDestructiveMigration(dropAllTables = false)
+ builder.addMigrations(MIGRATION_1_2)
+ builder.fallbackToDestructiveMigration(dropAllTables = true)
callback?.let { callback -> builder.addCallback(callback) }
}
.build()
@@ -64,5 +69,23 @@
fun setInstance(database: CommunalDatabase) {
instance = database
}
+
+ /**
+ * This migration adds a user_serial_number column and sets its default value as
+ * [CommunalWidgetItem.USER_SERIAL_NUMBER_UNDEFINED]. Work profile widgets added before the
+ * migration still work as expected, but they would be backed up as personal.
+ */
+ @VisibleForTesting
+ val MIGRATION_1_2 =
+ object : Migration(1, 2) {
+ override fun migrate(db: SupportSQLiteDatabase) {
+ Log.i(TAG, "Migrating from version 1 to 2")
+ db.execSQL(
+ "ALTER TABLE communal_widget_table " +
+ "ADD COLUMN user_serial_number INTEGER NOT NULL DEFAULT " +
+ "${CommunalWidgetItem.USER_SERIAL_NUMBER_UNDEFINED}"
+ )
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt
index 0d5336a..e33aead 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalEntities.kt
@@ -29,7 +29,28 @@
@ColumnInfo(name = "component_name") val componentName: String,
/** Reference the id of an item persisted in the glanceable hub */
@ColumnInfo(name = "item_id") val itemId: Long,
-)
+ /**
+ * A serial number of the user that the widget provider is associated with. For example, a work
+ * profile widget.
+ *
+ * A serial number may be different from its user id in that user ids may be recycled but serial
+ * numbers are unique until the device is wiped.
+ *
+ * Most commonly, this value would be 0 for the primary user, and 10 for the work profile.
+ */
+ @ColumnInfo(name = "user_serial_number", defaultValue = "$USER_SERIAL_NUMBER_UNDEFINED")
+ val userSerialNumber: Int,
+) {
+ companion object {
+ /**
+ * The user serial number associated with the widget is undefined.
+ *
+ * This should only happen for widgets migrated from V1 before user serial number was
+ * included in the schema.
+ */
+ const val USER_SERIAL_NUMBER_UNDEFINED = -1
+ }
+}
@Entity(tableName = "communal_item_rank_table")
data class CommunalItemRank(
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
index d174fd1..4dcd9bf 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
@@ -17,6 +17,7 @@
package com.android.systemui.communal.data.db
import android.content.ComponentName
+import android.os.UserManager
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Query
@@ -53,23 +54,34 @@
private val communalWidgetDaoProvider: Provider<CommunalWidgetDao>,
@Named(DEFAULT_WIDGETS) private val defaultWidgets: Array<String>,
@CommunalLog logBuffer: LogBuffer,
+ private val userManager: UserManager,
) : RoomDatabase.Callback() {
companion object {
private const val TAG = "DefaultWidgetPopulation"
}
+
private val logger = Logger(logBuffer, TAG)
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
- applicationScope.launch {
- addDefaultWidgets()
- logger.i("Default widgets were populated in the database.")
- }
+ applicationScope.launch { addDefaultWidgets() }
}
// Read default widgets from config.xml and populate the database.
private suspend fun addDefaultWidgets() =
withContext(bgDispatcher) {
+ // Default widgets should be associated with the main user.
+ val userSerialNumber =
+ userManager.mainUser?.let { mainUser ->
+ userManager.getUserSerialNumber(mainUser.identifier)
+ }
+ if (userSerialNumber == null) {
+ logger.w(
+ "Skipped populating default widgets because device does not have a main user"
+ )
+ return@withContext
+ }
+
defaultWidgets.forEachIndexed { index, name ->
val provider = ComponentName.unflattenFromString(name)
provider?.let {
@@ -80,11 +92,14 @@
.addWidget(
widgetId = id,
provider = provider,
- priority = defaultWidgets.size - index
+ priority = defaultWidgets.size - index,
+ userSerialNumber = userSerialNumber,
)
}
}
}
+
+ logger.i("Populated default widgets in the database.")
}
}
@@ -106,10 +121,16 @@
fun deleteItemRankById(itemId: Long)
@Query(
- "INSERT INTO communal_widget_table(widget_id, component_name, item_id) " +
- "VALUES(:widgetId, :componentName, :itemId)"
+ "INSERT INTO communal_widget_table" +
+ "(widget_id, component_name, item_id, user_serial_number) " +
+ "VALUES(:widgetId, :componentName, :itemId, :userSerialNumber)"
)
- fun insertWidget(widgetId: Int, componentName: String, itemId: Long): Long
+ fun insertWidget(
+ widgetId: Int,
+ componentName: String,
+ itemId: Long,
+ userSerialNumber: Int,
+ ): Long
@Query("INSERT INTO communal_item_rank_table(rank) VALUES(:rank)")
fun insertItemRank(rank: Int): Long
@@ -132,28 +153,41 @@
}
@Transaction
- fun addWidget(widgetId: Int, provider: ComponentName, priority: Int): Long {
+ fun addWidget(
+ widgetId: Int,
+ provider: ComponentName,
+ priority: Int,
+ userSerialNumber: Int,
+ ): Long {
return addWidget(
widgetId = widgetId,
componentName = provider.flattenToString(),
priority = priority,
+ userSerialNumber = userSerialNumber,
)
}
@Transaction
- fun addWidget(widgetId: Int, componentName: String, priority: Int): Long {
+ fun addWidget(
+ widgetId: Int,
+ componentName: String,
+ priority: Int,
+ userSerialNumber: Int,
+ ): Long {
return insertWidget(
widgetId = widgetId,
componentName = componentName,
itemId = insertItemRank(priority),
+ userSerialNumber = userSerialNumber,
)
}
@Transaction
fun deleteWidgetById(widgetId: Int): Boolean {
val widget =
- getWidgetByIdNow(widgetId) ?: // no entry to delete from db
- return false
+ getWidgetByIdNow(widgetId)
+ ?: // no entry to delete from db
+ return false
deleteItemRankById(widget.itemId)
deleteWidgets(widget)
@@ -166,6 +200,8 @@
clearCommunalWidgetsTable()
clearCommunalItemRankTable()
- state.widgets.forEach { addWidget(it.widgetId, it.componentName, it.rank) }
+ state.widgets.forEach {
+ addWidget(it.widgetId, it.componentName, it.rank, it.userSerialNumber)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index fdb797d..ab4c9d2 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -20,10 +20,12 @@
import android.appwidget.AppWidgetProviderInfo
import android.content.ComponentName
import android.os.UserHandle
+import android.os.UserManager
import com.android.systemui.common.data.repository.PackageChangeRepository
import com.android.systemui.common.shared.model.PackageInstallSession
import com.android.systemui.communal.data.backup.CommunalBackupUtils
import com.android.systemui.communal.data.db.CommunalWidgetDao
+import com.android.systemui.communal.data.db.CommunalWidgetItem
import com.android.systemui.communal.nano.CommunalHubState
import com.android.systemui.communal.proto.toCommunalHubState
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
@@ -98,6 +100,7 @@
private val backupManager: BackupManager,
private val backupUtils: CommunalBackupUtils,
packageChangeRepository: PackageChangeRepository,
+ private val userManager: UserManager,
) : CommunalWidgetRepository {
companion object {
const val TAG = "CommunalWidgetRepository"
@@ -185,6 +188,7 @@
widgetId = id,
provider = provider,
priority = priority,
+ userSerialNumber = userManager.getUserSerialNumber(user.identifier),
)
backupManager.dataChanged()
} else {
@@ -228,9 +232,38 @@
return@launch
}
+ // Abort restoring widgets if this code is somehow run on a device that does not have
+ // a main user, e.g. auto.
+ val mainUser = userManager.mainUser
+ if (mainUser == null) {
+ logger.w("Skipped restoring widgets because device does not have a main user")
+ return@launch
+ }
+
val widgetsWithHost = appWidgetHost.appWidgetIds.toList()
val widgetsToRemove = widgetsWithHost.toMutableList()
+ val oldUserSerialNumbers = state.widgets.map { it.userSerialNumber }.distinct()
+ val usersMap =
+ oldUserSerialNumbers.associateWith { oldUserSerialNumber ->
+ if (oldUserSerialNumber == CommunalWidgetItem.USER_SERIAL_NUMBER_UNDEFINED) {
+ // If user serial number from the backup is undefined, the widget was added
+ // to the hub before user serial numbers are stored in the database. In this
+ // case, we restore the widget with the main user.
+ mainUser
+ } else {
+ // If the user serial number is defined, look up whether the user is
+ // restored. This API returns a user handle matching its backed up user
+ // serial number, if the user is restored. Otherwise, null is returned.
+ backupManager.getUserForAncestralSerialNumber(oldUserSerialNumber.toLong())
+ ?: null
+ }
+ }
+ logger.d({ "Restored users map: $str1" }) { str1 = usersMap.toString() }
+
+ // A set to hold all widgets that belong to non-main users
+ val secondaryUserWidgets = mutableSetOf<CommunalHubState.CommunalWidgetItem>()
+
// Produce a new state to be restored, skipping invalid widgets
val newWidgets =
state.widgets.mapNotNull { restoredWidget ->
@@ -249,20 +282,64 @@
return@mapNotNull null
}
+ // Skip if user / profile is not registered
+ val newUser = usersMap[restoredWidget.userSerialNumber]
+ if (newUser == null) {
+ logger.d({
+ "Skipped restoring widget $int1 because its user $int2 is not " +
+ "registered"
+ }) {
+ int1 = restoredWidget.widgetId
+ int2 = restoredWidget.userSerialNumber
+ }
+ return@mapNotNull null
+ }
+
+ // Place secondary user widgets in a bucket to be manually bound later because
+ // of a platform bug (b/349852237) that backs up work profile widgets as
+ // personal.
+ if (newUser.identifier != mainUser.identifier) {
+ logger.d({
+ "Skipped restoring widget $int1 for now because its new user $int2 " +
+ "is secondary. This widget will be bound later."
+ }) {
+ int1 = restoredWidget.widgetId
+ int2 = newUser.identifier
+ }
+ secondaryUserWidgets.add(restoredWidget)
+ return@mapNotNull null
+ }
+
widgetsToRemove.remove(newWidgetId)
CommunalHubState.CommunalWidgetItem().apply {
widgetId = newWidgetId
componentName = restoredWidget.componentName
rank = restoredWidget.rank
+ userSerialNumber = userManager.getUserSerialNumber(newUser.identifier)
}
}
val newState = CommunalHubState().apply { widgets = newWidgets.toTypedArray() }
// Restore database
- logger.i("Restoring communal database $newState")
+ logger.i("Restoring communal database:\n$newState")
communalWidgetDao.restoreCommunalHubState(newState)
+ // Manually bind each secondary user widget due to platform bug b/349852237
+ secondaryUserWidgets.forEach { widget ->
+ val newUser = usersMap[widget.userSerialNumber]!!
+ logger.i({ "Binding secondary user ($int1) widget $int2: $str1" }) {
+ int1 = newUser.identifier
+ int2 = widget.widgetId
+ str1 = widget.componentName
+ }
+ addWidget(
+ provider = ComponentName.unflattenFromString(widget.componentName)!!,
+ user = newUser,
+ priority = widget.rank,
+ )
+ }
+
// Delete restored state file from disk
backupUtils.clear()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto b/packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto
index 0816259..bc14ae1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto
+++ b/packages/SystemUI/src/com/android/systemui/communal/proto/communal_hub_state.proto
@@ -35,5 +35,8 @@
// Rank or order of the widget in the communal hub.
int32 rank = 3;
+
+ // Serial number of the user associated with the widget.
+ int32 user_serial_number = 4;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt
index 2000f96..684303ae 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt
@@ -43,12 +43,6 @@
@SysUISingleton
@Provides
- fun provideAppWidgetManager(@Application context: Context): Optional<AppWidgetManager> {
- return Optional.ofNullable(AppWidgetManager.getInstance(context))
- }
-
- @SysUISingleton
- @Provides
fun provideCommunalAppWidgetHost(
@Application context: Context,
@Background backgroundScope: CoroutineScope,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index a6deca7..2ea27b7 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -42,6 +42,7 @@
import android.app.role.RoleManager;
import android.app.smartspace.SmartspaceManager;
import android.app.trust.TrustManager;
+import android.appwidget.AppWidgetManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.companion.virtual.VirtualDeviceManager;
@@ -374,6 +375,12 @@
@Provides
@Singleton
+ static Optional<AppWidgetManager> provideAppWidgetManager(@Application Context context) {
+ return Optional.ofNullable(AppWidgetManager.getInstance(context));
+ }
+
+ @Provides
+ @Singleton
static IAppWidgetService provideIAppWidgetService() {
return IAppWidgetService.Stub.asInterface(
ServiceManager.getService(Context.APPWIDGET_SERVICE));
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index aee65a8..cd28bec 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -17,8 +17,11 @@
package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
+import android.annotation.SuppressLint
+import android.app.DreamManager
import com.android.app.animation.Interpolators
import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -28,6 +31,7 @@
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode.Companion.isWakeAndUnlock
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.util.kotlin.Utils.Companion.sample
import com.android.systemui.util.kotlin.sample
@@ -53,9 +57,11 @@
keyguardInteractor: KeyguardInteractor,
powerInteractor: PowerInteractor,
private val communalInteractor: CommunalInteractor,
+ private val communalSceneInteractor: CommunalSceneInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
val deviceEntryRepository: DeviceEntryRepository,
private val wakeToGoneInteractor: KeyguardWakeDirectlyToGoneInteractor,
+ private val dreamManager: DreamManager,
) :
TransitionInteractor(
fromState = KeyguardState.DOZING,
@@ -115,6 +121,7 @@
}
}
+ @SuppressLint("MissingPermission")
private fun listenForDozingToAny() {
if (KeyguardWmStateRefactor.isEnabled) {
return
@@ -126,7 +133,8 @@
.filterRelevantKeyguardStateAnd { isAwake -> isAwake }
.sample(
keyguardInteractor.isKeyguardOccluded,
- communalInteractor.isIdleOnCommunal,
+ communalInteractor.isCommunalAvailable,
+ communalSceneInteractor.isIdleOnCommunal,
canTransitionToGoneOnWake,
keyguardInteractor.primaryBouncerShowing,
)
@@ -134,6 +142,7 @@
(
_,
occluded,
+ isCommunalAvailable,
isIdleOnCommunal,
canTransitionToGoneOnWake,
primaryBouncerShowing) ->
@@ -163,6 +172,19 @@
} else {
startTransitionTo(KeyguardState.GLANCEABLE_HUB)
}
+ } else if (
+ powerInteractor.detailedWakefulness.value.lastWakeReason ==
+ WakeSleepReason.POWER_BUTTON &&
+ isCommunalAvailable &&
+ dreamManager.canStartDreaming(true)
+ ) {
+ // This case handles tapping the power button to transition through
+ // dream -> off -> hub.
+ if (SceneContainerFlag.isEnabled) {
+ // TODO(b/336576536): Check if adaptation for scene framework is needed
+ } else {
+ startTransitionTo(KeyguardState.GLANCEABLE_HUB)
+ }
} else {
startTransitionTo(KeyguardState.LOCKSCREEN)
}
@@ -171,6 +193,7 @@
}
/** Figure out what state to transition to when we awake from DOZING. */
+ @SuppressLint("MissingPermission")
private fun listenForWakeFromDozing() {
if (!KeyguardWmStateRefactor.isEnabled) {
return
@@ -180,7 +203,8 @@
powerInteractor.detailedWakefulness
.filterRelevantKeyguardStateAnd { it.isAwake() }
.sample(
- communalInteractor.isIdleOnCommunal,
+ communalInteractor.isCommunalAvailable,
+ communalSceneInteractor.isIdleOnCommunal,
keyguardInteractor.biometricUnlockState,
wakeToGoneInteractor.canWakeDirectlyToGone,
keyguardInteractor.primaryBouncerShowing,
@@ -188,6 +212,7 @@
.collect {
(
_,
+ isCommunalAvailable,
isIdleOnCommunal,
biometricUnlockState,
canWakeDirectlyToGone,
@@ -227,6 +252,23 @@
ownerReason = "waking from dozing"
)
}
+ } else if (
+ powerInteractor.detailedWakefulness.value.lastWakeReason ==
+ WakeSleepReason.POWER_BUTTON &&
+ isCommunalAvailable &&
+ dreamManager.canStartDreaming(true)
+ ) {
+ // This case handles tapping the power button to transition through
+ // dream -> off -> hub.
+ if (SceneContainerFlag.isEnabled) {
+ // TODO(b/336576536): Check if adaptation for scene framework is
+ // needed
+ } else {
+ startTransitionTo(
+ KeyguardState.GLANCEABLE_HUB,
+ ownerReason = "waking from dozing"
+ )
+ }
} else {
startTransitionTo(
KeyguardState.LOCKSCREEN,
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleBackupHelper.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleBackupHelper.java
index d8c96dd..eadbffe 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleBackupHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleBackupHelper.java
@@ -41,6 +41,8 @@
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.people.PeopleBackupFollowUpJob;
import com.android.systemui.people.SharedPreferencesHelper;
@@ -67,6 +69,7 @@
private final UserHandle mUserHandle;
private final PackageManager mPackageManager;
private final IPeopleManager mIPeopleManager;
+ @Nullable
private final AppWidgetManager mAppWidgetManager;
/**
@@ -404,6 +407,9 @@
private List<String> getExistingWidgetsForUser(int userId) {
List<String> existingWidgets = new ArrayList<>();
+ if (mAppWidgetManager == null) {
+ return existingWidgets;
+ }
int[] ids = mAppWidgetManager.getAppWidgetIds(
new ComponentName(mContext, PeopleSpaceWidgetProvider.class));
for (int id : ids) {
@@ -491,7 +497,11 @@
/** Sends a broadcast to update the existing Conversation widgets. */
public static void updateWidgets(Context context) {
- int[] widgetIds = AppWidgetManager.getInstance(context)
+ AppWidgetManager manager = AppWidgetManager.getInstance(context);
+ if (manager == null) {
+ return;
+ }
+ int[] widgetIds = manager
.getAppWidgetIds(new ComponentName(context, PeopleSpaceWidgetProvider.class));
if (DEBUG) {
for (int id : widgetIds) {
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
index 0a880293..b0de80c 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
@@ -140,7 +140,7 @@
private final Object mLock = new Object();
private final Context mContext;
private LauncherApps mLauncherApps;
- private AppWidgetManager mAppWidgetManager;
+ private Optional<AppWidgetManager> mAppWidgetManagerOptional;
private IPeopleManager mIPeopleManager;
private SharedPreferences mSharedPrefs;
private PeopleManager mPeopleManager;
@@ -183,8 +183,9 @@
};
@Inject
- public PeopleSpaceWidgetManager(Context context, LauncherApps launcherApps,
- CommonNotifCollection notifCollection,
+ public PeopleSpaceWidgetManager(Context context,
+ Optional<AppWidgetManager> appWidgetManagerOptional,
+ LauncherApps launcherApps, CommonNotifCollection notifCollection,
PackageManager packageManager, Optional<Bubbles> bubblesOptional,
UserManager userManager, NotificationManager notificationManager,
BroadcastDispatcher broadcastDispatcher, @Background Executor bgExecutor,
@@ -192,7 +193,7 @@
@NonNull KeyguardUpdateMonitor keyguardUpdateMonitor) {
if (DEBUG) Log.d(TAG, "constructor");
mContext = context;
- mAppWidgetManager = AppWidgetManager.getInstance(context);
+ mAppWidgetManagerOptional = appWidgetManagerOptional;
mIPeopleManager = IPeopleManager.Stub.asInterface(
ServiceManager.getService(Context.PEOPLE_SERVICE));
mLauncherApps = launcherApps;
@@ -266,14 +267,14 @@
*/
@VisibleForTesting
PeopleSpaceWidgetManager(Context context,
- AppWidgetManager appWidgetManager, IPeopleManager iPeopleManager,
+ Optional<AppWidgetManager> appWidgetManager, IPeopleManager iPeopleManager,
PeopleManager peopleManager, LauncherApps launcherApps,
CommonNotifCollection notifCollection, PackageManager packageManager,
Optional<Bubbles> bubblesOptional, UserManager userManager, BackupManager backupManager,
INotificationManager iNotificationManager, NotificationManager notificationManager,
@Background Executor executor, UserTracker userTracker) {
mContext = context;
- mAppWidgetManager = appWidgetManager;
+ mAppWidgetManagerOptional = appWidgetManager;
mIPeopleManager = iPeopleManager;
mPeopleManager = peopleManager;
mLauncherApps = launcherApps;
@@ -337,6 +338,10 @@
/** Updates the current widget view with provided {@link PeopleSpaceTile}. */
private void updateAppWidgetViews(int appWidgetId, PeopleSpaceTile tile, Bundle options) {
+ if (mAppWidgetManagerOptional.isEmpty()) {
+ return;
+ }
+
PeopleTileKey key = getKeyFromStorageByWidgetId(appWidgetId);
if (DEBUG) Log.d(TAG, "Widget: " + appWidgetId + " for: " + key.toString());
@@ -349,7 +354,7 @@
// Tell the AppWidgetManager to perform an update on the current app widget.
if (DEBUG) Log.d(TAG, "Calling update widget for widgetId: " + appWidgetId);
- mAppWidgetManager.updateAppWidget(appWidgetId, views);
+ mAppWidgetManagerOptional.get().updateAppWidget(appWidgetId, views);
}
/** Updates tile in app widget options and the current view. */
@@ -362,13 +367,17 @@
/** Updates tile in app widget options and the current view. */
public void updateAppWidgetOptionsAndView(int appWidgetId, PeopleSpaceTile tile) {
+ if (mAppWidgetManagerOptional.isEmpty()) {
+ return;
+ }
+
if (tile == null) {
Log.w(TAG, "Storing null tile for widget " + appWidgetId);
}
synchronized (mTiles) {
mTiles.put(appWidgetId, tile);
}
- Bundle options = mAppWidgetManager.getAppWidgetOptions(appWidgetId);
+ Bundle options = mAppWidgetManagerOptional.get().getAppWidgetOptions(appWidgetId);
updateAppWidgetViews(appWidgetId, tile, options);
}
@@ -484,6 +493,10 @@
private void updateWidgetsWithNotificationChangedInBackground(StatusBarNotification sbn,
PeopleSpaceUtils.NotificationAction action,
Collection<NotificationEntry> notifications) {
+ if (mAppWidgetManagerOptional.isEmpty()) {
+ return;
+ }
+
try {
PeopleTileKey key = new PeopleTileKey(
sbn.getShortcutId(), sbn.getUser().getIdentifier(), sbn.getPackageName());
@@ -491,7 +504,7 @@
if (DEBUG) Log.d(TAG, "Sbn doesn't contain valid PeopleTileKey: " + key.toString());
return;
}
- int[] widgetIds = mAppWidgetManager.getAppWidgetIds(
+ int[] widgetIds = mAppWidgetManagerOptional.get().getAppWidgetIds(
new ComponentName(mContext, PeopleSpaceWidgetProvider.class)
);
if (widgetIds.length == 0) {
@@ -807,10 +820,14 @@
UserHandle user,
NotificationChannel channel,
int modificationType) {
+ if (mAppWidgetManagerOptional.isEmpty()) {
+ return;
+ }
+
if (channel.isConversation()) {
mBgExecutor.execute(() -> {
if (mUserManager.isUserUnlocked(user)) {
- updateWidgets(mAppWidgetManager.getAppWidgetIds(
+ updateWidgets(mAppWidgetManagerOptional.get().getAppWidgetIds(
new ComponentName(mContext, PeopleSpaceWidgetProvider.class)
));
}
@@ -829,13 +846,18 @@
// learning about the widget. If so, the widget adder should have populated options with
// PeopleTileKey arguments.
if (DEBUG) Log.d(TAG, "onAppWidgetOptionsChanged called for widget: " + appWidgetId);
+ if (mAppWidgetManagerOptional.isEmpty()) {
+ return;
+ }
+
PeopleTileKey optionsKey = AppWidgetOptionsHelper.getPeopleTileKeyFromBundle(newOptions);
if (PeopleTileKey.isValid(optionsKey)) {
if (DEBUG) {
Log.d(TAG, "PeopleTileKey was present in Options, shortcutId: "
+ optionsKey.getShortcutId());
}
- AppWidgetOptionsHelper.removePeopleTileKey(mAppWidgetManager, appWidgetId);
+ AppWidgetOptionsHelper.removePeopleTileKey(mAppWidgetManagerOptional.get(),
+ appWidgetId);
addNewWidget(appWidgetId, optionsKey);
}
// Update views for new widget dimensions.
@@ -1004,6 +1026,10 @@
public boolean requestPinAppWidget(ShortcutInfo shortcutInfo, Bundle options) {
if (DEBUG) Log.d(TAG, "Requesting pin widget, shortcutId: " + shortcutInfo.getId());
+ if (mAppWidgetManagerOptional.isEmpty()) {
+ return false;
+ }
+
RemoteViews widgetPreview = getPreview(shortcutInfo.getId(),
shortcutInfo.getUserHandle(), shortcutInfo.getPackage(), options);
if (widgetPreview == null) {
@@ -1017,7 +1043,8 @@
PeopleSpaceWidgetPinnedReceiver.getPendingIntent(mContext, shortcutInfo);
ComponentName componentName = new ComponentName(mContext, PeopleSpaceWidgetProvider.class);
- return mAppWidgetManager.requestPinAppWidget(componentName, extras, successCallback);
+ return mAppWidgetManagerOptional.get().requestPinAppWidget(componentName, extras,
+ successCallback);
}
/** Returns a list of map entries corresponding to user's priority conversations. */
@@ -1104,7 +1131,11 @@
/** Updates any app widget to the current state, triggered by a broadcast update. */
@VisibleForTesting
void updateWidgetsFromBroadcastInBackground(String entryPoint) {
- int[] appWidgetIds = mAppWidgetManager.getAppWidgetIds(
+ if (mAppWidgetManagerOptional.isEmpty()) {
+ return;
+ }
+
+ int[] appWidgetIds = mAppWidgetManagerOptional.get().getAppWidgetIds(
new ComponentName(mContext, PeopleSpaceWidgetProvider.class));
if (appWidgetIds == null) {
return;
@@ -1272,13 +1303,17 @@
remapSharedFile(widgets);
remapFollowupFile(widgets);
- int[] widgetIds = mAppWidgetManager.getAppWidgetIds(
+ if (mAppWidgetManagerOptional.isEmpty()) {
+ return;
+ }
+
+ int[] widgetIds = mAppWidgetManagerOptional.get().getAppWidgetIds(
new ComponentName(mContext, PeopleSpaceWidgetProvider.class));
Bundle b = new Bundle();
b.putBoolean(AppWidgetManager.OPTION_APPWIDGET_RESTORE_COMPLETED, true);
for (int id : widgetIds) {
if (DEBUG) Log.d(TAG, "Setting widget as restored, widget id:" + id);
- mAppWidgetManager.updateAppWidgetOptions(id, b);
+ mAppWidgetManagerOptional.get().updateAppWidgetOptions(id, b);
}
updateWidgets(widgetIds);
@@ -1437,14 +1472,15 @@
@VisibleForTesting
void updateGeneratedPreviewForUser(UserHandle user) {
if (!generatedPreviews() || mUpdatedPreviews.get(user.getIdentifier())
- || !mUserManager.isUserUnlocked(user)) {
+ || !mUserManager.isUserUnlocked(user) || mAppWidgetManagerOptional.isEmpty()) {
return;
}
// The widget provider may be disabled on SystemUI implementers, e.g. TvSystemUI.
ComponentName provider = new ComponentName(mContext, PeopleSpaceWidgetProvider.class);
- List<AppWidgetProviderInfo> infos = mAppWidgetManager.getInstalledProvidersForPackage(
- mContext.getPackageName(), user);
+ List<AppWidgetProviderInfo> infos =
+ mAppWidgetManagerOptional.get().getInstalledProvidersForPackage(
+ mContext.getPackageName(), user);
if (infos.stream().noneMatch(info -> info.provider.equals(provider))) {
return;
}
@@ -1452,7 +1488,7 @@
if (DEBUG) {
Log.d(TAG, "Updating People Space widget preview for user " + user.getIdentifier());
}
- boolean success = mAppWidgetManager.setWidgetPreview(
+ boolean success = mAppWidgetManagerOptional.get().setWidgetPreview(
provider, WIDGET_CATEGORY_HOME_SCREEN | WIDGET_CATEGORY_KEYGUARD,
new RemoteViews(mContext.getPackageName(),
R.layout.people_space_placeholder_layout));
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt
index 1868b4a..54e0319 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt
@@ -40,7 +40,7 @@
private val intentExecutor: ActionIntentExecutor,
@Application private val applicationScope: CoroutineScope,
@Assisted val window: Window,
- @Assisted val viewProxy: ScreenshotViewProxy,
+ @Assisted val viewProxy: ScreenshotShelfViewProxy,
@Assisted val finishDismiss: () -> Unit,
) {
@@ -109,7 +109,7 @@
interface Factory {
fun create(
window: Window,
- viewProxy: ScreenshotViewProxy,
+ viewProxy: ScreenshotShelfViewProxy,
finishDismiss: (() -> Unit)
): ActionExecutor
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
deleted file mode 100644
index 3d024a6..0000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.screenshot
-
-import android.animation.Animator
-import android.app.Notification
-import android.content.Context
-import android.graphics.Bitmap
-import android.graphics.Rect
-import android.util.Log
-import android.view.KeyEvent
-import android.view.LayoutInflater
-import android.view.ScrollCaptureResponse
-import android.view.View
-import android.view.ViewTreeObserver
-import android.view.WindowInsets
-import android.window.OnBackInvokedCallback
-import android.window.OnBackInvokedDispatcher
-import androidx.appcompat.content.res.AppCompatResources
-import com.android.internal.logging.UiEventLogger
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.res.R
-import com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS
-import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER
-import com.android.systemui.screenshot.scroll.ScrollCaptureController
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
-
-/**
- * Legacy implementation of screenshot view methods. Just proxies the calls down into the original
- * ScreenshotView.
- */
-class LegacyScreenshotViewProxy
-@AssistedInject
-constructor(
- private val logger: UiEventLogger,
- flags: FeatureFlags,
- @Assisted private val context: Context,
- @Assisted private val displayId: Int
-) : ScreenshotViewProxy {
- override val view: ScreenshotView =
- LayoutInflater.from(context).inflate(R.layout.screenshot, null) as ScreenshotView
- override val screenshotPreview: View
- override var packageName: String = ""
- set(value) {
- field = value
- view.setPackageName(value)
- }
- override var callbacks: ScreenshotView.ScreenshotViewCallback? = null
- set(value) {
- field = value
- view.setCallbacks(value)
- }
- override var screenshot: ScreenshotData? = null
- set(value) {
- field = value
- value?.let {
- val badgeBg =
- AppCompatResources.getDrawable(context, R.drawable.overlay_badge_background)
- val user = it.userHandle
- if (badgeBg != null && user != null) {
- view.badgeScreenshot(context.packageManager.getUserBadgedIcon(badgeBg, user))
- }
- view.setScreenshot(it)
- }
- }
-
- override val isAttachedToWindow
- get() = view.isAttachedToWindow
- override val isDismissing
- get() = view.isDismissing
- override val isPendingSharedTransition
- get() = view.isPendingSharedTransition
-
- init {
- view.setUiEventLogger(logger)
- view.setDefaultDisplay(displayId)
- view.setFlags(flags)
- addPredictiveBackListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
- setOnKeyListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
- if (LogConfig.DEBUG_WINDOW) {
- Log.d(TAG, "adding OnComputeInternalInsetsListener")
- }
- view.viewTreeObserver.addOnComputeInternalInsetsListener(view)
- screenshotPreview = view.screenshotPreview
- }
-
- override fun reset() = view.reset()
- override fun updateInsets(insets: WindowInsets) = view.updateInsets(insets)
- override fun updateOrientation(insets: WindowInsets) = view.updateOrientation(insets)
-
- override fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator =
- view.createScreenshotDropInAnimation(screenRect, showFlash)
-
- override fun addQuickShareChip(quickShareAction: Notification.Action) =
- view.addQuickShareChip(quickShareAction)
-
- override fun setChipIntents(imageData: ScreenshotController.SavedImageData) =
- view.setChipIntents(imageData)
-
- override fun requestDismissal(event: ScreenshotEvent?) {
- if (DEBUG_DISMISS) {
- Log.d(TAG, "screenshot dismissal requested")
- }
- // If we're already animating out, don't restart the animation
- if (view.isDismissing) {
- if (DEBUG_DISMISS) {
- Log.v(TAG, "Already dismissing, ignoring duplicate command $event")
- }
- return
- }
- event?.let { logger.log(event, 0, packageName) }
- view.animateDismissal()
- }
-
- override fun showScrollChip(packageName: String, onClick: Runnable) =
- view.showScrollChip(packageName, onClick)
-
- override fun hideScrollChip() = view.hideScrollChip()
-
- override fun prepareScrollingTransition(
- response: ScrollCaptureResponse,
- screenBitmap: Bitmap,
- newScreenshot: Bitmap,
- screenshotTakenInPortrait: Boolean,
- onTransitionPrepared: Runnable,
- ) {
- view.prepareScrollingTransition(
- response,
- screenBitmap,
- newScreenshot,
- screenshotTakenInPortrait
- )
- view.post { onTransitionPrepared.run() }
- }
-
- override fun startLongScreenshotTransition(
- transitionDestination: Rect,
- onTransitionEnd: Runnable,
- longScreenshot: ScrollCaptureController.LongScreenshot
- ) = view.startLongScreenshotTransition(transitionDestination, onTransitionEnd, longScreenshot)
-
- override fun restoreNonScrollingUi() = view.restoreNonScrollingUi()
-
- override fun fadeForSharedTransition() {} // unused
-
- override fun stopInputListening() = view.stopInputListening()
-
- override fun requestFocus() {
- view.requestFocus()
- }
-
- override fun announceForAccessibility(string: String) = view.announceForAccessibility(string)
-
- override fun prepareEntranceAnimation(runnable: Runnable) {
- view.viewTreeObserver.addOnPreDrawListener(
- object : ViewTreeObserver.OnPreDrawListener {
- override fun onPreDraw(): Boolean {
- if (LogConfig.DEBUG_WINDOW) {
- Log.d(TAG, "onPreDraw: startAnimation")
- }
- view.viewTreeObserver.removeOnPreDrawListener(this)
- runnable.run()
- return true
- }
- }
- )
- }
-
- private fun addPredictiveBackListener(onDismissRequested: (ScreenshotEvent) -> Unit) {
- val onBackInvokedCallback = OnBackInvokedCallback {
- if (LogConfig.DEBUG_INPUT) {
- Log.d(TAG, "Predictive Back callback dispatched")
- }
- onDismissRequested.invoke(SCREENSHOT_DISMISSED_OTHER)
- }
- view.addOnAttachStateChangeListener(
- object : View.OnAttachStateChangeListener {
- override fun onViewAttachedToWindow(v: View) {
- if (LogConfig.DEBUG_INPUT) {
- Log.d(TAG, "Registering Predictive Back callback")
- }
- view
- .findOnBackInvokedDispatcher()
- ?.registerOnBackInvokedCallback(
- OnBackInvokedDispatcher.PRIORITY_DEFAULT,
- onBackInvokedCallback
- )
- }
-
- override fun onViewDetachedFromWindow(view: View) {
- if (LogConfig.DEBUG_INPUT) {
- Log.d(TAG, "Unregistering Predictive Back callback")
- }
- view
- .findOnBackInvokedDispatcher()
- ?.unregisterOnBackInvokedCallback(onBackInvokedCallback)
- }
- }
- )
- }
- private fun setOnKeyListener(onDismissRequested: (ScreenshotEvent) -> Unit) {
- view.setOnKeyListener(
- object : View.OnKeyListener {
- override fun onKey(view: View, keyCode: Int, event: KeyEvent): Boolean {
- if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
- if (LogConfig.DEBUG_INPUT) {
- Log.d(TAG, "onKeyEvent: $keyCode")
- }
- onDismissRequested.invoke(SCREENSHOT_DISMISSED_OTHER)
- return true
- }
- return false
- }
- }
- )
- }
-
- @AssistedFactory
- interface Factory : ScreenshotViewProxy.Factory {
- override fun getProxy(context: Context, displayId: Int): LegacyScreenshotViewProxy
- }
-
- companion object {
- private const val TAG = "LegacyScreenshotViewProxy"
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
index 5960462..474afa8b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
@@ -137,7 +137,7 @@
val offset = container.height + params.topMargin + params.bottomMargin
val anim = if (animateIn) ValueAnimator.ofFloat(0f, 1f) else ValueAnimator.ofFloat(1f, 0f)
with(anim) {
- duration = ScreenshotView.SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS
+ duration = MESSAGE_EXPANSION_DURATION_MS
interpolator = AccelerateDecelerateInterpolator()
addUpdateListener { valueAnimator: ValueAnimator ->
val interpolation = valueAnimator.animatedValue as Float
@@ -147,4 +147,8 @@
}
return anim
}
+
+ companion object {
+ const val MESSAGE_EXPANSION_DURATION_MS: Long = 400
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 0cbf8f9..7739009 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -191,7 +191,7 @@
private final WindowContext mContext;
private final FeatureFlags mFlags;
- private final ScreenshotViewProxy mViewProxy;
+ private final ScreenshotShelfViewProxy mViewProxy;
private final ScreenshotNotificationsController mNotificationsController;
private final ScreenshotSmartActions mScreenshotSmartActions;
private final UiEventLogger mUiEventLogger;
@@ -246,7 +246,7 @@
Context context,
WindowManager windowManager,
FeatureFlags flags,
- ScreenshotViewProxy.Factory viewProxyFactory,
+ ScreenshotShelfViewProxy.Factory viewProxyFactory,
ScreenshotSmartActions screenshotSmartActions,
ScreenshotNotificationsController.Factory screenshotNotificationsControllerFactory,
UiEventLogger uiEventLogger,
@@ -477,9 +477,6 @@
}
mViewProxy.setPackageName(mPackageName);
-
- mViewProxy.updateOrientation(
- mWindowManager.getCurrentWindowMetrics().getWindowInsets());
}
/**
@@ -528,7 +525,7 @@
}
mMessageContainerController.setView(mViewProxy.getView());
- mViewProxy.setCallbacks(new ScreenshotView.ScreenshotViewCallback() {
+ mViewProxy.setCallbacks(new ScreenshotShelfViewProxy.ScreenshotViewCallback() {
@Override
public void onUserInteraction() {
if (DEBUG_INPUT) {
@@ -538,13 +535,6 @@
}
@Override
- public void onAction(Intent intent, UserHandle owner, boolean overrideTransition) {
- Pair<ActivityOptions, ExitTransitionCoordinator> exit = createWindowTransition();
- mActionIntentExecutor.launchIntentAsync(
- intent, owner, overrideTransition, exit.first, exit.second);
- }
-
- @Override
public void onDismiss() {
finishDismiss();
}
@@ -871,62 +861,6 @@
mSaveInBgTask.execute();
}
-
- /**
- * Sets up the action shade and its entrance animation, once we get the screenshot URI.
- */
- private void showUiOnActionsReady(ScreenshotController.SavedImageData imageData) {
- logSuccessOnActionsReady(imageData);
- mScreenshotHandler.resetTimeout();
-
- if (imageData.uri != null) {
- if (DEBUG_UI) {
- Log.d(TAG, "Showing UI actions");
- }
- if (!imageData.owner.equals(Process.myUserHandle())) {
- Log.d(TAG, "Screenshot saved to user " + imageData.owner + " as "
- + imageData.uri);
- }
- mScreenshotHandler.post(() -> {
- if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
- mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- mViewProxy.setChipIntents(imageData);
- }
- });
- } else {
- mViewProxy.setChipIntents(imageData);
- }
- });
- }
- }
-
- /**
- * Sets up the action shade and its entrance animation, once we get the Quick Share action data.
- */
- private void showUiOnQuickShareActionReady(ScreenshotController.QuickShareData quickShareData) {
- if (DEBUG_UI) {
- Log.d(TAG, "Showing UI for Quick Share action");
- }
- if (quickShareData.quickShareAction != null) {
- mScreenshotHandler.post(() -> {
- if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
- mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- mViewProxy.addQuickShareChip(quickShareData.quickShareAction);
- }
- });
- } else {
- mViewProxy.addQuickShareChip(quickShareData.quickShareAction);
- }
- });
- }
- }
-
/**
* Logs success/failure of the screenshot saving task, and shows an error if it failed.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
index 1b5fa34..50215af 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
@@ -18,7 +18,6 @@
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
-import android.app.Notification
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Rect
@@ -45,7 +44,6 @@
import com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS
import com.android.systemui.screenshot.LogConfig.DEBUG_INPUT
import com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW
-import com.android.systemui.screenshot.ScreenshotController.SavedImageData
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER
import com.android.systemui.screenshot.scroll.ScrollCaptureController
import com.android.systemui.screenshot.ui.ScreenshotAnimationController
@@ -70,13 +68,23 @@
private val thumbnailObserver: ThumbnailObserver,
@Assisted private val context: Context,
@Assisted private val displayId: Int
-) : ScreenshotViewProxy {
- override val view: ScreenshotShelfView =
+) {
+
+ interface ScreenshotViewCallback {
+ fun onUserInteraction()
+
+ fun onDismiss()
+
+ /** DOWN motion event was observed outside of the touchable areas of this view. */
+ fun onTouchOutside()
+ }
+
+ val view: ScreenshotShelfView =
LayoutInflater.from(context).inflate(R.layout.screenshot_shelf, null) as ScreenshotShelfView
- override val screenshotPreview: View
- override var packageName: String = ""
- override var callbacks: ScreenshotView.ScreenshotViewCallback? = null
- override var screenshot: ScreenshotData? = null
+ val screenshotPreview: View
+ var packageName: String = ""
+ var callbacks: ScreenshotViewCallback? = null
+ var screenshot: ScreenshotData? = null
set(value) {
value?.let {
viewModel.setScreenshotBitmap(it.bitmap)
@@ -92,10 +100,11 @@
field = value
}
- override val isAttachedToWindow
+ val isAttachedToWindow
get() = view.isAttachedToWindow
- override var isDismissing = false
- override var isPendingSharedTransition = false
+
+ var isDismissing = false
+ var isPendingSharedTransition = false
private val animationController = ScreenshotAnimationController(view, viewModel)
private var inputMonitor: InputMonitorCompat? = null
@@ -136,17 +145,17 @@
)
}
- override fun reset() {
+ fun reset() {
animationController.cancel()
isPendingSharedTransition = false
viewModel.reset()
}
- override fun updateInsets(insets: WindowInsets) {
+
+ fun updateInsets(insets: WindowInsets) {
view.updateInsets(insets)
}
- override fun updateOrientation(insets: WindowInsets) {}
- override fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator {
+ fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator {
val entrance =
animationController.getEntranceAnimation(screenRect, showFlash) {
viewModel.setAnimationState(AnimationState.ENTRANCE_REVEAL)
@@ -164,11 +173,7 @@
return entrance
}
- override fun addQuickShareChip(quickShareAction: Notification.Action) {}
-
- override fun setChipIntents(imageData: SavedImageData) {}
-
- override fun requestDismissal(event: ScreenshotEvent?) {
+ fun requestDismissal(event: ScreenshotEvent?) {
requestDismissal(event, null)
}
@@ -187,6 +192,7 @@
override fun onAnimationStart(animator: Animator) {
isDismissing = true
}
+
override fun onAnimationEnd(animator: Animator) {
isDismissing = false
callbacks?.onDismiss()
@@ -196,11 +202,7 @@
animator.start()
}
- override fun showScrollChip(packageName: String, onClick: Runnable) {}
-
- override fun hideScrollChip() {}
-
- override fun prepareScrollingTransition(
+ fun prepareScrollingTransition(
response: ScrollCaptureResponse,
screenBitmap: Bitmap, // unused
newScreenshot: Bitmap,
@@ -228,7 +230,7 @@
return r
}
- override fun startLongScreenshotTransition(
+ fun startLongScreenshotTransition(
transitionDestination: Rect,
onTransitionEnd: Runnable,
longScreenshot: ScrollCaptureController.LongScreenshot,
@@ -243,27 +245,27 @@
transitionAnimation.start()
}
- override fun restoreNonScrollingUi() {
+ fun restoreNonScrollingUi() {
viewModel.setScrollableRect(null)
viewModel.setScrollingScrimBitmap(null)
animationController.restoreUI()
callbacks?.onUserInteraction() // reset the timeout
}
- override fun stopInputListening() {
+ fun stopInputListening() {
inputMonitor?.dispose()
inputMonitor = null
inputEventReceiver?.dispose()
inputEventReceiver = null
}
- override fun requestFocus() {
+ fun requestFocus() {
view.requestFocus()
}
- override fun announceForAccessibility(string: String) = view.announceForAccessibility(string)
+ fun announceForAccessibility(string: String) = view.announceForAccessibility(string)
- override fun prepareEntranceAnimation(runnable: Runnable) {
+ fun prepareEntranceAnimation(runnable: Runnable) {
view.viewTreeObserver.addOnPreDrawListener(
object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
@@ -276,7 +278,7 @@
)
}
- override fun fadeForSharedTransition() {
+ fun fadeForSharedTransition() {
animationController.fadeForSharedTransition()
}
@@ -349,7 +351,7 @@
}
@AssistedFactory
- interface Factory : ScreenshotViewProxy.Factory {
- override fun getProxy(context: Context, displayId: Int): ScreenshotShelfViewProxy
+ interface Factory {
+ fun getProxy(context: Context, displayId: Int): ScreenshotShelfViewProxy
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
deleted file mode 100644
index 59e38a8..0000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ /dev/null
@@ -1,1126 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.screenshot;
-
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_TAKE_SCREENSHOT;
-import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
-import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
-import static com.android.systemui.screenshot.LogConfig.DEBUG_INPUT;
-import static com.android.systemui.screenshot.LogConfig.DEBUG_SCROLL;
-import static com.android.systemui.screenshot.LogConfig.DEBUG_UI;
-import static com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW;
-import static com.android.systemui.screenshot.LogConfig.logTag;
-import static com.android.systemui.screenshot.ScreenshotController.SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS;
-
-import static java.util.Objects.requireNonNull;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ValueAnimator;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.BroadcastOptions;
-import android.app.Notification;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.ColorStateList;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BlendMode;
-import android.graphics.Color;
-import android.graphics.Insets;
-import android.graphics.Matrix;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.graphics.drawable.InsetDrawable;
-import android.graphics.drawable.LayerDrawable;
-import android.os.Bundle;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.util.MathUtils;
-import android.view.Choreographer;
-import android.view.Display;
-import android.view.DisplayCutout;
-import android.view.GestureDetector;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.ScrollCaptureResponse;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.WindowMetrics;
-import android.view.accessibility.AccessibilityManager;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
-import android.widget.FrameLayout;
-import android.widget.HorizontalScrollView;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-import androidx.constraintlayout.widget.ConstraintLayout;
-
-import com.android.internal.jank.InteractionJankMonitor;
-import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.res.R;
-import com.android.systemui.screenshot.scroll.ScrollCaptureController;
-import com.android.systemui.shared.system.InputChannelCompat;
-import com.android.systemui.shared.system.InputMonitorCompat;
-import com.android.systemui.shared.system.QuickStepContract;
-
-import java.util.ArrayList;
-
-/**
- * Handles the visual elements and animations for the screenshot flow.
- */
-public class ScreenshotView extends FrameLayout implements
- ViewTreeObserver.OnComputeInternalInsetsListener {
-
- public interface ScreenshotViewCallback {
- void onUserInteraction();
-
- void onAction(Intent intent, UserHandle owner, boolean overrideTransition);
-
- void onDismiss();
-
- /** DOWN motion event was observed outside of the touchable areas of this view. */
- void onTouchOutside();
- }
-
- private static final String TAG = logTag(ScreenshotView.class);
-
- private static final long SCREENSHOT_FLASH_IN_DURATION_MS = 133;
- private static final long SCREENSHOT_FLASH_OUT_DURATION_MS = 217;
- // delay before starting to fade in dismiss button
- private static final long SCREENSHOT_TO_CORNER_DISMISS_DELAY_MS = 200;
- private static final long SCREENSHOT_TO_CORNER_X_DURATION_MS = 234;
- private static final long SCREENSHOT_TO_CORNER_Y_DURATION_MS = 500;
- private static final long SCREENSHOT_TO_CORNER_SCALE_DURATION_MS = 234;
- public static final long SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS = 400;
- private static final long SCREENSHOT_ACTIONS_ALPHA_DURATION_MS = 100;
- private static final float SCREENSHOT_ACTIONS_START_SCALE_X = .7f;
-
- private final Resources mResources;
- private final Interpolator mFastOutSlowIn;
- private final DisplayMetrics mDisplayMetrics;
- private final float mFixedSize;
- private final AccessibilityManager mAccessibilityManager;
- private final GestureDetector mSwipeDetector;
-
- private int mDefaultDisplay = Display.DEFAULT_DISPLAY;
- private int mNavMode;
- private boolean mOrientationPortrait;
- private boolean mDirectionLTR;
-
- private ImageView mScrollingScrim;
- private DraggableConstraintLayout mScreenshotStatic;
- private ImageView mScreenshotPreview;
- private ImageView mScreenshotBadge;
- private View mScreenshotPreviewBorder;
- private ImageView mScrollablePreview;
- private ImageView mScreenshotFlash;
- private ImageView mActionsContainerBackground;
- private HorizontalScrollView mActionsContainer;
- private LinearLayout mActionsView;
- private FrameLayout mDismissButton;
- private OverlayActionChip mShareChip;
- private OverlayActionChip mEditChip;
- private OverlayActionChip mScrollChip;
- private OverlayActionChip mQuickShareChip;
-
- private UiEventLogger mUiEventLogger;
- private ScreenshotViewCallback mCallbacks;
- private boolean mPendingSharedTransition;
- private InputMonitorCompat mInputMonitor;
- private InputChannelCompat.InputEventReceiver mInputEventReceiver;
- private boolean mShowScrollablePreview;
- private String mPackageName = "";
-
- private final ArrayList<OverlayActionChip> mSmartChips = new ArrayList<>();
- private PendingInteraction mPendingInteraction;
- // Should only be set/used if the SCREENSHOT_METADATA flag is set.
- private ScreenshotData mScreenshotData;
-
- private final InteractionJankMonitor mInteractionJankMonitor;
- private FeatureFlags mFlags;
- private final Bundle mInteractiveBroadcastOption;
-
- private enum PendingInteraction {
- PREVIEW,
- EDIT,
- SHARE,
- QUICK_SHARE
- }
-
- public ScreenshotView(Context context) {
- this(context, null);
- }
-
- public ScreenshotView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public ScreenshotView(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public ScreenshotView(
- Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- mResources = mContext.getResources();
- mInteractionJankMonitor = getInteractionJankMonitorInstance();
-
- BroadcastOptions options = BroadcastOptions.makeBasic();
- options.setInteractive(true);
- mInteractiveBroadcastOption = options.toBundle();
-
- mFixedSize = mResources.getDimensionPixelSize(R.dimen.overlay_x_scale);
-
- // standard material ease
- mFastOutSlowIn =
- AnimationUtils.loadInterpolator(mContext, android.R.interpolator.fast_out_slow_in);
-
- mDisplayMetrics = new DisplayMetrics();
- mContext.getDisplay().getRealMetrics(mDisplayMetrics);
-
- mAccessibilityManager = AccessibilityManager.getInstance(mContext);
-
- mSwipeDetector = new GestureDetector(mContext,
- new GestureDetector.SimpleOnGestureListener() {
- final Rect mActionsRect = new Rect();
-
- @Override
- public boolean onScroll(
- MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) {
- mActionsContainer.getBoundsOnScreen(mActionsRect);
- // return true if we aren't in the actions bar, or if we are but it isn't
- // scrollable in the direction of movement
- return !mActionsRect.contains((int) ev2.getRawX(), (int) ev2.getRawY())
- || !mActionsContainer.canScrollHorizontally((int) distanceX);
- }
- });
- mSwipeDetector.setIsLongpressEnabled(false);
- addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(View v) {
- startInputListening();
- }
-
- @Override
- public void onViewDetachedFromWindow(View v) {
- stopInputListening();
- }
- });
- }
-
- private InteractionJankMonitor getInteractionJankMonitorInstance() {
- return InteractionJankMonitor.getInstance();
- }
-
- public void hideScrollChip() {
- mScrollChip.setVisibility(View.GONE);
- }
-
- /**
- * Called to display the scroll action chip when support is detected.
- *
- * @param packageName the owning package of the window to be captured
- * @param onClick the action to take when the chip is clicked.
- */
- public void showScrollChip(String packageName, Runnable onClick) {
- if (DEBUG_SCROLL) {
- Log.d(TAG, "Showing Scroll option");
- }
- mScrollChip.setVisibility(VISIBLE);
- mScrollChip.setOnClickListener((v) -> onClick.run());
- }
-
- @Override // ViewTreeObserver.OnComputeInternalInsetsListener
- public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
- inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
- inoutInfo.touchableRegion.set(getTouchRegion(true));
- }
-
- private Region getSwipeRegion() {
- Region swipeRegion = new Region();
-
- final Rect tmpRect = new Rect();
- int swipePadding = (int) FloatingWindowUtil.dpToPx(
- mDisplayMetrics, DraggableConstraintLayout.SWIPE_PADDING_DP * -1);
- mScreenshotPreview.getBoundsOnScreen(tmpRect);
- tmpRect.inset(swipePadding, swipePadding);
- swipeRegion.op(tmpRect, Region.Op.UNION);
- mActionsContainerBackground.getBoundsOnScreen(tmpRect);
- tmpRect.inset(swipePadding, swipePadding);
- swipeRegion.op(tmpRect, Region.Op.UNION);
- mDismissButton.getBoundsOnScreen(tmpRect);
- swipeRegion.op(tmpRect, Region.Op.UNION);
-
- View messageContainer = findViewById(R.id.screenshot_message_container);
- if (messageContainer != null) {
- messageContainer.getBoundsOnScreen(tmpRect);
- swipeRegion.op(tmpRect, Region.Op.UNION);
- }
- View messageDismiss = findViewById(R.id.message_dismiss_button);
- if (messageDismiss != null) {
- messageDismiss.getBoundsOnScreen(tmpRect);
- swipeRegion.op(tmpRect, Region.Op.UNION);
- }
-
- return swipeRegion;
- }
-
- private Region getTouchRegion(boolean includeScrim) {
- Region touchRegion = getSwipeRegion();
-
- if (includeScrim && mScrollingScrim.getVisibility() == View.VISIBLE) {
- final Rect tmpRect = new Rect();
- mScrollingScrim.getBoundsOnScreen(tmpRect);
- touchRegion.op(tmpRect, Region.Op.UNION);
- }
-
- if (QuickStepContract.isGesturalMode(mNavMode)) {
- final WindowManager wm = mContext.getSystemService(WindowManager.class);
- final WindowMetrics windowMetrics = wm.getCurrentWindowMetrics();
- final Insets gestureInsets = windowMetrics.getWindowInsets().getInsets(
- WindowInsets.Type.systemGestures());
- // Receive touches in gesture insets such that they don't cause TOUCH_OUTSIDE
- Rect inset = new Rect(0, 0, gestureInsets.left, mDisplayMetrics.heightPixels);
- touchRegion.op(inset, Region.Op.UNION);
- inset.set(mDisplayMetrics.widthPixels - gestureInsets.right, 0,
- mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels);
- touchRegion.op(inset, Region.Op.UNION);
- }
- return touchRegion;
- }
-
- private void startInputListening() {
- stopInputListening();
- mInputMonitor = new InputMonitorCompat("Screenshot", mDefaultDisplay);
- mInputEventReceiver = mInputMonitor.getInputReceiver(
- Looper.getMainLooper(), Choreographer.getInstance(), ev -> {
- if (ev instanceof MotionEvent) {
- MotionEvent event = (MotionEvent) ev;
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN
- && !getTouchRegion(false).contains(
- (int) event.getRawX(), (int) event.getRawY())) {
- mCallbacks.onTouchOutside();
- }
- }
- });
- }
-
- void stopInputListening() {
- if (mInputMonitor != null) {
- mInputMonitor.dispose();
- mInputMonitor = null;
- }
- if (mInputEventReceiver != null) {
- mInputEventReceiver.dispose();
- mInputEventReceiver = null;
- }
- }
-
- @Override // View
- protected void onFinishInflate() {
- super.onFinishInflate();
- mScrollingScrim = requireNonNull(findViewById(R.id.screenshot_scrolling_scrim));
- mScreenshotStatic = requireNonNull(findViewById(R.id.screenshot_static));
- mScreenshotPreview = requireNonNull(findViewById(R.id.screenshot_preview));
-
- mScreenshotPreviewBorder = requireNonNull(
- findViewById(R.id.screenshot_preview_border));
- mScreenshotPreview.setClipToOutline(true);
- mScreenshotBadge = requireNonNull(findViewById(R.id.screenshot_badge));
-
- mActionsContainerBackground = requireNonNull(findViewById(
- R.id.actions_container_background));
- mActionsContainer = requireNonNull(findViewById(R.id.actions_container));
- mActionsView = requireNonNull(findViewById(R.id.screenshot_actions));
- mDismissButton = requireNonNull(findViewById(R.id.screenshot_dismiss_button));
- mScrollablePreview = requireNonNull(findViewById(R.id.screenshot_scrollable_preview));
- mScreenshotFlash = requireNonNull(findViewById(R.id.screenshot_flash));
- mShareChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_share_chip));
- mEditChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_edit_chip));
- mScrollChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_scroll_chip));
-
- setFocusable(true);
- mActionsContainer.setScrollX(0);
-
- mNavMode = getResources().getInteger(
- com.android.internal.R.integer.config_navBarInteractionMode);
- mOrientationPortrait =
- getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT;
- mDirectionLTR =
- getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
-
- // Get focus so that the key events go to the layout.
- setFocusableInTouchMode(true);
- requestFocus();
-
- mScreenshotStatic.setCallbacks(new DraggableConstraintLayout.SwipeDismissCallbacks() {
- @Override
- public void onInteraction() {
- mCallbacks.onUserInteraction();
- }
-
- @Override
- public void onSwipeDismissInitiated(Animator animator) {
- if (DEBUG_DISMISS) {
- Log.d(ScreenshotView.TAG, "dismiss triggered via swipe gesture");
- }
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED, 0,
- mPackageName);
- }
-
- @Override
- public void onDismissComplete() {
- if (mInteractionJankMonitor.isInstrumenting(CUJ_TAKE_SCREENSHOT)) {
- mInteractionJankMonitor.end(CUJ_TAKE_SCREENSHOT);
- }
- mCallbacks.onDismiss();
- }
- });
- }
-
- View getScreenshotPreview() {
- return mScreenshotPreview;
- }
-
- void setUiEventLogger(UiEventLogger uiEventLogger) {
- mUiEventLogger = uiEventLogger;
- }
-
- void setCallbacks(ScreenshotViewCallback callbacks) {
- mCallbacks = callbacks;
- }
-
- void setFlags(FeatureFlags flags) {
- mFlags = flags;
- }
-
- void setScreenshot(Bitmap bitmap, Insets screenInsets) {
- mScreenshotPreview.setImageDrawable(createScreenDrawable(mResources, bitmap, screenInsets));
- }
-
- void setScreenshot(ScreenshotData screenshot) {
- mScreenshotData = screenshot;
- setScreenshot(screenshot.getBitmap(), screenshot.getInsets());
- mScreenshotPreview.setImageDrawable(createScreenDrawable(mResources, screenshot.getBitmap(),
- screenshot.getInsets()));
- }
-
- void setPackageName(String packageName) {
- mPackageName = packageName;
- }
-
- void setDefaultDisplay(int displayId) {
- mDefaultDisplay = displayId;
- }
-
- void updateInsets(WindowInsets insets) {
- int orientation = mContext.getResources().getConfiguration().orientation;
- mOrientationPortrait = (orientation == ORIENTATION_PORTRAIT);
- FrameLayout.LayoutParams p =
- (FrameLayout.LayoutParams) mScreenshotStatic.getLayoutParams();
- DisplayCutout cutout = insets.getDisplayCutout();
- Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars());
- if (cutout == null) {
- p.setMargins(0, 0, 0, navBarInsets.bottom);
- } else {
- Insets waterfall = cutout.getWaterfallInsets();
- if (mOrientationPortrait) {
- p.setMargins(
- waterfall.left,
- Math.max(cutout.getSafeInsetTop(), waterfall.top),
- waterfall.right,
- Math.max(cutout.getSafeInsetBottom(),
- Math.max(navBarInsets.bottom, waterfall.bottom)));
- } else {
- p.setMargins(
- Math.max(cutout.getSafeInsetLeft(), waterfall.left),
- waterfall.top,
- Math.max(cutout.getSafeInsetRight(), waterfall.right),
- Math.max(navBarInsets.bottom, waterfall.bottom));
- }
- }
- mScreenshotStatic.setLayoutParams(p);
- mScreenshotStatic.requestLayout();
- }
-
- void updateOrientation(WindowInsets insets) {
- int orientation = mContext.getResources().getConfiguration().orientation;
- mOrientationPortrait = (orientation == ORIENTATION_PORTRAIT);
- updateInsets(insets);
- ViewGroup.LayoutParams params = mScreenshotPreview.getLayoutParams();
- if (mOrientationPortrait) {
- params.width = (int) mFixedSize;
- params.height = LayoutParams.WRAP_CONTENT;
- mScreenshotPreview.setScaleType(ImageView.ScaleType.FIT_START);
- } else {
- params.width = LayoutParams.WRAP_CONTENT;
- params.height = (int) mFixedSize;
- mScreenshotPreview.setScaleType(ImageView.ScaleType.FIT_END);
- }
-
- mScreenshotPreview.setLayoutParams(params);
- }
-
- AnimatorSet createScreenshotDropInAnimation(Rect bounds, boolean showFlash) {
- if (DEBUG_ANIM) {
- Log.d(TAG, "createAnim: bounds=" + bounds + " showFlash=" + showFlash);
- }
-
- Rect targetPosition = new Rect();
- mScreenshotPreview.getHitRect(targetPosition);
-
- // ratio of preview width, end vs. start size
- float cornerScale =
- mFixedSize / (mOrientationPortrait ? bounds.width() : bounds.height());
- final float currentScale = 1 / cornerScale;
-
- AnimatorSet dropInAnimation = new AnimatorSet();
- ValueAnimator flashInAnimator = ValueAnimator.ofFloat(0, 1);
- flashInAnimator.setDuration(SCREENSHOT_FLASH_IN_DURATION_MS);
- flashInAnimator.setInterpolator(mFastOutSlowIn);
- flashInAnimator.addUpdateListener(animation ->
- mScreenshotFlash.setAlpha((float) animation.getAnimatedValue()));
-
- ValueAnimator flashOutAnimator = ValueAnimator.ofFloat(1, 0);
- flashOutAnimator.setDuration(SCREENSHOT_FLASH_OUT_DURATION_MS);
- flashOutAnimator.setInterpolator(mFastOutSlowIn);
- flashOutAnimator.addUpdateListener(animation ->
- mScreenshotFlash.setAlpha((float) animation.getAnimatedValue()));
-
- // animate from the current location, to the static preview location
- final PointF startPos = new PointF(bounds.centerX(), bounds.centerY());
- final PointF finalPos = new PointF(targetPosition.exactCenterX(),
- targetPosition.exactCenterY());
-
- // Shift to screen coordinates so that the animation runs on top of the entire screen,
- // including e.g. bars covering the display cutout.
- int[] locInScreen = mScreenshotPreview.getLocationOnScreen();
- startPos.offset(targetPosition.left - locInScreen[0], targetPosition.top - locInScreen[1]);
-
- if (DEBUG_ANIM) {
- Log.d(TAG, "toCorner: startPos=" + startPos);
- Log.d(TAG, "toCorner: finalPos=" + finalPos);
- }
-
- ValueAnimator toCorner = ValueAnimator.ofFloat(0, 1);
- toCorner.setDuration(SCREENSHOT_TO_CORNER_Y_DURATION_MS);
-
- toCorner.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- mScreenshotPreview.setScaleX(currentScale);
- mScreenshotPreview.setScaleY(currentScale);
- mScreenshotPreview.setVisibility(View.VISIBLE);
- if (mAccessibilityManager.isEnabled()) {
- mDismissButton.setAlpha(0);
- mDismissButton.setVisibility(View.VISIBLE);
- }
- }
- });
-
- float xPositionPct =
- SCREENSHOT_TO_CORNER_X_DURATION_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
- float dismissPct =
- SCREENSHOT_TO_CORNER_DISMISS_DELAY_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
- float scalePct =
- SCREENSHOT_TO_CORNER_SCALE_DURATION_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
- toCorner.addUpdateListener(animation -> {
- float t = animation.getAnimatedFraction();
- if (t < scalePct) {
- float scale = MathUtils.lerp(
- currentScale, 1, mFastOutSlowIn.getInterpolation(t / scalePct));
- mScreenshotPreview.setScaleX(scale);
- mScreenshotPreview.setScaleY(scale);
- } else {
- mScreenshotPreview.setScaleX(1);
- mScreenshotPreview.setScaleY(1);
- }
-
- if (t < xPositionPct) {
- float xCenter = MathUtils.lerp(startPos.x, finalPos.x,
- mFastOutSlowIn.getInterpolation(t / xPositionPct));
- mScreenshotPreview.setX(xCenter - mScreenshotPreview.getWidth() / 2f);
- } else {
- mScreenshotPreview.setX(finalPos.x - mScreenshotPreview.getWidth() / 2f);
- }
- float yCenter = MathUtils.lerp(
- startPos.y, finalPos.y, mFastOutSlowIn.getInterpolation(t));
- mScreenshotPreview.setY(yCenter - mScreenshotPreview.getHeight() / 2f);
-
- if (t >= dismissPct) {
- mDismissButton.setAlpha((t - dismissPct) / (1 - dismissPct));
- float currentX = mScreenshotPreview.getX();
- float currentY = mScreenshotPreview.getY();
- mDismissButton.setY(currentY - mDismissButton.getHeight() / 2f);
- if (mDirectionLTR) {
- mDismissButton.setX(currentX + mScreenshotPreview.getWidth()
- - mDismissButton.getWidth() / 2f);
- } else {
- mDismissButton.setX(currentX - mDismissButton.getWidth() / 2f);
- }
- }
- });
-
- mScreenshotFlash.setAlpha(0f);
- mScreenshotFlash.setVisibility(View.VISIBLE);
-
- ValueAnimator borderFadeIn = ValueAnimator.ofFloat(0, 1);
- borderFadeIn.setDuration(100);
- borderFadeIn.addUpdateListener((animation) -> {
- float borderAlpha = animation.getAnimatedFraction();
- mScreenshotPreviewBorder.setAlpha(borderAlpha);
- mScreenshotBadge.setAlpha(borderAlpha);
- });
-
- if (showFlash) {
- dropInAnimation.play(flashOutAnimator).after(flashInAnimator);
- dropInAnimation.play(flashOutAnimator).with(toCorner);
- } else {
- dropInAnimation.play(toCorner);
- }
- dropInAnimation.play(borderFadeIn).after(toCorner);
-
- dropInAnimation.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationCancel(Animator animation) {
- mInteractionJankMonitor.cancel(CUJ_TAKE_SCREENSHOT);
- }
-
- @Override
- public void onAnimationStart(Animator animation) {
- InteractionJankMonitor.Configuration.Builder builder =
- InteractionJankMonitor.Configuration.Builder.withView(
- CUJ_TAKE_SCREENSHOT, mScreenshotPreview)
- .setTag("DropIn");
- mInteractionJankMonitor.begin(builder);
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- if (DEBUG_ANIM) {
- Log.d(TAG, "drop-in animation ended");
- }
- mDismissButton.setOnClickListener(view -> {
- if (DEBUG_INPUT) {
- Log.d(TAG, "dismiss button clicked");
- }
- mUiEventLogger.log(
- ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL, 0, mPackageName);
- animateDismissal();
- });
- mDismissButton.setAlpha(1);
- float dismissOffset = mDismissButton.getWidth() / 2f;
- float finalDismissX = mDirectionLTR
- ? finalPos.x - dismissOffset + bounds.width() * cornerScale / 2f
- : finalPos.x - dismissOffset - bounds.width() * cornerScale / 2f;
- mDismissButton.setX(finalDismissX);
- mDismissButton.setY(
- finalPos.y - dismissOffset - bounds.height() * cornerScale / 2f);
- mScreenshotPreview.setScaleX(1);
- mScreenshotPreview.setScaleY(1);
- mScreenshotPreview.setX(finalPos.x - mScreenshotPreview.getWidth() / 2f);
- mScreenshotPreview.setY(finalPos.y - mScreenshotPreview.getHeight() / 2f);
- requestLayout();
- mInteractionJankMonitor.end(CUJ_TAKE_SCREENSHOT);
- createScreenshotActionsShadeAnimation().start();
- }
- });
-
- return dropInAnimation;
- }
-
- ValueAnimator createScreenshotActionsShadeAnimation() {
- // By default the activities won't be able to start immediately; override this to keep
- // the same behavior as if started from a notification
- try {
- ActivityManager.getService().resumeAppSwitches();
- } catch (RemoteException e) {
- }
-
- ArrayList<OverlayActionChip> chips = new ArrayList<>();
-
- mShareChip.setContentDescription(mContext.getString(R.string.screenshot_share_description));
- mShareChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_share), true);
- mShareChip.setOnClickListener(v -> {
- mShareChip.setIsPending(true);
- mEditChip.setIsPending(false);
- if (mQuickShareChip != null) {
- mQuickShareChip.setIsPending(false);
- }
- mPendingInteraction = PendingInteraction.SHARE;
- });
- chips.add(mShareChip);
-
- mEditChip.setContentDescription(
- mContext.getString(R.string.screenshot_edit_description));
- mEditChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_edit),
- true);
- mEditChip.setOnClickListener(v -> {
- mEditChip.setIsPending(true);
- mShareChip.setIsPending(false);
- if (mQuickShareChip != null) {
- mQuickShareChip.setIsPending(false);
- }
- mPendingInteraction = PendingInteraction.EDIT;
- });
- chips.add(mEditChip);
-
- mScreenshotPreview.setOnClickListener(v -> {
- mShareChip.setIsPending(false);
- mEditChip.setIsPending(false);
- if (mQuickShareChip != null) {
- mQuickShareChip.setIsPending(false);
- }
- mPendingInteraction = PendingInteraction.PREVIEW;
- });
-
- mScrollChip.setText(mContext.getString(R.string.screenshot_scroll_label));
- mScrollChip.setIcon(Icon.createWithResource(mContext,
- R.drawable.ic_screenshot_scroll), true);
- chips.add(mScrollChip);
-
- // remove the margin from the last chip so that it's correctly aligned with the end
- LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)
- mActionsView.getChildAt(0).getLayoutParams();
- params.setMarginEnd(0);
- mActionsView.getChildAt(0).setLayoutParams(params);
-
- ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
- animator.setDuration(SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS);
- float alphaFraction = (float) SCREENSHOT_ACTIONS_ALPHA_DURATION_MS
- / SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS;
- mActionsContainer.setAlpha(0f);
- mActionsContainerBackground.setAlpha(0f);
- mActionsContainer.setVisibility(View.VISIBLE);
- mActionsContainerBackground.setVisibility(View.VISIBLE);
-
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationCancel(Animator animation) {
- mInteractionJankMonitor.cancel(CUJ_TAKE_SCREENSHOT);
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- mInteractionJankMonitor.end(CUJ_TAKE_SCREENSHOT);
- }
-
- @Override
- public void onAnimationStart(Animator animation) {
- InteractionJankMonitor.Configuration.Builder builder =
- InteractionJankMonitor.Configuration.Builder.withView(
- CUJ_TAKE_SCREENSHOT, mScreenshotStatic)
- .setTag("Actions")
- .setTimeout(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS);
- mInteractionJankMonitor.begin(builder);
- }
- });
-
- animator.addUpdateListener(animation -> {
- float t = animation.getAnimatedFraction();
- float containerAlpha = t < alphaFraction ? t / alphaFraction : 1;
- mActionsContainer.setAlpha(containerAlpha);
- mActionsContainerBackground.setAlpha(containerAlpha);
- float containerScale = SCREENSHOT_ACTIONS_START_SCALE_X
- + (t * (1 - SCREENSHOT_ACTIONS_START_SCALE_X));
- mActionsContainer.setScaleX(containerScale);
- mActionsContainerBackground.setScaleX(containerScale);
- for (OverlayActionChip chip : chips) {
- chip.setAlpha(t);
- chip.setScaleX(1 / containerScale); // invert to keep size of children constant
- }
- mActionsContainer.setScrollX(mDirectionLTR ? 0 : mActionsContainer.getWidth());
- mActionsContainer.setPivotX(mDirectionLTR ? 0 : mActionsContainer.getWidth());
- mActionsContainerBackground.setPivotX(
- mDirectionLTR ? 0 : mActionsContainerBackground.getWidth());
- });
- return animator;
- }
-
- void badgeScreenshot(@Nullable Drawable badge) {
- mScreenshotBadge.setImageDrawable(badge);
- mScreenshotBadge.setVisibility(badge != null ? View.VISIBLE : View.GONE);
- }
-
- void setChipIntents(ScreenshotController.SavedImageData imageData) {
- mShareChip.setOnClickListener(v -> {
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED, 0, mPackageName);
- prepareSharedTransition();
-
- Intent shareIntent = ActionIntentCreator.INSTANCE.createShareWithSubject(
- imageData.uri, imageData.subject);
- mCallbacks.onAction(shareIntent, imageData.owner, false);
-
- });
- mEditChip.setOnClickListener(v -> {
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED, 0, mPackageName);
- prepareSharedTransition();
- mCallbacks.onAction(
- ActionIntentCreator.INSTANCE.createEdit(imageData.uri, mContext),
- imageData.owner, true);
- });
- mScreenshotPreview.setOnClickListener(v -> {
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED, 0, mPackageName);
- prepareSharedTransition();
- mCallbacks.onAction(
- ActionIntentCreator.INSTANCE.createEdit(imageData.uri, mContext),
- imageData.owner, true);
- });
- if (mQuickShareChip != null) {
- if (imageData.quickShareAction != null) {
- mQuickShareChip.setPendingIntent(imageData.quickShareAction.actionIntent,
- () -> {
- mUiEventLogger.log(
- ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED, 0,
- mPackageName);
- animateDismissal();
- });
- } else {
- // hide chip and unset pending interaction if necessary, since we don't actually
- // have a useable quick share intent
- Log.wtf(TAG, "Showed quick share chip, but quick share intent was null");
- if (mPendingInteraction == PendingInteraction.QUICK_SHARE) {
- mPendingInteraction = null;
- }
- mQuickShareChip.setVisibility(GONE);
- }
- }
-
- if (mPendingInteraction != null) {
- switch (mPendingInteraction) {
- case PREVIEW:
- mScreenshotPreview.callOnClick();
- break;
- case SHARE:
- mShareChip.callOnClick();
- break;
- case EDIT:
- mEditChip.callOnClick();
- break;
- case QUICK_SHARE:
- mQuickShareChip.callOnClick();
- break;
- }
- } else {
- LayoutInflater inflater = LayoutInflater.from(mContext);
-
- for (Notification.Action smartAction : imageData.smartActions) {
- OverlayActionChip actionChip = (OverlayActionChip) inflater.inflate(
- R.layout.overlay_action_chip, mActionsView, false);
- actionChip.setText(smartAction.title);
- actionChip.setIcon(smartAction.getIcon(), false);
- actionChip.setPendingIntent(smartAction.actionIntent,
- () -> {
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED,
- 0, mPackageName);
- animateDismissal();
- });
- actionChip.setAlpha(1);
- mActionsView.addView(actionChip, mActionsView.getChildCount() - 1);
- mSmartChips.add(actionChip);
- }
- }
- }
-
- void addQuickShareChip(Notification.Action quickShareAction) {
- if (mQuickShareChip != null) {
- mSmartChips.remove(mQuickShareChip);
- mActionsView.removeView(mQuickShareChip);
- }
- if (mPendingInteraction == PendingInteraction.QUICK_SHARE) {
- mPendingInteraction = null;
- }
- if (mPendingInteraction == null) {
- LayoutInflater inflater = LayoutInflater.from(mContext);
- mQuickShareChip = (OverlayActionChip) inflater.inflate(
- R.layout.overlay_action_chip, mActionsView, false);
- mQuickShareChip.setText(quickShareAction.title);
- mQuickShareChip.setIcon(quickShareAction.getIcon(), false);
- mQuickShareChip.setOnClickListener(v -> {
- mShareChip.setIsPending(false);
- mEditChip.setIsPending(false);
- mQuickShareChip.setIsPending(true);
- mPendingInteraction = PendingInteraction.QUICK_SHARE;
- });
- mQuickShareChip.setAlpha(1);
- mActionsView.addView(mQuickShareChip);
- mSmartChips.add(mQuickShareChip);
- }
- }
-
- private Rect scrollableAreaOnScreen(ScrollCaptureResponse response) {
- Rect r = new Rect(response.getBoundsInWindow());
- Rect windowInScreen = response.getWindowBounds();
- r.offset(windowInScreen.left, windowInScreen.top);
- r.intersect(new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
- return r;
- }
-
- void startLongScreenshotTransition(Rect destination, Runnable onTransitionEnd,
- ScrollCaptureController.LongScreenshot longScreenshot) {
- mPendingSharedTransition = true;
- AnimatorSet animSet = new AnimatorSet();
-
- ValueAnimator scrimAnim = ValueAnimator.ofFloat(0, 1);
- scrimAnim.addUpdateListener(animation ->
- mScrollingScrim.setAlpha(1 - animation.getAnimatedFraction()));
-
- if (mShowScrollablePreview) {
- mScrollablePreview.setImageBitmap(longScreenshot.toBitmap());
- float startX = mScrollablePreview.getX();
- float startY = mScrollablePreview.getY();
- int[] locInScreen = mScrollablePreview.getLocationOnScreen();
- destination.offset((int) startX - locInScreen[0], (int) startY - locInScreen[1]);
- mScrollablePreview.setPivotX(0);
- mScrollablePreview.setPivotY(0);
- mScrollablePreview.setAlpha(1f);
- float currentScale = mScrollablePreview.getWidth() / (float) longScreenshot.getWidth();
- Matrix matrix = new Matrix();
- matrix.setScale(currentScale, currentScale);
- matrix.postTranslate(
- longScreenshot.getLeft() * currentScale,
- longScreenshot.getTop() * currentScale);
- mScrollablePreview.setImageMatrix(matrix);
- float destinationScale = destination.width() / (float) mScrollablePreview.getWidth();
-
- ValueAnimator previewAnim = ValueAnimator.ofFloat(0, 1);
- previewAnim.addUpdateListener(animation -> {
- float t = animation.getAnimatedFraction();
- float currScale = MathUtils.lerp(1, destinationScale, t);
- mScrollablePreview.setScaleX(currScale);
- mScrollablePreview.setScaleY(currScale);
- mScrollablePreview.setX(MathUtils.lerp(startX, destination.left, t));
- mScrollablePreview.setY(MathUtils.lerp(startY, destination.top, t));
- });
- ValueAnimator previewFadeAnim = ValueAnimator.ofFloat(1, 0);
- previewFadeAnim.addUpdateListener(animation ->
- mScrollablePreview.setAlpha(1 - animation.getAnimatedFraction()));
- animSet.play(previewAnim).with(scrimAnim).before(previewFadeAnim);
- previewAnim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- onTransitionEnd.run();
- }
- });
- } else {
- // if we switched orientations between the original screenshot and the long screenshot
- // capture, just fade out the scrim instead of running the preview animation
- animSet.play(scrimAnim);
- animSet.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- onTransitionEnd.run();
- }
- });
- }
- animSet.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- mCallbacks.onDismiss();
- }
- });
- animSet.start();
- }
-
- void prepareScrollingTransition(ScrollCaptureResponse response, Bitmap screenBitmap,
- Bitmap newBitmap, boolean screenshotTakenInPortrait) {
- mShowScrollablePreview = (screenshotTakenInPortrait == mOrientationPortrait);
-
- mScrollingScrim.setImageBitmap(newBitmap);
- mScrollingScrim.setVisibility(View.VISIBLE);
-
- if (mShowScrollablePreview) {
- Rect scrollableArea = scrollableAreaOnScreen(response);
-
- float scale = mFixedSize
- / (mOrientationPortrait ? screenBitmap.getWidth() : screenBitmap.getHeight());
- ConstraintLayout.LayoutParams params =
- (ConstraintLayout.LayoutParams) mScrollablePreview.getLayoutParams();
-
- params.width = (int) (scale * scrollableArea.width());
- params.height = (int) (scale * scrollableArea.height());
- Matrix matrix = new Matrix();
- matrix.setScale(scale, scale);
- matrix.postTranslate(-scrollableArea.left * scale, -scrollableArea.top * scale);
-
- mScrollablePreview.setTranslationX(scale
- * (mDirectionLTR ? scrollableArea.left : scrollableArea.right - getWidth()));
- mScrollablePreview.setTranslationY(scale * scrollableArea.top);
- mScrollablePreview.setImageMatrix(matrix);
- mScrollablePreview.setImageBitmap(screenBitmap);
- mScrollablePreview.setVisibility(View.VISIBLE);
- }
- mDismissButton.setVisibility(View.GONE);
- mActionsContainer.setVisibility(View.GONE);
- // set these invisible, but not gone, so that the views are laid out correctly
- mActionsContainerBackground.setVisibility(View.INVISIBLE);
- mScreenshotPreviewBorder.setVisibility(View.INVISIBLE);
- mScreenshotPreview.setVisibility(View.INVISIBLE);
- mScrollingScrim.setImageTintBlendMode(BlendMode.SRC_ATOP);
- ValueAnimator anim = ValueAnimator.ofFloat(0, .3f);
- anim.addUpdateListener(animation -> mScrollingScrim.setImageTintList(
- ColorStateList.valueOf(Color.argb((float) animation.getAnimatedValue(), 0, 0, 0))));
- anim.setDuration(200);
- anim.start();
- }
-
- void restoreNonScrollingUi() {
- mScrollChip.setVisibility(View.GONE);
- mScrollablePreview.setVisibility(View.GONE);
- mScrollingScrim.setVisibility(View.GONE);
-
- if (mAccessibilityManager.isEnabled()) {
- mDismissButton.setVisibility(View.VISIBLE);
- }
- mActionsContainer.setVisibility(View.VISIBLE);
- mActionsContainerBackground.setVisibility(View.VISIBLE);
- mScreenshotPreviewBorder.setVisibility(View.VISIBLE);
- mScreenshotPreview.setVisibility(View.VISIBLE);
- // reset the timeout
- mCallbacks.onUserInteraction();
- }
-
- boolean isDismissing() {
- return mScreenshotStatic.isDismissing();
- }
-
- boolean isPendingSharedTransition() {
- return mPendingSharedTransition;
- }
-
- void animateDismissal() {
- mScreenshotStatic.dismiss();
- }
-
- void reset() {
- if (DEBUG_UI) {
- Log.d(TAG, "reset screenshot view");
- }
- mScreenshotStatic.cancelDismissal();
- if (DEBUG_WINDOW) {
- Log.d(TAG, "removing OnComputeInternalInsetsListener");
- }
- // Make sure we clean up the view tree observer
- getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
- // Clear any references to the bitmap
- mScreenshotPreview.setImageDrawable(null);
- mScreenshotPreview.setVisibility(View.INVISIBLE);
- mScreenshotPreview.setAlpha(1f);
- mScreenshotPreviewBorder.setAlpha(0);
- mScreenshotBadge.setAlpha(0f);
- mScreenshotBadge.setVisibility(View.GONE);
- mScreenshotBadge.setImageDrawable(null);
- mPendingSharedTransition = false;
- mActionsContainerBackground.setVisibility(View.INVISIBLE);
- mActionsContainer.setVisibility(View.GONE);
- mDismissButton.setVisibility(View.GONE);
- mScrollingScrim.setVisibility(View.GONE);
- mScrollablePreview.setVisibility(View.GONE);
- mScreenshotStatic.setTranslationX(0);
- mScreenshotPreview.setContentDescription(
- mContext.getResources().getString(R.string.screenshot_preview_description));
- mScreenshotPreview.setOnClickListener(null);
- mShareChip.setOnClickListener(null);
- mScrollingScrim.setVisibility(View.GONE);
- mEditChip.setOnClickListener(null);
- mShareChip.setIsPending(false);
- mEditChip.setIsPending(false);
- mPendingInteraction = null;
- for (OverlayActionChip chip : mSmartChips) {
- mActionsView.removeView(chip);
- }
- mSmartChips.clear();
- mQuickShareChip = null;
- setAlpha(1);
- mScreenshotStatic.setAlpha(1);
- mScreenshotData = null;
- }
-
- private void prepareSharedTransition() {
- mPendingSharedTransition = true;
- // fade out non-preview UI
- createScreenshotFadeDismissAnimation().start();
- }
-
- ValueAnimator createScreenshotFadeDismissAnimation() {
- ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
- alphaAnim.addUpdateListener(animation -> {
- float alpha = 1 - animation.getAnimatedFraction();
- mDismissButton.setAlpha(alpha);
- mActionsContainerBackground.setAlpha(alpha);
- mActionsContainer.setAlpha(alpha);
- mScreenshotPreviewBorder.setAlpha(alpha);
- mScreenshotBadge.setAlpha(alpha);
- });
- alphaAnim.setDuration(600);
- return alphaAnim;
- }
-
- /**
- * Create a drawable using the size of the bitmap and insets as the fractional inset parameters.
- */
- private static Drawable createScreenDrawable(Resources res, Bitmap bitmap, Insets insets) {
- int insettedWidth = bitmap.getWidth() - insets.left - insets.right;
- int insettedHeight = bitmap.getHeight() - insets.top - insets.bottom;
-
- BitmapDrawable bitmapDrawable = new BitmapDrawable(res, bitmap);
- if (insettedHeight == 0 || insettedWidth == 0 || bitmap.getWidth() == 0
- || bitmap.getHeight() == 0) {
- Log.e(TAG, "Can't create inset drawable, using 0 insets bitmap and insets create "
- + "degenerate region: " + bitmap.getWidth() + "x" + bitmap.getHeight() + " "
- + bitmapDrawable);
- return bitmapDrawable;
- }
-
- InsetDrawable insetDrawable = new InsetDrawable(bitmapDrawable,
- -1f * insets.left / insettedWidth,
- -1f * insets.top / insettedHeight,
- -1f * insets.right / insettedWidth,
- -1f * insets.bottom / insettedHeight);
-
- if (insets.left < 0 || insets.top < 0 || insets.right < 0 || insets.bottom < 0) {
- // Are any of the insets negative, meaning the bitmap is smaller than the bounds so need
- // to fill in the background of the drawable.
- return new LayerDrawable(new Drawable[]{
- new ColorDrawable(Color.BLACK), insetDrawable});
- } else {
- return insetDrawable;
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
deleted file mode 100644
index df93a5e..0000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.screenshot
-
-import android.animation.Animator
-import android.app.Notification
-import android.content.Context
-import android.graphics.Bitmap
-import android.graphics.Rect
-import android.view.ScrollCaptureResponse
-import android.view.View
-import android.view.ViewGroup
-import android.view.WindowInsets
-import com.android.systemui.screenshot.scroll.ScrollCaptureController
-
-/** Abstraction of the surface between ScreenshotController and ScreenshotView */
-interface ScreenshotViewProxy {
- val view: ViewGroup
- val screenshotPreview: View
-
- var packageName: String
- var callbacks: ScreenshotView.ScreenshotViewCallback?
- var screenshot: ScreenshotData?
-
- val isAttachedToWindow: Boolean
- val isDismissing: Boolean
- val isPendingSharedTransition: Boolean
-
- fun reset()
- fun updateInsets(insets: WindowInsets)
- fun updateOrientation(insets: WindowInsets)
- fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator
- fun addQuickShareChip(quickShareAction: Notification.Action)
- fun setChipIntents(imageData: ScreenshotController.SavedImageData)
- fun requestDismissal(event: ScreenshotEvent?)
-
- fun showScrollChip(packageName: String, onClick: Runnable)
- fun hideScrollChip()
- fun prepareScrollingTransition(
- response: ScrollCaptureResponse,
- screenBitmap: Bitmap,
- newScreenshot: Bitmap,
- screenshotTakenInPortrait: Boolean,
- onTransitionPrepared: Runnable,
- )
- fun startLongScreenshotTransition(
- transitionDestination: Rect,
- onTransitionEnd: Runnable,
- longScreenshot: ScrollCaptureController.LongScreenshot
- )
- fun restoreNonScrollingUi()
- fun fadeForSharedTransition()
-
- fun stopInputListening()
- fun requestFocus()
- fun announceForAccessibility(string: String)
- fun prepareEntranceAnimation(runnable: Runnable)
-
- interface Factory {
- fun getProxy(context: Context, displayId: Int): ScreenshotViewProxy
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index 56ba1af4..682f848 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -24,12 +24,10 @@
import com.android.systemui.screenshot.ImageCaptureImpl;
import com.android.systemui.screenshot.ScreenshotPolicy;
import com.android.systemui.screenshot.ScreenshotPolicyImpl;
-import com.android.systemui.screenshot.ScreenshotShelfViewProxy;
import com.android.systemui.screenshot.ScreenshotSoundController;
import com.android.systemui.screenshot.ScreenshotSoundControllerImpl;
import com.android.systemui.screenshot.ScreenshotSoundProvider;
import com.android.systemui.screenshot.ScreenshotSoundProviderImpl;
-import com.android.systemui.screenshot.ScreenshotViewProxy;
import com.android.systemui.screenshot.TakeScreenshotExecutor;
import com.android.systemui.screenshot.TakeScreenshotExecutorImpl;
import com.android.systemui.screenshot.TakeScreenshotService;
@@ -92,8 +90,4 @@
AccessibilityManager accessibilityManager) {
return new ScreenshotViewModel(accessibilityManager);
}
-
- @Binds
- abstract ScreenshotViewProxy.Factory bindScreenshotViewProxyFactory(
- ScreenshotShelfViewProxy.Factory shelfScreenshotViewProxyFactory);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt
index 7094848..e60848b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt
@@ -120,11 +120,26 @@
private fun setUpDatabase(): List<FakeWidgetMetadata> {
return listOf(
- FakeWidgetMetadata(11, "com.android.fakePackage1/fakeWidget1", 3),
- FakeWidgetMetadata(12, "com.android.fakePackage2/fakeWidget2", 2),
- FakeWidgetMetadata(13, "com.android.fakePackage3/fakeWidget3", 1),
+ FakeWidgetMetadata(
+ widgetId = 11,
+ componentName = "com.android.fakePackage1/fakeWidget1",
+ rank = 3,
+ userSerialNumber = 0,
+ ),
+ FakeWidgetMetadata(
+ widgetId = 12,
+ componentName = "com.android.fakePackage2/fakeWidget2",
+ rank = 2,
+ userSerialNumber = 0,
+ ),
+ FakeWidgetMetadata(
+ widgetId = 13,
+ componentName = "com.android.fakePackage3/fakeWidget3",
+ rank = 1,
+ userSerialNumber = 10,
+ ),
)
- .onEach { dao.addWidget(it.widgetId, it.componentName, it.rank) }
+ .onEach { dao.addWidget(it.widgetId, it.componentName, it.rank, it.userSerialNumber) }
}
private fun getBackupDataInputStream(): BackupDataInputStream {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
index cde7a0e..983a435 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
@@ -66,11 +66,28 @@
// Set up database
val expectedWidgets =
listOf(
- FakeWidgetMetadata(11, "com.android.fakePackage1/fakeWidget1", 3),
- FakeWidgetMetadata(12, "com.android.fakePackage2/fakeWidget2", 2),
- FakeWidgetMetadata(13, "com.android.fakePackage3/fakeWidget3", 1),
+ FakeWidgetMetadata(
+ widgetId = 11,
+ componentName = "com.android.fakePackage1/fakeWidget1",
+ rank = 3,
+ userSerialNumber = 0,
+ ),
+ FakeWidgetMetadata(
+ widgetId = 12,
+ componentName = "com.android.fakePackage2/fakeWidget2",
+ rank = 2,
+ userSerialNumber = 0,
+ ),
+ FakeWidgetMetadata(
+ widgetId = 13,
+ componentName = "com.android.fakePackage3/fakeWidget3",
+ rank = 1,
+ userSerialNumber = 10,
+ ),
)
- expectedWidgets.forEach { dao.addWidget(it.widgetId, it.componentName, it.rank) }
+ expectedWidgets.forEach {
+ dao.addWidget(it.widgetId, it.componentName, it.rank, it.userSerialNumber)
+ }
// Get communal hub state
val state = underTest.getCommunalHubState()
@@ -128,7 +145,12 @@
assertThat(underTest.fileExists()).isFalse()
}
- data class FakeWidgetMetadata(val widgetId: Int, val componentName: String, val rank: Int)
+ data class FakeWidgetMetadata(
+ val widgetId: Int,
+ val componentName: String,
+ val rank: Int,
+ val userSerialNumber: Int,
+ )
companion object {
/**
@@ -140,7 +162,8 @@
{ actual, expected ->
actual?.widgetId == expected?.widgetId &&
actual?.componentName == expected?.componentName &&
- actual?.rank == expected?.rank
+ actual?.rank == expected?.rank &&
+ actual?.userSerialNumber == expected?.userSerialNumber
},
"represents",
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt
new file mode 100644
index 0000000..eb0ab78
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt
@@ -0,0 +1,149 @@
+/*
+ * 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.communal.data.db
+
+import androidx.room.testing.MigrationTestHelper
+import androidx.sqlite.db.SupportSQLiteDatabase
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.lifecycle.InstantTaskExecutorRule
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalDatabaseMigrationsTest : SysuiTestCase() {
+
+ @JvmField @Rule val instantTaskExecutor = InstantTaskExecutorRule()
+
+ @get:Rule
+ val migrationTestHelper =
+ MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ CommunalDatabase::class.java.canonicalName,
+ )
+
+ @Test
+ fun migrate1To2() {
+ // Create a communal database in version 1
+ val databaseV1 = migrationTestHelper.createDatabase(DATABASE_NAME, version = 1)
+
+ // Populate some fake data
+ val fakeWidgetsV1 =
+ listOf(
+ FakeCommunalWidgetItemV1(1, "test_widget_1", 11),
+ FakeCommunalWidgetItemV1(2, "test_widget_2", 12),
+ FakeCommunalWidgetItemV1(3, "test_widget_3", 13),
+ )
+ databaseV1.insertWidgetsV1(fakeWidgetsV1)
+
+ // Verify fake widgets populated
+ databaseV1.verifyWidgetsV1(fakeWidgetsV1)
+
+ // Run migration and get database V2, the migration test helper verifies that the schema is
+ // updated correctly
+ val databaseV2 =
+ migrationTestHelper.runMigrationsAndValidate(
+ name = DATABASE_NAME,
+ version = 2,
+ validateDroppedTables = false,
+ CommunalDatabase.MIGRATION_1_2,
+ )
+
+ // Verify data is migrated correctly
+ databaseV2.verifyWidgetsV2(fakeWidgetsV1.map { it.getV2() })
+ }
+
+ private fun SupportSQLiteDatabase.insertWidgetsV1(widgets: List<FakeCommunalWidgetItemV1>) {
+ widgets.forEach { widget ->
+ execSQL(
+ "INSERT INTO communal_widget_table(widget_id, component_name, item_id) " +
+ "VALUES(${widget.widgetId}, '${widget.componentName}', ${widget.itemId})"
+ )
+ }
+ }
+
+ private fun SupportSQLiteDatabase.verifyWidgetsV1(widgets: List<FakeCommunalWidgetItemV1>) {
+ val cursor = query("SELECT * FROM communal_widget_table")
+ assertThat(cursor.moveToFirst()).isTrue()
+
+ widgets.forEach { widget ->
+ assertThat(cursor.getInt(cursor.getColumnIndex("widget_id"))).isEqualTo(widget.widgetId)
+ assertThat(cursor.getString(cursor.getColumnIndex("component_name")))
+ .isEqualTo(widget.componentName)
+ assertThat(cursor.getInt(cursor.getColumnIndex("item_id"))).isEqualTo(widget.itemId)
+
+ cursor.moveToNext()
+ }
+
+ // Verify there is no more columns
+ assertThat(cursor.isAfterLast).isTrue()
+ }
+
+ private fun SupportSQLiteDatabase.verifyWidgetsV2(widgets: List<FakeCommunalWidgetItemV2>) {
+ val cursor = query("SELECT * FROM communal_widget_table")
+ assertThat(cursor.moveToFirst()).isTrue()
+
+ widgets.forEach { widget ->
+ assertThat(cursor.getInt(cursor.getColumnIndex("widget_id"))).isEqualTo(widget.widgetId)
+ assertThat(cursor.getString(cursor.getColumnIndex("component_name")))
+ .isEqualTo(widget.componentName)
+ assertThat(cursor.getInt(cursor.getColumnIndex("item_id"))).isEqualTo(widget.itemId)
+ assertThat(cursor.getInt(cursor.getColumnIndex("user_serial_number")))
+ .isEqualTo(widget.userSerialNumber)
+
+ cursor.moveToNext()
+ }
+
+ // Verify there is no more columns
+ assertThat(cursor.isAfterLast).isTrue()
+ }
+
+ /**
+ * Returns the expected data after migration from V1 to V2, which is simply that the new user
+ * serial number field is now set to [CommunalWidgetItem.USER_SERIAL_NUMBER_UNDEFINED].
+ */
+ private fun FakeCommunalWidgetItemV1.getV2(): FakeCommunalWidgetItemV2 {
+ return FakeCommunalWidgetItemV2(
+ widgetId,
+ componentName,
+ itemId,
+ CommunalWidgetItem.USER_SERIAL_NUMBER_UNDEFINED,
+ )
+ }
+
+ private data class FakeCommunalWidgetItemV1(
+ val widgetId: Int,
+ val componentName: String,
+ val itemId: Int,
+ )
+
+ private data class FakeCommunalWidgetItemV2(
+ val widgetId: Int,
+ val componentName: String,
+ val itemId: Int,
+ val userSerialNumber: Int,
+ )
+
+ companion object {
+ private const val DATABASE_NAME = "communal_db"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
index f77c7a6..d670508 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
@@ -67,11 +67,12 @@
@Test
fun addWidget_readValueInDb() =
testScope.runTest {
- val (widgetId, provider, priority) = widgetInfo1
+ val (widgetId, provider, priority, userSerialNumber) = widgetInfo1
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
priority = priority,
+ userSerialNumber = userSerialNumber,
)
val entry = communalWidgetDao.getWidgetByIdNow(id = 1)
assertThat(entry).isEqualTo(communalWidgetItemEntry1)
@@ -80,11 +81,12 @@
@Test
fun deleteWidget_notInDb_returnsFalse() =
testScope.runTest {
- val (widgetId, provider, priority) = widgetInfo1
+ val (widgetId, provider, priority, userSerialNumber) = widgetInfo1
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
priority = priority,
+ userSerialNumber = userSerialNumber,
)
assertThat(communalWidgetDao.deleteWidgetById(widgetId = 123)).isFalse()
}
@@ -95,11 +97,12 @@
val widgetsToAdd = listOf(widgetInfo1, widgetInfo2)
val widgets = collectLastValue(communalWidgetDao.getWidgets())
widgetsToAdd.forEach {
- val (widgetId, provider, priority) = it
+ val (widgetId, provider, priority, userSerialNumber) = it
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
priority = priority,
+ userSerialNumber = userSerialNumber
)
}
assertThat(widgets())
@@ -118,11 +121,12 @@
val widgets = collectLastValue(communalWidgetDao.getWidgets())
widgetsToAdd.forEach {
- val (widgetId, provider, priority) = it
+ val (widgetId, provider, priority, userSerialNumber) = it
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
priority = priority,
+ userSerialNumber = userSerialNumber,
)
}
assertThat(widgets())
@@ -144,11 +148,12 @@
val widgets = collectLastValue(communalWidgetDao.getWidgets())
widgetsToAdd.forEach {
- val (widgetId, provider, priority) = it
+ val (widgetId, provider, priority, userSerialNumber) = it
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
priority = priority,
+ userSerialNumber = userSerialNumber,
)
}
assertThat(widgets())
@@ -180,11 +185,12 @@
val widgets = collectLastValue(communalWidgetDao.getWidgets())
existingWidgets.forEach {
- val (widgetId, provider, priority) = it
+ val (widgetId, provider, priority, userSerialNumber) = it
communalWidgetDao.addWidget(
widgetId = widgetId,
provider = provider,
priority = priority,
+ userSerialNumber = userSerialNumber,
)
}
assertThat(widgets())
@@ -212,6 +218,7 @@
widgetId = widgetInfo3.widgetId,
provider = widgetInfo3.provider,
priority = 2,
+ userSerialNumber = widgetInfo3.userSerialNumber,
)
assertThat(widgets())
.containsExactly(
@@ -246,6 +253,7 @@
widgetId = fakeWidget.widgetId,
componentName = fakeWidget.componentName,
itemId = rank.uid,
+ userSerialNumber = fakeWidget.userSerialNumber,
)
expected[rank] = widget
}
@@ -258,13 +266,15 @@
widgetId = metadata.widgetId,
provider = metadata.provider,
priority = priority ?: metadata.priority,
+ userSerialNumber = metadata.userSerialNumber,
)
}
data class FakeWidgetMetadata(
val widgetId: Int,
val provider: ComponentName,
- val priority: Int
+ val priority: Int,
+ val userSerialNumber: Int,
)
companion object {
@@ -272,19 +282,22 @@
FakeWidgetMetadata(
widgetId = 1,
provider = ComponentName("pk_name", "cls_name_1"),
- priority = 1
+ priority = 1,
+ userSerialNumber = 0,
)
val widgetInfo2 =
FakeWidgetMetadata(
widgetId = 2,
provider = ComponentName("pk_name", "cls_name_2"),
- priority = 2
+ priority = 2,
+ userSerialNumber = 0,
)
val widgetInfo3 =
FakeWidgetMetadata(
widgetId = 3,
provider = ComponentName("pk_name", "cls_name_3"),
- priority = 3
+ priority = 3,
+ userSerialNumber = 10,
)
val communalItemRankEntry1 = CommunalItemRank(uid = 1L, rank = widgetInfo1.priority)
val communalItemRankEntry2 = CommunalItemRank(uid = 2L, rank = widgetInfo2.priority)
@@ -295,6 +308,7 @@
widgetId = widgetInfo1.widgetId,
componentName = widgetInfo1.provider.flattenToString(),
itemId = communalItemRankEntry1.uid,
+ userSerialNumber = widgetInfo1.userSerialNumber,
)
val communalWidgetItemEntry2 =
CommunalWidgetItem(
@@ -302,6 +316,7 @@
widgetId = widgetInfo2.widgetId,
componentName = widgetInfo2.provider.flattenToString(),
itemId = communalItemRankEntry2.uid,
+ userSerialNumber = widgetInfo2.userSerialNumber,
)
val communalWidgetItemEntry3 =
CommunalWidgetItem(
@@ -309,6 +324,7 @@
widgetId = widgetInfo3.widgetId,
componentName = widgetInfo3.provider.flattenToString(),
itemId = communalItemRankEntry3.uid,
+ userSerialNumber = widgetInfo3.userSerialNumber,
)
val fakeState =
CommunalHubState().apply {
@@ -318,11 +334,13 @@
widgetId = 1
componentName = "pk_name/fake_widget_1"
rank = 1
+ userSerialNumber = 0
},
CommunalHubState.CommunalWidgetItem().apply {
widgetId = 2
componentName = "pk_name/fake_widget_2"
rank = 2
+ userSerialNumber = 10
},
)
.toTypedArray()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
index 16a022f..8681123 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
@@ -276,8 +276,8 @@
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mLauncherApps = mock(LauncherApps.class);
- mManager = new PeopleSpaceWidgetManager(mContext, mAppWidgetManager, mIPeopleManager,
- mPeopleManager, mLauncherApps, mNotifCollection, mPackageManager,
+ mManager = new PeopleSpaceWidgetManager(mContext, Optional.of(mAppWidgetManager),
+ mIPeopleManager, mPeopleManager, mLauncherApps, mNotifCollection, mPackageManager,
Optional.of(mBubbles), mUserManager, mBackupManager, mINotificationManager,
mNotificationManager, mFakeExecutor, mUserTracker);
mManager.attach(mListenerService);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
index 7c53639..0f8833c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui
import android.app.ActivityManager
+import android.app.DreamManager
import android.app.admin.DevicePolicyManager
import android.app.trust.TrustManager
import android.hardware.fingerprint.FingerprintManager
@@ -33,6 +34,7 @@
import com.android.systemui.biometrics.AuthController
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.ScreenLifecycle
@@ -94,6 +96,7 @@
@get:Provides val demoModeController: DemoModeController = mock(),
@get:Provides val deviceProvisionedController: DeviceProvisionedController = mock(),
@get:Provides val dozeParameters: DozeParameters = mock(),
+ @get:Provides val dreamManager: DreamManager = mock(),
@get:Provides val dumpManager: DumpManager = mock(),
@get:Provides val fingerprintManager: FingerprintManager = mock(),
@get:Provides val headsUpManager: HeadsUpManager = mock(),
@@ -132,6 +135,7 @@
@get:Provides val systemUIDialogManager: SystemUIDialogManager = mock(),
@get:Provides val deviceEntryIconTransitions: Set<DeviceEntryIconTransition> = emptySet(),
@get:Provides val communalInteractor: CommunalInteractor = mock(),
+ @get:Provides val communalSceneInteractor: CommunalSceneInteractor = mock(),
@get:Provides val sceneLogger: SceneLogger = mock(),
@get:Provides val trustManager: TrustManager = mock(),
@get:Provides val primaryBouncerInteractor: PrimaryBouncerInteractor = mock(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
index 446652c..126d858 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
@@ -16,7 +16,9 @@
package com.android.systemui.keyguard.domain.interactor
+import android.service.dream.dreamManager
import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
@@ -36,9 +38,11 @@
mainDispatcher = testDispatcher,
keyguardInteractor = keyguardInteractor,
communalInteractor = communalInteractor,
+ communalSceneInteractor = communalSceneInteractor,
powerInteractor = powerInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
deviceEntryRepository = deviceEntryRepository,
wakeToGoneInteractor = keyguardWakeDirectlyToGoneInteractor,
+ dreamManager = dreamManager
)
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index cc476a3..69ee8fc 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2076,7 +2076,8 @@
app.setPersistent(true);
app.setPid(MY_PID);
app.mState.setMaxAdj(ProcessList.SYSTEM_ADJ);
- app.makeActive(mSystemThread.getApplicationThread(), mProcessStats);
+ app.makeActive(new ApplicationThreadDeferred(mSystemThread.getApplicationThread()),
+ mProcessStats);
app.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_SYSTEM);
addPidLocked(app);
updateLruProcessLocked(app, false, null);
@@ -4874,7 +4875,7 @@
// Make app active after binding application or client may be running requests (e.g
// starting activities) before it is ready.
synchronized (mProcLock) {
- app.makeActive(thread, mProcessStats);
+ app.makeActive(new ApplicationThreadDeferred(thread), mProcessStats);
checkTime(startTime, "attachApplicationLocked: immediately after bindApplication");
}
app.setPendingFinishAttach(true);
diff --git a/services/core/java/com/android/server/am/ApplicationThreadDeferred.java b/services/core/java/com/android/server/am/ApplicationThreadDeferred.java
new file mode 100644
index 0000000..b0f9b53
--- /dev/null
+++ b/services/core/java/com/android/server/am/ApplicationThreadDeferred.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.annotation.IntDef;
+import android.app.IApplicationThread;
+import android.os.RemoteException;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+
+/**
+ * A subclass of {@link IApplicationThread} that defers certain binder calls while the process is
+ * paused (frozen). Any deferred calls are executed when the process is unpaused. In some cases,
+ * multiple instances of deferred calls are collapsed into a single call when the process is
+ * unpaused.
+ *
+ * {@hide}
+ */
+class ApplicationThreadDeferred extends ApplicationThreadFilter {
+
+ static final String TAG = TAG_WITH_CLASS_NAME ? "ApplicationThreadDeferred" : TAG_AM;
+
+ // The flag that enables the deferral behavior of this class. If the flag is disabled then
+ // the class behaves exactly like an ApplicationThreadFilter.
+ private static boolean deferBindersWhenPaused() {
+ return Flags.deferBindersWhenPaused();
+ }
+
+ // The list of notifications that may be deferred.
+ private static final int CLEAR_DNS_CACHE = 0;
+ private static final int UPDATE_TIME_ZONE = 1;
+ private static final int SCHEDULE_LOW_MEMORY = 2;
+ private static final int UPDATE_HTTP_PROXY = 3;
+ private static final int NOTIFICATION_COUNT = 4;
+
+ @IntDef(value = {
+ CLEAR_DNS_CACHE,
+ UPDATE_TIME_ZONE,
+ SCHEDULE_LOW_MEMORY,
+ UPDATE_HTTP_PROXY
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface NotificationType {};
+
+ private final Object mLock = new Object();
+
+ // If this is true, notifications should be queued for later delivery. If this is false,
+ // notifications should be delivered immediately.
+ @GuardedBy("mLock")
+ private boolean mPaused = false;
+
+ // An operation is a lambda that throws an exception.
+ private interface Operation {
+ void run() throws RemoteException;
+ }
+
+ // The array of operations.
+ @GuardedBy("mLock")
+ private final Operation[] mOperations = new Operation[NOTIFICATION_COUNT];
+
+ // The array of operations that actually pending right now.
+ @GuardedBy("mLock")
+ private final boolean[] mPending = new boolean[NOTIFICATION_COUNT];
+
+ // When true, binder calls to paused processes will be deferred until the process is unpaused.
+ private final boolean mDefer;
+
+ /** Create an instance with a base thread and a deferral enable flag. */
+ @VisibleForTesting
+ public ApplicationThreadDeferred(IApplicationThread thread, boolean defer) {
+ super(thread);
+
+ mDefer = defer;
+
+ mOperations[CLEAR_DNS_CACHE] = () -> { super.clearDnsCache(); };
+ mOperations[UPDATE_TIME_ZONE] = () -> { super.updateTimeZone(); };
+ mOperations[SCHEDULE_LOW_MEMORY] = () -> { super.scheduleLowMemory(); };
+ mOperations[UPDATE_HTTP_PROXY] = () -> { super.updateHttpProxy(); };
+ }
+
+ /** Create an instance with a base flag, using the system deferral enable flag. */
+ public ApplicationThreadDeferred(IApplicationThread thread) {
+ this(thread, deferBindersWhenPaused());
+ }
+
+ /** The process is being paused. Start deferring calls. */
+ void onProcessPaused() {
+ synchronized (mLock) {
+ mPaused = true;
+ }
+ }
+
+ /** The process is no longer paused. Drain any deferred calls. */
+ void onProcessUnpaused() {
+ synchronized (mLock) {
+ mPaused = false;
+ try {
+ for (int i = 0; i < mOperations.length; i++) {
+ if (mPending[i]) {
+ mOperations[i].run();
+ }
+ }
+ } catch (RemoteException e) {
+ // Swallow the exception. The caller is not expecting it. Remote exceptions
+ // happen if a has process died; there is no need to report it here.
+ } finally {
+ Arrays.fill(mPending, false);
+ }
+ }
+ }
+
+ /** The pause operation has been canceled. Drain any deferred calls. */
+ void onProcessPausedCancelled() {
+ onProcessUnpaused();
+ }
+
+ /**
+ * If the thread is not paused, execute the operation. Otherwise, save it to the pending
+ * list.
+ */
+ private void execute(@NotificationType int tag) throws RemoteException {
+ synchronized (mLock) {
+ if (mPaused && mDefer) {
+ mPending[tag] = true;
+ return;
+ }
+ }
+ // Outside the synchronization block to avoid contention.
+ mOperations[tag].run();
+ }
+
+ @Override
+ public void clearDnsCache() throws RemoteException {
+ execute(CLEAR_DNS_CACHE);
+ }
+
+ @Override
+ public void updateTimeZone() throws RemoteException {
+ execute(UPDATE_TIME_ZONE);
+ }
+
+ @Override
+ public void scheduleLowMemory() throws RemoteException {
+ execute(SCHEDULE_LOW_MEMORY);
+ }
+
+ @Override
+ public void updateHttpProxy() throws RemoteException {
+ execute(UPDATE_HTTP_PROXY);
+ }
+}
diff --git a/services/core/java/com/android/server/am/ApplicationThreadFilter.java b/services/core/java/com/android/server/am/ApplicationThreadFilter.java
new file mode 100644
index 0000000..d049305
--- /dev/null
+++ b/services/core/java/com/android/server/am/ApplicationThreadFilter.java
@@ -0,0 +1,603 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+
+class ApplicationThreadFilter implements android.app.IApplicationThread {
+ private final android.app.IApplicationThread mBase;
+ public ApplicationThreadFilter(android.app.IApplicationThread base) { mBase = base; }
+ android.app.IApplicationThread getBase() { return mBase; }
+ public android.os.IBinder asBinder() {
+ return mBase.asBinder();
+ }
+
+ @Override
+ public void scheduleReceiver(android.content.Intent intent,
+ android.content.pm.ActivityInfo info,
+ android.content.res.CompatibilityInfo compatInfo,
+ int resultCode,
+ String data,
+ android.os.Bundle extras,
+ boolean ordered,
+ boolean assumeDelivered,
+ int sendingUser,
+ int processState,
+ int sentFromUid,
+ String sentFromPackage)
+ throws android.os.RemoteException {
+ mBase.scheduleReceiver(intent,
+ info,
+ compatInfo,
+ resultCode,
+ data,
+ extras,
+ ordered,
+ assumeDelivered,
+ sendingUser,
+ processState,
+ sentFromUid,
+ sentFromPackage);
+ }
+ @Override
+ public void scheduleReceiverList(java.util.List<android.app.ReceiverInfo> info)
+ throws android.os.RemoteException {
+ mBase.scheduleReceiverList(info);
+ }
+ @Override
+ public void scheduleCreateService(android.os.IBinder token,
+ android.content.pm.ServiceInfo info,
+ android.content.res.CompatibilityInfo compatInfo,
+ int processState)
+ throws android.os.RemoteException {
+ mBase.scheduleCreateService(token,
+ info,
+ compatInfo,
+ processState);
+ }
+ @Override
+ public void scheduleStopService(android.os.IBinder token)
+ throws android.os.RemoteException {
+ mBase.scheduleStopService(token);
+ }
+ @Override
+ public void bindApplication(String packageName,
+ android.content.pm.ApplicationInfo info,
+ String sdkSandboxClientAppVolumeUuid,
+ String sdkSandboxClientAppPackage,
+ boolean isSdkInSandbox,
+ android.content.pm.ProviderInfoList providerList,
+ android.content.ComponentName testName,
+ android.app.ProfilerInfo profilerInfo,
+ android.os.Bundle testArguments,
+ android.app.IInstrumentationWatcher testWatcher,
+ android.app.IUiAutomationConnection uiAutomationConnection,
+ int debugMode,
+ boolean enableBinderTracking,
+ boolean trackAllocation,
+ boolean restrictedBackupMode,
+ boolean persistent,
+ android.content.res.Configuration config,
+ android.content.res.CompatibilityInfo compatInfo,
+ java.util.Map services,
+ android.os.Bundle coreSettings,
+ String buildSerial,
+ android.content.AutofillOptions autofillOptions,
+ android.content.ContentCaptureOptions contentCaptureOptions,
+ long[] disabledCompatChanges,
+ long[] loggableCompatChanges,
+ android.os.SharedMemory serializedSystemFontMap,
+ long startRequestedElapsedTime,
+ long startRequestedUptime)
+ throws android.os.RemoteException {
+ mBase.bindApplication(packageName,
+ info,
+ sdkSandboxClientAppVolumeUuid,
+ sdkSandboxClientAppPackage,
+ isSdkInSandbox,
+ providerList,
+ testName,
+ profilerInfo,
+ testArguments,
+ testWatcher,
+ uiAutomationConnection,
+ debugMode,
+ enableBinderTracking,
+ trackAllocation,
+ restrictedBackupMode,
+ persistent,
+ config,
+ compatInfo,
+ services,
+ coreSettings,
+ buildSerial,
+ autofillOptions,
+ contentCaptureOptions,
+ disabledCompatChanges,
+ loggableCompatChanges,
+ serializedSystemFontMap,
+ startRequestedElapsedTime,
+ startRequestedUptime);
+ }
+ @Override
+ public void runIsolatedEntryPoint(String entryPoint,
+ String[] entryPointArgs)
+ throws android.os.RemoteException {
+ mBase.runIsolatedEntryPoint(entryPoint,
+ entryPointArgs);
+ }
+ @Override
+ public void scheduleExit()
+ throws android.os.RemoteException {
+ mBase.scheduleExit();
+ }
+ @Override
+ public void scheduleServiceArgs(android.os.IBinder token,
+ android.content.pm.ParceledListSlice args)
+ throws android.os.RemoteException {
+ mBase.scheduleServiceArgs(token,
+ args);
+ }
+ @Override
+ public void updateTimeZone()
+ throws android.os.RemoteException {
+ mBase.updateTimeZone();
+ }
+ @Override
+ public void processInBackground()
+ throws android.os.RemoteException {
+ mBase.processInBackground();
+ }
+ @Override
+ public void scheduleBindService(android.os.IBinder token,
+ android.content.Intent intent,
+ boolean rebind,
+ int processState,
+ long bindSeq)
+ throws android.os.RemoteException {
+ mBase.scheduleBindService(token,
+ intent,
+ rebind,
+ processState,
+ bindSeq);
+ }
+ @Override
+ public void scheduleUnbindService(android.os.IBinder token,
+ android.content.Intent intent)
+ throws android.os.RemoteException {
+ mBase.scheduleUnbindService(token,
+ intent);
+ }
+ @Override
+ public void dumpService(android.os.ParcelFileDescriptor fd,
+ android.os.IBinder servicetoken,
+ String[] args)
+ throws android.os.RemoteException {
+ mBase.dumpService(fd,
+ servicetoken,
+ args);
+ }
+ @Override
+ public void scheduleRegisteredReceiver(android.content.IIntentReceiver receiver,
+ android.content.Intent intent,
+ int resultCode,
+ String data,
+ android.os.Bundle extras,
+ boolean ordered,
+ boolean sticky,
+ boolean assumeDelivered,
+ int sendingUser,
+ int processState,
+ int sentFromUid,
+ String sentFromPackage)
+ throws android.os.RemoteException {
+ mBase.scheduleRegisteredReceiver(receiver,
+ intent,
+ resultCode,
+ data,
+ extras,
+ ordered,
+ sticky,
+ assumeDelivered,
+ sendingUser,
+ processState,
+ sentFromUid,
+ sentFromPackage);
+ }
+ @Override
+ public void scheduleLowMemory()
+ throws android.os.RemoteException {
+ mBase.scheduleLowMemory();
+ }
+ @Override
+ public void profilerControl(boolean start,
+ android.app.ProfilerInfo profilerInfo,
+ int profileType)
+ throws android.os.RemoteException {
+ mBase.profilerControl(start,
+ profilerInfo,
+ profileType);
+ }
+ @Override
+ public void setSchedulingGroup(int group)
+ throws android.os.RemoteException {
+ mBase.setSchedulingGroup(group);
+ }
+ @Override
+ public void scheduleCreateBackupAgent(android.content.pm.ApplicationInfo app,
+ int backupMode,
+ int userId,
+ int operationType)
+ throws android.os.RemoteException {
+ mBase.scheduleCreateBackupAgent(app,
+ backupMode,
+ userId,
+ operationType);
+ }
+ @Override
+ public void scheduleDestroyBackupAgent(android.content.pm.ApplicationInfo app,
+ int userId)
+ throws android.os.RemoteException {
+ mBase.scheduleDestroyBackupAgent(app,
+ userId);
+ }
+ @Override
+ public void scheduleOnNewSceneTransitionInfo(android.os.IBinder token,
+ android.app.ActivityOptions.SceneTransitionInfo info)
+ throws android.os.RemoteException {
+ mBase.scheduleOnNewSceneTransitionInfo(token,
+ info);
+ }
+ @Override
+ public void scheduleSuicide()
+ throws android.os.RemoteException {
+ mBase.scheduleSuicide();
+ }
+ @Override
+ public void dispatchPackageBroadcast(int cmd,
+ String[] packages)
+ throws android.os.RemoteException {
+ mBase.dispatchPackageBroadcast(cmd,
+ packages);
+ }
+ @Override
+ public void scheduleCrash(String msg,
+ int typeId,
+ android.os.Bundle extras)
+ throws android.os.RemoteException {
+ mBase.scheduleCrash(msg,
+ typeId,
+ extras);
+ }
+ @Override
+ public void dumpHeap(boolean managed,
+ boolean mallocInfo,
+ boolean runGc,
+ String dumpBitmaps,
+ String path,
+ android.os.ParcelFileDescriptor fd,
+ android.os.RemoteCallback finishCallback)
+ throws android.os.RemoteException {
+ mBase.dumpHeap(managed,
+ mallocInfo,
+ runGc,
+ dumpBitmaps,
+ path,
+ fd,
+ finishCallback);
+ }
+ @Override
+ public void dumpActivity(android.os.ParcelFileDescriptor fd,
+ android.os.IBinder servicetoken,
+ String prefix,
+ String[] args)
+ throws android.os.RemoteException {
+ mBase.dumpActivity(fd,
+ servicetoken,
+ prefix,
+ args);
+ }
+ @Override
+ public void dumpResources(android.os.ParcelFileDescriptor fd,
+ android.os.RemoteCallback finishCallback)
+ throws android.os.RemoteException {
+ mBase.dumpResources(fd,
+ finishCallback);
+ }
+ @Override
+ public void clearDnsCache()
+ throws android.os.RemoteException {
+ mBase.clearDnsCache();
+ }
+ @Override
+ public void updateHttpProxy()
+ throws android.os.RemoteException {
+ mBase.updateHttpProxy();
+ }
+ @Override
+ public void setCoreSettings(android.os.Bundle coreSettings)
+ throws android.os.RemoteException {
+ mBase.setCoreSettings(coreSettings);
+ }
+ @Override
+ public void updatePackageCompatibilityInfo(String pkg,
+ android.content.res.CompatibilityInfo info)
+ throws android.os.RemoteException {
+ mBase.updatePackageCompatibilityInfo(pkg,
+ info);
+ }
+ @Override
+ public void scheduleTrimMemory(int level)
+ throws android.os.RemoteException {
+ mBase.scheduleTrimMemory(level);
+ }
+ @Override
+ public void dumpMemInfo(android.os.ParcelFileDescriptor fd,
+ android.os.Debug.MemoryInfo mem,
+ boolean checkin,
+ boolean dumpInfo,
+ boolean dumpDalvik,
+ boolean dumpSummaryOnly,
+ boolean dumpUnreachable,
+ boolean dumpAllocatorLogs,
+ String[] args)
+ throws android.os.RemoteException {
+ mBase.dumpMemInfo(fd,
+ mem,
+ checkin,
+ dumpInfo,
+ dumpDalvik,
+ dumpSummaryOnly,
+ dumpUnreachable,
+ dumpAllocatorLogs,
+ args);
+ }
+ @Override
+ public void dumpMemInfoProto(android.os.ParcelFileDescriptor fd,
+ android.os.Debug.MemoryInfo mem,
+ boolean dumpInfo,
+ boolean dumpDalvik,
+ boolean dumpSummaryOnly,
+ boolean dumpUnreachable,
+ String[] args)
+ throws android.os.RemoteException {
+ mBase.dumpMemInfoProto(fd,
+ mem,
+ dumpInfo,
+ dumpDalvik,
+ dumpSummaryOnly,
+ dumpUnreachable,
+ args);
+ }
+ @Override
+ public void dumpGfxInfo(android.os.ParcelFileDescriptor fd,
+ String[] args)
+ throws android.os.RemoteException {
+ mBase.dumpGfxInfo(fd,
+ args);
+ }
+ @Override
+ public void dumpCacheInfo(android.os.ParcelFileDescriptor fd,
+ String[] args)
+ throws android.os.RemoteException {
+ mBase.dumpCacheInfo(fd,
+ args);
+ }
+ @Override
+ public void dumpProvider(android.os.ParcelFileDescriptor fd,
+ android.os.IBinder servicetoken,
+ String[] args)
+ throws android.os.RemoteException {
+ mBase.dumpProvider(fd,
+ servicetoken,
+ args);
+ }
+ @Override
+ public void dumpDbInfo(android.os.ParcelFileDescriptor fd,
+ String[] args)
+ throws android.os.RemoteException {
+ mBase.dumpDbInfo(fd,
+ args);
+ }
+ @Override
+ public void unstableProviderDied(android.os.IBinder provider)
+ throws android.os.RemoteException {
+ mBase.unstableProviderDied(provider);
+ }
+ @Override
+ public void requestAssistContextExtras(android.os.IBinder activityToken,
+ android.os.IBinder requestToken,
+ int requestType,
+ int sessionId,
+ int flags)
+ throws android.os.RemoteException {
+ mBase.requestAssistContextExtras(activityToken,
+ requestToken,
+ requestType,
+ sessionId,
+ flags);
+ }
+ @Override
+ public void scheduleTranslucentConversionComplete(android.os.IBinder token,
+ boolean timeout)
+ throws android.os.RemoteException {
+ mBase.scheduleTranslucentConversionComplete(token,
+ timeout);
+ }
+ @Override
+ public void setProcessState(int state)
+ throws android.os.RemoteException {
+ mBase.setProcessState(state);
+ }
+ @Override
+ public void scheduleInstallProvider(android.content.pm.ProviderInfo provider)
+ throws android.os.RemoteException {
+ mBase.scheduleInstallProvider(provider);
+ }
+ @Override
+ public void updateTimePrefs(int timeFormatPreference)
+ throws android.os.RemoteException {
+ mBase.updateTimePrefs(timeFormatPreference);
+ }
+ @Override
+ public void scheduleEnterAnimationComplete(android.os.IBinder token)
+ throws android.os.RemoteException {
+ mBase.scheduleEnterAnimationComplete(token);
+ }
+ @Override
+ public void notifyCleartextNetwork(byte[] firstPacket)
+ throws android.os.RemoteException {
+ mBase.notifyCleartextNetwork(firstPacket);
+ }
+ @Override
+ public void startBinderTracking()
+ throws android.os.RemoteException {
+ mBase.startBinderTracking();
+ }
+ @Override
+ public void stopBinderTrackingAndDump(android.os.ParcelFileDescriptor fd)
+ throws android.os.RemoteException {
+ mBase.stopBinderTrackingAndDump(fd);
+ }
+ @Override
+ public void scheduleLocalVoiceInteractionStarted(android.os.IBinder token,
+ com.android.internal.app.IVoiceInteractor voiceInteractor)
+ throws android.os.RemoteException {
+ mBase.scheduleLocalVoiceInteractionStarted(token,
+ voiceInteractor);
+ }
+ @Override
+ public void handleTrustStorageUpdate()
+ throws android.os.RemoteException {
+ mBase.handleTrustStorageUpdate();
+ }
+ @Override
+ public void attachAgent(String path)
+ throws android.os.RemoteException {
+ mBase.attachAgent(path);
+ }
+ @Override
+ public void attachStartupAgents(String dataDir)
+ throws android.os.RemoteException {
+ mBase.attachStartupAgents(dataDir);
+ }
+ @Override
+ public void scheduleApplicationInfoChanged(android.content.pm.ApplicationInfo ai)
+ throws android.os.RemoteException {
+ mBase.scheduleApplicationInfoChanged(ai);
+ }
+ @Override
+ public void setNetworkBlockSeq(long procStateSeq)
+ throws android.os.RemoteException {
+ mBase.setNetworkBlockSeq(procStateSeq);
+ }
+ @Override
+ public void scheduleTransaction(android.app.servertransaction.ClientTransaction transaction)
+ throws android.os.RemoteException {
+ mBase.scheduleTransaction(transaction);
+ }
+ @Override
+ public void scheduleTaskFragmentTransaction(android.window.ITaskFragmentOrganizer organizer,
+ android.window.TaskFragmentTransaction transaction)
+ throws android.os.RemoteException {
+ mBase.scheduleTaskFragmentTransaction(organizer,
+ transaction);
+ }
+ @Override
+ public void requestDirectActions(android.os.IBinder activityToken,
+ com.android.internal.app.IVoiceInteractor intractor,
+ android.os.RemoteCallback cancellationCallback,
+ android.os.RemoteCallback callback)
+ throws android.os.RemoteException {
+ mBase.requestDirectActions(activityToken,
+ intractor,
+ cancellationCallback,
+ callback);
+ }
+ @Override
+ public void performDirectAction(android.os.IBinder activityToken,
+ String actionId,
+ android.os.Bundle arguments,
+ android.os.RemoteCallback cancellationCallback,
+ android.os.RemoteCallback resultCallback)
+ throws android.os.RemoteException {
+ mBase.performDirectAction(activityToken,
+ actionId,
+ arguments,
+ cancellationCallback,
+ resultCallback);
+ }
+ @Override
+ public void notifyContentProviderPublishStatus(android.app.ContentProviderHolder holder,
+ String authorities,
+ int userId,
+ boolean published)
+ throws android.os.RemoteException {
+ mBase.notifyContentProviderPublishStatus(holder,
+ authorities,
+ userId,
+ published);
+ }
+ @Override
+ public void instrumentWithoutRestart(android.content.ComponentName instrumentationName,
+ android.os.Bundle instrumentationArgs,
+ android.app.IInstrumentationWatcher instrumentationWatcher,
+ android.app.IUiAutomationConnection instrumentationUiConnection,
+ android.content.pm.ApplicationInfo targetInfo)
+ throws android.os.RemoteException {
+ mBase.instrumentWithoutRestart(instrumentationName,
+ instrumentationArgs,
+ instrumentationWatcher,
+ instrumentationUiConnection,
+ targetInfo);
+ }
+ @Override
+ public void updateUiTranslationState(android.os.IBinder activityToken,
+ int state,
+ android.view.translation.TranslationSpec sourceSpec,
+ android.view.translation.TranslationSpec targetSpec,
+ java.util.List<android.view.autofill.AutofillId> viewIds,
+ android.view.translation.UiTranslationSpec uiTranslationSpec)
+ throws android.os.RemoteException {
+ mBase.updateUiTranslationState(activityToken,
+ state,
+ sourceSpec,
+ targetSpec,
+ viewIds,
+ uiTranslationSpec);
+ }
+ @Override
+ public void scheduleTimeoutService(android.os.IBinder token,
+ int startId)
+ throws android.os.RemoteException {
+ mBase.scheduleTimeoutService(token,
+ startId);
+ }
+ @Override
+ public void scheduleTimeoutServiceForType(android.os.IBinder token,
+ int startId,
+ int fgsType)
+ throws android.os.RemoteException {
+ mBase.scheduleTimeoutServiceForType(token,
+ startId,
+ fgsType);
+ }
+ @Override
+ public void schedulePing(android.os.RemoteCallback pong)
+ throws android.os.RemoteException {
+ mBase.schedulePing(pong);
+ }
+}
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 6433f2c..1c4ffbb 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -2656,6 +2656,7 @@
// PIDs that run out of async binder buffer when being frozen
ArraySet<Integer> pidsAsync = (mFreezerBinderAsyncThreshold < 0) ? null : new ArraySet<>();
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "binderErrorSync");
for (int i = 0; i < pids.size(); i++) {
int current = pids.get(i);
try {
@@ -2684,6 +2685,7 @@
Slog.w(TAG_AM, "Unable to query binder frozen stats for pid " + current);
}
}
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
// TODO: when kernel binder driver supports, poll the binder status directly.
// Binderfs stats, like other debugfs files, is not a reliable interface. But it's the
@@ -2693,6 +2695,8 @@
if (pidsAsync == null || pidsAsync.size() == 0) {
return;
}
+
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "binderErrorAsync");
new BinderfsStatsReader().handleFreeAsyncSpace(
// Check if the frozen process has pending async calls
pidsAsync::contains,
@@ -2710,5 +2714,6 @@
// Log the error if binderfs stats can't be accesses or correctly parsed
exception -> Slog.e(TAG_AM, "Unable to parse binderfs stats"));
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index a74c489..3e71d00 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -151,7 +151,7 @@
* (in which case we are in the process of launching the app).
*/
@CompositeRWLock({"mService", "mProcLock"})
- private IApplicationThread mThread;
+ private ApplicationThreadDeferred mThread;
/**
* Instance of {@link #mThread} that will always meet the {@code oneway}
@@ -737,15 +737,15 @@
}
@GuardedBy({"mService", "mProcLock"})
- public void makeActive(IApplicationThread thread, ProcessStatsService tracker) {
+ public void makeActive(ApplicationThreadDeferred thread, ProcessStatsService tracker) {
mProfile.onProcessActive(thread, tracker);
mThread = thread;
if (mPid == Process.myPid()) {
- mOnewayThread = new SameProcessApplicationThread(thread, FgThread.getHandler());
+ mOnewayThread = new SameProcessApplicationThread(mThread, FgThread.getHandler());
} else {
- mOnewayThread = thread;
+ mOnewayThread = mThread;
}
- mWindowProcessController.setThread(thread);
+ mWindowProcessController.setThread(mThread);
if (mWindowProcessController.useFifoUiScheduling()) {
mService.mSpecifiedFifoProcesses.add(this);
}
@@ -1436,14 +1436,17 @@
void onProcessFrozen() {
mProfile.onProcessFrozen();
+ if (mThread != null) mThread.onProcessPaused();
}
void onProcessUnfrozen() {
+ if (mThread != null) mThread.onProcessUnpaused();
mProfile.onProcessUnfrozen();
mServices.onProcessUnfrozen();
}
void onProcessFrozenCancelled() {
+ if (mThread != null) mThread.onProcessPausedCancelled();
mServices.onProcessFrozenCancelled();
}
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 9b380ff..d214788 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -176,3 +176,11 @@
bug: "330682397"
is_fixed_read_only: true
}
+
+flag {
+ name: "defer_binders_when_paused"
+ namespace: "system_performance"
+ is_fixed_read_only: true
+ description: "Defer submitting binder calls to paused processes."
+ bug: "327038797"
+}
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
index 82ecb4a..91ab872 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
@@ -225,17 +225,17 @@
sWriter.startThread();
}
- static void initialize(@NonNull Handler handler, @NonNull Context context) {
+ static void initialize(@NonNull Handler ioHandler, @NonNull Context context) {
final UserManagerInternal userManagerInternal =
LocalServices.getService(UserManagerInternal.class);
- handler.post(() -> {
+ ioHandler.post(() -> {
userManagerInternal.addUserLifecycleListener(
new UserManagerInternal.UserLifecycleListener() {
@Override
public void onUserCreated(UserInfo user, @Nullable Object token) {
final int userId = user.id;
sWriter.onUserCreated(userId);
- handler.post(() -> {
+ ioHandler.post(() -> {
synchronized (ImfLock.class) {
if (!sPerUserMap.contains(userId)) {
final AdditionalSubtypeMap additionalSubtypeMap =
@@ -257,7 +257,7 @@
public void onUserRemoved(UserInfo user) {
final int userId = user.id;
sWriter.onUserRemoved(userId);
- handler.post(() -> {
+ ioHandler.post(() -> {
synchronized (ImfLock.class) {
sPerUserMap.remove(userId);
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 875380f..5665738 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -26,6 +26,7 @@
import static android.provider.Settings.Secure.STYLUS_HANDWRITING_ENABLED;
import static android.server.inputmethod.InputMethodManagerServiceProto.BACK_DISPOSITION;
import static android.server.inputmethod.InputMethodManagerServiceProto.BOUND_TO_METHOD;
+import static android.server.inputmethod.InputMethodManagerServiceProto.CONCURRENT_MULTI_USER_MODE_ENABLED;
import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_ATTRIBUTE;
import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_CLIENT;
import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE;
@@ -78,6 +79,7 @@
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.hardware.input.InputManager;
import android.inputmethodservice.InputMethodService;
@@ -345,8 +347,8 @@
private int mCurrentUserId;
/** Holds all user related data */
- @GuardedBy("ImfLock.class")
- private UserDataRepository mUserDataRepository;
+ @SharedByAllUsersField
+ private final UserDataRepository mUserDataRepository;
final WindowManagerInternal mWindowManagerInternal;
private final ActivityManagerInternal mActivityManagerInternal;
@@ -922,11 +924,15 @@
* {@link SystemService} used to publish and manage the lifecycle of
* {@link InputMethodManagerService}.
*/
- public static final class Lifecycle extends SystemService {
+ public static final class Lifecycle extends SystemService
+ implements UserManagerInternal.UserLifecycleListener {
private final InputMethodManagerService mService;
public Lifecycle(Context context) {
this(context, createServiceForProduction(context));
+
+ // For production code, hook up user lifecycle
+ mService.mUserManagerInternal.addUserLifecycleListener(this);
}
@VisibleForTesting
@@ -1005,6 +1011,18 @@
}
@Override
+ public void onUserCreated(UserInfo user, @Nullable Object token) {
+ // Called directly from UserManagerService. Do not block the calling thread.
+ }
+
+ @Override
+ public void onUserRemoved(UserInfo user) {
+ // Called directly from UserManagerService. Do not block the calling thread.
+ final int userId = user.id;
+ mService.mUserDataRepository.remove(userId);
+ }
+
+ @Override
public void onUserUnlocking(@NonNull TargetUser user) {
// Called on ActivityManager thread.
SecureSettingsWrapper.onUserUnlocking(user.getUserIdentifier());
@@ -1106,14 +1124,14 @@
// Search for InputMethodSettingsRepository.put() to find where and when it's actually
// being updated. In general IMMS should refrain from exposing the existence of IMEs
// until systemReady().
- InputMethodSettingsRepository.initialize(mHandler, mContext);
- AdditionalSubtypeMapRepository.initialize(mHandler, mContext);
+ InputMethodSettingsRepository.initialize(mIoHandler, mContext);
+ AdditionalSubtypeMapRepository.initialize(mIoHandler, mContext);
mCurrentUserId = mActivityManagerInternal.getCurrentUserId();
@SuppressWarnings("GuardedBy") final IntFunction<InputMethodBindingController>
bindingControllerFactory = userId -> new InputMethodBindingController(userId,
InputMethodManagerService.this);
- mUserDataRepository = new UserDataRepository(mHandler, mUserManagerInternal,
+ mUserDataRepository = new UserDataRepository(
bindingControllerForTesting != null ? bindingControllerForTesting
: bindingControllerFactory);
@@ -4560,6 +4578,7 @@
proto.write(BACK_DISPOSITION, bindingController.getBackDisposition());
proto.write(IME_WINDOW_VISIBILITY, bindingController.getImeWindowVis());
proto.write(SHOW_IME_WITH_HARD_KEYBOARD, mMenuController.getShowImeWithHardKeyboard());
+ proto.write(CONCURRENT_MULTI_USER_MODE_ENABLED, mConcurrentMultiUserModeEnabled);
proto.end(token);
}
}
@@ -6013,6 +6032,7 @@
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final var userData = getUserData(userId);
p.println("Current Input Method Manager state:");
+ p.println(" concurrentMultiUserModeEnabled" + mConcurrentMultiUserModeEnabled);
final List<InputMethodInfo> methodList = settings.getMethodList();
int numImes = methodList.size();
p.println(" Input Methods:");
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
index 68924b5..a4d8ee5 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
@@ -54,16 +54,16 @@
sPerUserMap.put(userId, obj);
}
- static void initialize(@NonNull Handler handler, @NonNull Context context) {
+ static void initialize(@NonNull Handler ioHandler, @NonNull Context context) {
final UserManagerInternal userManagerInternal =
LocalServices.getService(UserManagerInternal.class);
- handler.post(() -> {
+ ioHandler.post(() -> {
userManagerInternal.addUserLifecycleListener(
new UserManagerInternal.UserLifecycleListener() {
@Override
public void onUserRemoved(UserInfo user) {
final int userId = user.id;
- handler.post(() -> {
+ ioHandler.post(() -> {
synchronized (ImfLock.class) {
sPerUserMap.remove(userId);
}
diff --git a/services/core/java/com/android/server/inputmethod/UserDataRepository.java b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
index 5cd980f..98d7548 100644
--- a/services/core/java/com/android/server/inputmethod/UserDataRepository.java
+++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
@@ -16,11 +16,10 @@
package com.android.server.inputmethod;
+import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
-import android.content.pm.UserInfo;
-import android.os.Handler;
import android.util.SparseArray;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.ImeTracker;
@@ -29,52 +28,63 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
import com.android.internal.inputmethod.IRemoteInputConnection;
-import com.android.server.pm.UserManagerInternal;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer;
import java.util.function.IntFunction;
final class UserDataRepository {
- @GuardedBy("ImfLock.class")
+ private final ReentrantReadWriteLock mUserDataLock = new ReentrantReadWriteLock();
+
+ @GuardedBy("mUserDataLock")
private final SparseArray<UserData> mUserData = new SparseArray<>();
private final IntFunction<InputMethodBindingController> mBindingControllerFactory;
- @GuardedBy("ImfLock.class")
+ @AnyThread
@NonNull
UserData getOrCreate(@UserIdInt int userId) {
- UserData userData = mUserData.get(userId);
- if (userData == null) {
- userData = new UserData(userId, mBindingControllerFactory.apply(userId));
- mUserData.put(userId, userData);
+ mUserDataLock.writeLock().lock();
+ try {
+ UserData userData = mUserData.get(userId);
+ if (userData == null) {
+ userData = new UserData(userId, mBindingControllerFactory.apply(userId));
+ mUserData.put(userId, userData);
+ }
+ return userData;
+ } finally {
+ mUserDataLock.writeLock().unlock();
}
- return userData;
}
- @GuardedBy("ImfLock.class")
+ @AnyThread
void forAllUserData(Consumer<UserData> consumer) {
- for (int i = 0; i < mUserData.size(); i++) {
- consumer.accept(mUserData.valueAt(i));
+ final SparseArray<UserData> copiedArray;
+ mUserDataLock.readLock().lock();
+ try {
+ copiedArray = mUserData.clone();
+ } finally {
+ mUserDataLock.readLock().unlock();
+ }
+ for (int i = 0; i < copiedArray.size(); i++) {
+ consumer.accept(copiedArray.valueAt(i));
}
}
UserDataRepository(
- @NonNull Handler handler, @NonNull UserManagerInternal userManagerInternal,
@NonNull IntFunction<InputMethodBindingController> bindingControllerFactory) {
mBindingControllerFactory = bindingControllerFactory;
- userManagerInternal.addUserLifecycleListener(
- new UserManagerInternal.UserLifecycleListener() {
- @Override
- public void onUserRemoved(UserInfo user) {
- final int userId = user.id;
- handler.post(() -> {
- synchronized (ImfLock.class) {
- mUserData.remove(userId);
- }
- });
- }
- });
+ }
+
+ @AnyThread
+ void remove(@UserIdInt int userId) {
+ mUserDataLock.writeLock().lock();
+ try {
+ mUserData.remove(userId);
+ } finally {
+ mUserDataLock.writeLock().unlock();
+ }
}
/** Placeholder for all IMMS user specific fields */
diff --git a/services/core/java/com/android/server/notification/ZenModeEventLogger.java b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
index ec5d96d..4a82057 100644
--- a/services/core/java/com/android/server/notification/ZenModeEventLogger.java
+++ b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
@@ -106,11 +106,11 @@
/**
* Potentially log a zen mode change if the provided config and policy changes warrant it.
*
- * @param prevInfo ZenModeInfo (zen mode setting, config, policy) prior to this change
- * @param newInfo ZenModeInfo after this change takes effect
- * @param callingUid the calling UID associated with the change; may be used to attribute the
- * change to a particular package or determine if this is a user action
- * @param origin The origin of the Zen change.
+ * @param prevInfo ZenModeInfo (zen mode setting, config, policy) prior to this change
+ * @param newInfo ZenModeInfo after this change takes effect
+ * @param callingUid the calling UID associated with the change; may be used to attribute the
+ * change to a particular package or determine if this is a user action
+ * @param origin The origin of the Zen change.
*/
public final void maybeLogZenChange(ZenModeInfo prevInfo, ZenModeInfo newInfo, int callingUid,
@ConfigChangeOrigin int origin) {
@@ -127,6 +127,9 @@
/**
* Reassign callingUid in mChangeState if we have more specific information that warrants it
* (for instance, if the change is automatic and due to an automatic rule change).
+ *
+ * <p>When Flags.modesUi() is enabled, we reassign the calling UID to the package UID in all
+ * changes whose source is not system or system UI, as long as there is only one rule changed.
*/
private void maybeReassignCallingUid() {
int userId = Process.INVALID_UID;
@@ -145,12 +148,23 @@
userId = mChangeState.mNewConfig.user; // mNewConfig must not be null if enabler exists
}
- // The conditions where we should consider reassigning UID for an automatic rule change:
+ // The conditions where we should consider reassigning UID for an automatic rule change
+ // (pre-modes_ui):
// - we've determined it's not a user action
// - our current best guess is that the calling uid is system/sysui
+ // When Flags.modesUi() is true, we get the package UID for the changed rule, as long as:
+ // - the change does not originate from the system based on change origin
+ // - there is only one rule changed
if (mChangeState.getChangedRuleType() == RULE_TYPE_AUTOMATIC) {
- if (mChangeState.getIsUserAction() || !mChangeState.isFromSystemOrSystemUi()) {
- return;
+ if (Flags.modesUi()) {
+ // ignore anything whose origin is system
+ if (mChangeState.isFromSystemOrSystemUi()) {
+ return;
+ }
+ } else {
+ if (mChangeState.getIsUserAction() || !mChangeState.isFromSystemOrSystemUi()) {
+ return;
+ }
}
// Only try to get the package UID if there's exactly one changed automatic rule. If
@@ -202,7 +216,8 @@
/* int32 package_uid = 7 */ mChangeState.getPackageUid(),
/* DNDPolicyProto current_policy = 8 */ mChangeState.getDNDPolicyProto(),
/* bool are_channels_bypassing = 9 */ mChangeState.getAreChannelsBypassing(),
- /* ActiveRuleType active_rule_types = 10 */ mChangeState.getActiveRuleTypes());
+ /* ActiveRuleType active_rule_types = 10 */ mChangeState.getActiveRuleTypes(),
+ /* ChangeOrigin change_origin = 11 */ mChangeState.getChangeOrigin());
}
/**
@@ -235,7 +250,8 @@
ZenModeConfig mPrevConfig, mNewConfig;
NotificationManager.Policy mPrevPolicy, mNewPolicy;
int mCallingUid = Process.INVALID_UID;
- @ConfigChangeOrigin int mOrigin = ZenModeConfig.UPDATE_ORIGIN_UNKNOWN;
+ @ConfigChangeOrigin
+ int mOrigin = ZenModeConfig.UPDATE_ORIGIN_UNKNOWN;
private void init(ZenModeInfo prevInfo, ZenModeInfo newInfo, int callingUid,
@ConfigChangeOrigin int origin) {
@@ -388,7 +404,8 @@
* rules available.
*/
@SuppressLint("WrongConstant") // special case for log-only type on manual rule
- @NonNull List<ZenRule> activeRulesList(ZenModeConfig config) {
+ @NonNull
+ List<ZenRule> activeRulesList(ZenModeConfig config) {
ArrayList<ZenRule> rules = new ArrayList<>();
if (config == null) {
return rules;
@@ -548,6 +565,17 @@
}
/**
+ * Get the config change origin associated with this change, which is stored in mOrigin.
+ * Only useable if modes_ui is true.
+ */
+ int getChangeOrigin() {
+ if (Flags.modesUi()) {
+ return mOrigin;
+ }
+ return 0;
+ }
+
+ /**
* Convert the new policy to a DNDPolicyProto format for output in logs.
*
* <p>If {@code mNewZenMode} is {@code ZEN_MODE_OFF} (which can mean either no rules
diff --git a/services/core/java/com/android/server/power/LowPowerStandbyController.java b/services/core/java/com/android/server/power/LowPowerStandbyController.java
index fa94b43..421471e 100644
--- a/services/core/java/com/android/server/power/LowPowerStandbyController.java
+++ b/services/core/java/com/android/server/power/LowPowerStandbyController.java
@@ -1380,10 +1380,7 @@
Slog.d(TAG, "notifyStandbyPortsChanged");
}
- final Intent intent = new Intent(PowerManager.ACTION_LOW_POWER_STANDBY_PORTS_CHANGED);
- intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
- Manifest.permission.MANAGE_LOW_POWER_STANDBY);
+ sendExplicitBroadcast(PowerManager.ACTION_LOW_POWER_STANDBY_PORTS_CHANGED);
}
/**
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index 7e27407..f6c3d8e 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -76,6 +76,7 @@
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
+import java.util.PriorityQueue;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
@@ -109,12 +110,31 @@
@GuardedBy("mChannelMapLock")
private ArrayMap<Integer, TreeMap<Integer, ChannelItem>> mChannelMap;
+ /*
+ * Multi-level map storing the session statistics since last pull from StatsD.
+ * The first level is keyed by the UID of the process owning the session.
+ * The second level is keyed by the tag of the session. The point of separating different
+ * tags is that since different categories (e.g. HWUI vs APP) of the sessions may have different
+ * behaviors.
+ */
+ @GuardedBy("mSessionSnapshotMapLock")
+ private ArrayMap<Integer, ArrayMap<Integer, AppHintSessionSnapshot>> mSessionSnapshotMap;
+
/** Lock to protect mActiveSessions and the UidObserver. */
private final Object mLock = new Object();
/** Lock to protect mChannelMap. */
private final Object mChannelMapLock = new Object();
+ /*
+ * Lock to protect mSessionSnapshotMap.
+ * Nested acquisition of mSessionSnapshotMapLock and mLock should be avoided.
+ * We should grab these separately.
+ * When we need to have nested acquisitions, we should always follow the order of acquiring
+ * mSessionSnapshotMapLock first then mLock.
+ */
+ private final Object mSessionSnapshotMapLock = new Object();
+
@GuardedBy("mNonIsolatedTidsLock")
private final Map<Integer, Set<Long>> mNonIsolatedTids;
@@ -135,6 +155,8 @@
private int mPowerHalVersion;
private final PackageManager mPackageManager;
+ private boolean mUsesFmq;
+
private static final String PROPERTY_SF_ENABLE_CPU_HINT = "debug.sf.enable_adpf_cpu_hint";
private static final String PROPERTY_HWUI_ENABLE_HINT_MANAGER = "debug.hwui.use_hint_manager";
@@ -162,6 +184,7 @@
}
mActiveSessions = new ArrayMap<>();
mChannelMap = new ArrayMap<>();
+ mSessionSnapshotMap = new ArrayMap<>();
mNativeWrapper = injector.createNativeWrapper();
mNativeWrapper.halInit();
mHintSessionPreferredRate = mNativeWrapper.halGetHintSessionPreferredRate();
@@ -170,6 +193,7 @@
LocalServices.getService(ActivityManagerInternal.class));
mPowerHal = injector.createIPower();
mPowerHalVersion = 0;
+ mUsesFmq = false;
if (mPowerHal != null) {
try {
mPowerHalVersion = mPowerHal.getInterfaceVersion();
@@ -197,6 +221,134 @@
}
}
+ private class AppHintSessionSnapshot {
+ /*
+ * Per-Uid and Per-SessionTag snapshot that tracks metrics including
+ * number of created sessions, number of power efficienct sessions, and
+ * maximum number of threads in a session.
+ * Given that it's Per-SessionTag, each uid can have multiple snapshots.
+ */
+ int mCurrentSessionCount;
+ int mMaxConcurrentSession;
+ int mMaxThreadCount;
+ int mPowerEfficientSessionCount;
+
+ final int mTargetDurationNsCountPQSize = 100;
+ PriorityQueue<TargetDurationRecord> mTargetDurationNsCountPQ;
+
+ class TargetDurationRecord implements Comparable<TargetDurationRecord> {
+ long mTargetDurationNs;
+ long mTimestamp;
+ int mCount;
+ TargetDurationRecord(long targetDurationNs) {
+ mTargetDurationNs = targetDurationNs;
+ mTimestamp = System.currentTimeMillis();
+ mCount = 1;
+ }
+
+ @Override
+ public int compareTo(TargetDurationRecord t) {
+ int tCount = t.getCount();
+ int thisCount = this.getCount();
+ // Here we sort in the order of number of count in ascending order.
+ // i.e. the lowest count of target duration is at the head of the queue.
+ // Upon same count, the tiebreaker is the timestamp, the older item will be at the
+ // front of the queue.
+ if (tCount == thisCount) {
+ return (t.getTimestamp() < this.getTimestamp()) ? 1 : -1;
+ }
+ return (tCount < thisCount) ? 1 : -1;
+ }
+ long getTargetDurationNs() {
+ return mTargetDurationNs;
+ }
+
+ int getCount() {
+ return mCount;
+ }
+
+ long getTimestamp() {
+ return mTimestamp;
+ }
+
+ void setCount(int count) {
+ mCount = count;
+ }
+
+ void setTimestamp() {
+ mTimestamp = System.currentTimeMillis();
+ }
+
+ void setTargetDurationNs(long targetDurationNs) {
+ mTargetDurationNs = targetDurationNs;
+ }
+ }
+
+ AppHintSessionSnapshot() {
+ mCurrentSessionCount = 0;
+ mMaxConcurrentSession = 0;
+ mMaxThreadCount = 0;
+ mPowerEfficientSessionCount = 0;
+ mTargetDurationNsCountPQ = new PriorityQueue<>(1);
+ }
+
+ void updateUponSessionCreation(int threadCount, long targetDuration) {
+ mCurrentSessionCount += 1;
+ mMaxConcurrentSession = Math.max(mMaxConcurrentSession, mCurrentSessionCount);
+ mMaxThreadCount = Math.max(mMaxThreadCount, threadCount);
+ updateTargetDurationNs(targetDuration);
+ }
+
+ void updateUponSessionClose() {
+ mCurrentSessionCount -= 1;
+ }
+
+ void logPowerEfficientSession() {
+ mPowerEfficientSessionCount += 1;
+ }
+
+ void updateThreadCount(int threadCount) {
+ mMaxThreadCount = Math.max(mMaxThreadCount, threadCount);
+ }
+
+ void updateTargetDurationNs(long targetDurationNs) {
+ for (TargetDurationRecord t : mTargetDurationNsCountPQ) {
+ if (t.getTargetDurationNs() == targetDurationNs) {
+ t.setCount(t.getCount() + 1);
+ t.setTimestamp();
+ return;
+ }
+ }
+ if (mTargetDurationNsCountPQ.size() == mTargetDurationNsCountPQSize) {
+ mTargetDurationNsCountPQ.poll();
+ }
+ mTargetDurationNsCountPQ.add(new TargetDurationRecord(targetDurationNs));
+ }
+
+ int getMaxConcurrentSession() {
+ return mMaxConcurrentSession;
+ }
+
+ int getMaxThreadCount() {
+ return mMaxThreadCount;
+ }
+
+ int getPowerEfficientSessionCount() {
+ return mPowerEfficientSessionCount;
+ }
+
+ long[] targetDurationNsList() {
+ final int listSize = 5;
+ long[] targetDurations = new long[listSize];
+ while (mTargetDurationNsCountPQ.size() > listSize) {
+ mTargetDurationNsCountPQ.poll();
+ }
+ for (int i = 0; i < listSize && !mTargetDurationNsCountPQ.isEmpty(); ++i) {
+ targetDurations[i] = mTargetDurationNsCountPQ.poll().getTargetDurationNs();
+ }
+ return targetDurations;
+ }
+ }
private boolean isHalSupported() {
return mHintSessionPreferredRate != -1;
}
@@ -235,6 +387,11 @@
null, // use default PullAtomMetadata values
DIRECT_EXECUTOR,
this::onPullAtom);
+ statsManager.setPullAtomCallback(
+ FrameworkStatsLog.ADPF_SESSION_SNAPSHOT,
+ null, // use default PullAtomMetadata values
+ DIRECT_EXECUTOR,
+ this::onPullAtom);
}
private int onPullAtom(int atomTag, @NonNull List<StatsEvent> data) {
@@ -247,11 +404,82 @@
data.add(FrameworkStatsLog.buildStatsEvent(
FrameworkStatsLog.ADPF_SYSTEM_COMPONENT_INFO,
isSurfaceFlingerUsingCpuHint,
- isHwuiHintManagerEnabled));
+ isHwuiHintManagerEnabled,
+ getFmqUsage()));
+ }
+ if (atomTag == FrameworkStatsLog.ADPF_SESSION_SNAPSHOT) {
+ synchronized (mSessionSnapshotMapLock) {
+ for (int i = 0; i < mSessionSnapshotMap.size(); ++i) {
+ final int uid = mSessionSnapshotMap.keyAt(i);
+ final ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots =
+ mSessionSnapshotMap.valueAt(i);
+ for (int j = 0; j < sessionSnapshots.size(); ++j) {
+ final int sessionTag = sessionSnapshots.keyAt(j);
+ final AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.valueAt(j);
+ data.add(FrameworkStatsLog.buildStatsEvent(
+ FrameworkStatsLog.ADPF_SESSION_SNAPSHOT,
+ uid,
+ sessionTag,
+ sessionSnapshot.getMaxConcurrentSession(),
+ sessionSnapshot.getMaxThreadCount(),
+ sessionSnapshot.getPowerEfficientSessionCount(),
+ sessionSnapshot.targetDurationNsList()
+ ));
+ }
+ }
+ }
+ restoreSessionSnapshot();
}
return android.app.StatsManager.PULL_SUCCESS;
}
+ private int getFmqUsage() {
+ if (mUsesFmq) {
+ return FrameworkStatsLog.ADPFSYSTEM_COMPONENT_INFO__FMQ_SUPPORTED__SUPPORTED;
+ } else if (mPowerHalVersion < 5) {
+ return FrameworkStatsLog.ADPFSYSTEM_COMPONENT_INFO__FMQ_SUPPORTED__HAL_VERSION_NOT_MET;
+ } else {
+ return FrameworkStatsLog.ADPFSYSTEM_COMPONENT_INFO__FMQ_SUPPORTED__UNSUPPORTED;
+ }
+ }
+
+ private void restoreSessionSnapshot() {
+ // clean up snapshot map and rebuild with current active sessions
+ synchronized (mSessionSnapshotMapLock) {
+ mSessionSnapshotMap.clear();
+ synchronized (mLock) {
+ for (int i = 0; i < mActiveSessions.size(); i++) {
+ ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap =
+ mActiveSessions.valueAt(i);
+ for (int j = 0; j < tokenMap.size(); j++) {
+ ArraySet<AppHintSession> sessionSet = tokenMap.valueAt(j);
+ for (int k = 0; k < sessionSet.size(); ++k) {
+ AppHintSession appHintSession = sessionSet.valueAt(k);
+ final int tag = appHintSession.getTag();
+ final int uid = appHintSession.getUid();
+ final long targetDuationNs =
+ appHintSession.getTargetDurationNs();
+ final int threadCount = appHintSession.getThreadIds().length;
+ ArrayMap<Integer, AppHintSessionSnapshot> snapshots =
+ mSessionSnapshotMap.get(uid);
+ if (snapshots == null) {
+ snapshots = new ArrayMap<>();
+ mSessionSnapshotMap.put(uid, snapshots);
+ }
+ AppHintSessionSnapshot snapshot = snapshots.get(tag);
+ if (snapshot == null) {
+ snapshot = new AppHintSessionSnapshot();
+ snapshots.put(tag, snapshot);
+ }
+ snapshot.updateUponSessionCreation(threadCount,
+ targetDuationNs);
+ }
+ }
+ }
+ }
+ }
+ }
+
/**
* Wrapper around the static-native methods from native.
*
@@ -833,17 +1061,13 @@
// we change the session tag to SessionTag.GAME
// as it was not previously classified
switch (getUidApplicationCategory(callingUid)) {
- case ApplicationInfo.CATEGORY_GAME:
- tag = SessionTag.GAME;
- break;
- case ApplicationInfo.CATEGORY_UNDEFINED:
+ case ApplicationInfo.CATEGORY_GAME -> tag = SessionTag.GAME;
+ case ApplicationInfo.CATEGORY_UNDEFINED ->
// We use CATEGORY_UNDEFINED to filter the case when
// PackageManager.NameNotFoundException is caught,
// which should not happen.
tag = SessionTag.APP;
- break;
- default:
- tag = SessionTag.APP;
+ default -> tag = SessionTag.APP;
}
}
@@ -889,9 +1113,15 @@
logPerformanceHintSessionAtom(
callingUid, sessionId, durationNanos, tids, tag);
+ synchronized (mSessionSnapshotMapLock) {
+ // Update session snapshot upon session creation
+ mSessionSnapshotMap.computeIfAbsent(callingUid, k -> new ArrayMap<>())
+ .computeIfAbsent(tag, k -> new AppHintSessionSnapshot())
+ .updateUponSessionCreation(tids.length, durationNanos);
+ }
synchronized (mLock) {
- AppHintSession hs = new AppHintSession(callingUid, callingTgid, tids, token,
- halSessionPtr, durationNanos);
+ AppHintSession hs = new AppHintSession(callingUid, callingTgid, tag, tids,
+ token, halSessionPtr, durationNanos);
ArrayMap<IBinder, ArraySet<AppHintSession>> tokenMap =
mActiveSessions.get(callingUid);
if (tokenMap == null) {
@@ -904,6 +1134,8 @@
tokenMap.put(token, sessionSet);
}
sessionSet.add(hs);
+ mUsesFmq = mUsesFmq || hasChannel(callingTgid, callingUid);
+
return hs;
}
} finally {
@@ -996,6 +1228,7 @@
final class AppHintSession extends IHintSession.Stub implements IBinder.DeathRecipient {
protected final int mUid;
protected final int mPid;
+ protected final int mTag;
protected int[] mThreadIds;
protected final IBinder mToken;
protected long mHalSessionPtr;
@@ -1003,6 +1236,7 @@
protected boolean mUpdateAllowedByProcState;
protected int[] mNewThreadIds;
protected boolean mPowerEfficient;
+ protected boolean mHasBeenPowerEfficient;
protected boolean mShouldForcePause;
private enum SessionModes {
@@ -1010,16 +1244,18 @@
};
protected AppHintSession(
- int uid, int pid, int[] threadIds, IBinder token,
+ int uid, int pid, int sessionTag, int[] threadIds, IBinder token,
long halSessionPtr, long durationNanos) {
mUid = uid;
mPid = pid;
+ mTag = sessionTag;
mToken = token;
mThreadIds = threadIds;
mHalSessionPtr = halSessionPtr;
mTargetDurationNanos = durationNanos;
mUpdateAllowedByProcState = true;
mPowerEfficient = false;
+ mHasBeenPowerEfficient = false;
mShouldForcePause = false;
final boolean allowed = mUidObserver.isUidForeground(mUid);
updateHintAllowedByProcState(allowed);
@@ -1056,6 +1292,20 @@
mNativeWrapper.halUpdateTargetWorkDuration(mHalSessionPtr, targetDurationNanos);
mTargetDurationNanos = targetDurationNanos;
}
+ synchronized (mSessionSnapshotMapLock) {
+ ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots =
+ mSessionSnapshotMap.get(mUid);
+ if (sessionSnapshots == null) {
+ Slogf.w(TAG, "Session snapshot map is null for uid " + mUid);
+ return;
+ }
+ AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag);
+ if (sessionSnapshot == null) {
+ Slogf.w(TAG, "Session snapshot is null for uid " + mUid + " and tag " + mTag);
+ return;
+ }
+ sessionSnapshot.updateTargetDurationNs(mTargetDurationNanos);
+ }
}
@Override
@@ -1108,6 +1358,20 @@
if (sessionSet.isEmpty()) tokenMap.remove(mToken);
if (tokenMap.isEmpty()) mActiveSessions.remove(mUid);
}
+ synchronized (mSessionSnapshotMapLock) {
+ ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots =
+ mSessionSnapshotMap.get(mUid);
+ if (sessionSnapshots == null) {
+ Slogf.w(TAG, "Session snapshot map is null for uid " + mUid);
+ return;
+ }
+ AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag);
+ if (sessionSnapshot == null) {
+ Slogf.w(TAG, "Session snapshot is null for uid " + mUid + " and tag " + mTag);
+ return;
+ }
+ sessionSnapshot.updateUponSessionClose();
+ }
if (powerhintThreadCleanup()) {
synchronized (mNonIsolatedTidsLock) {
final int[] tids = getTidsInternal();
@@ -1191,6 +1455,21 @@
mShouldForcePause = false;
}
}
+ synchronized (mSessionSnapshotMapLock) {
+ ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots =
+ mSessionSnapshotMap.get(mUid);
+ if (sessionSnapshots == null) {
+ Slogf.w(TAG, "Session snapshot map is null for uid " + mUid);
+ return;
+ }
+ AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag);
+ if (sessionSnapshot == null) {
+ Slogf.w(TAG, "Session snapshot is null for uid " + mUid + " and tag "
+ + mTag);
+ return;
+ }
+ sessionSnapshot.updateThreadCount(tids.length);
+ }
}
public int[] getThreadIds() {
@@ -1231,6 +1510,26 @@
}
mNativeWrapper.halSetMode(mHalSessionPtr, mode, enabled);
}
+ if (enabled && (mode == SessionModes.POWER_EFFICIENCY.ordinal())) {
+ if (!mHasBeenPowerEfficient) {
+ mHasBeenPowerEfficient = true;
+ synchronized (mSessionSnapshotMapLock) {
+ ArrayMap<Integer, AppHintSessionSnapshot> sessionSnapshots =
+ mSessionSnapshotMap.get(mUid);
+ if (sessionSnapshots == null) {
+ Slogf.w(TAG, "Session snapshot map is null for uid " + mUid);
+ return;
+ }
+ AppHintSessionSnapshot sessionSnapshot = sessionSnapshots.get(mTag);
+ if (sessionSnapshot == null) {
+ Slogf.w(TAG, "Session snapshot is null for uid " + mUid
+ + " and tag " + mTag);
+ return;
+ }
+ sessionSnapshot.logPowerEfficientSession();
+ }
+ }
+ }
}
@Override
@@ -1254,6 +1553,20 @@
}
}
+ public int getUid() {
+ return mUid;
+ }
+
+ public int getTag() {
+ return mTag;
+ }
+
+ public long getTargetDurationNs() {
+ synchronized (this) {
+ return mTargetDurationNanos;
+ }
+ }
+
void validateWorkDuration(WorkDuration workDuration) {
if (DEBUG) {
Slogf.d(TAG, "WorkDuration("
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 5b17875..59b5da8 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5183,6 +5183,10 @@
String hostingType) {
if (!mStartingProcessActivities.contains(activity)) {
mStartingProcessActivities.add(activity);
+ // Let the activity with higher z-order be started first.
+ if (mStartingProcessActivities.size() > 1) {
+ mStartingProcessActivities.sort(null /* by WindowContainer#compareTo */);
+ }
} else if (mProcessNames.get(
activity.processName, activity.info.applicationInfo.uid) != null) {
// The process is already starting. Wait for it to attach.
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 3f4bda7..54024e9 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -128,21 +128,24 @@
// TODO(b/263368846) Rename when ASM logic is moved in
@Retention(SOURCE)
- @IntDef({BAL_BLOCK,
- BAL_ALLOW_DEFAULT,
- BAL_ALLOW_ALLOWLISTED_UID,
+ @IntDef({
BAL_ALLOW_ALLOWLISTED_COMPONENT,
- BAL_ALLOW_VISIBLE_WINDOW,
+ BAL_ALLOW_ALLOWLISTED_UID,
+ BAL_ALLOW_BOUND_BY_FOREGROUND,
+ BAL_ALLOW_DEFAULT,
+ BAL_ALLOW_FOREGROUND,
+ BAL_ALLOW_GRACE_PERIOD,
BAL_ALLOW_PENDING_INTENT,
BAL_ALLOW_PERMISSION,
BAL_ALLOW_SAW_PERMISSION,
- BAL_ALLOW_GRACE_PERIOD,
- BAL_ALLOW_FOREGROUND,
- BAL_ALLOW_SDK_SANDBOX
+ BAL_ALLOW_SDK_SANDBOX,
+ BAL_ALLOW_TOKEN,
+ BAL_ALLOW_VISIBLE_WINDOW,
+ BAL_BLOCK
})
public @interface BalCode {}
- static final int BAL_BLOCK = 0;
+ static final int BAL_BLOCK = FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_BLOCKED;
static final int BAL_ALLOW_DEFAULT =
FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_DEFAULT;
@@ -195,10 +198,19 @@
static final int BAL_ALLOW_NON_APP_VISIBLE_WINDOW =
FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_NON_APP_VISIBLE_WINDOW;
+ /** Process belongs to a SDK sandbox */
+ static final int BAL_ALLOW_TOKEN =
+ FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_TOKEN;
+
+ /** Process belongs to a SDK sandbox */
+ static final int BAL_ALLOW_BOUND_BY_FOREGROUND =
+ FrameworkStatsLog.BAL_ALLOWED__ALLOWED_REASON__BAL_ALLOW_BOUND_BY_FOREGROUND;
+
static String balCodeToString(@BalCode int balCode) {
return switch (balCode) {
case BAL_ALLOW_ALLOWLISTED_COMPONENT -> "BAL_ALLOW_ALLOWLISTED_COMPONENT";
case BAL_ALLOW_ALLOWLISTED_UID -> "BAL_ALLOW_ALLOWLISTED_UID";
+ case BAL_ALLOW_BOUND_BY_FOREGROUND -> "BAL_ALLOW_BOUND_BY_FOREGROUND";
case BAL_ALLOW_DEFAULT -> "BAL_ALLOW_DEFAULT";
case BAL_ALLOW_FOREGROUND -> "BAL_ALLOW_FOREGROUND";
case BAL_ALLOW_GRACE_PERIOD -> "BAL_ALLOW_GRACE_PERIOD";
@@ -207,6 +219,7 @@
case BAL_ALLOW_PERMISSION -> "BAL_ALLOW_PERMISSION";
case BAL_ALLOW_SAW_PERMISSION -> "BAL_ALLOW_SAW_PERMISSION";
case BAL_ALLOW_SDK_SANDBOX -> "BAL_ALLOW_SDK_SANDBOX";
+ case BAL_ALLOW_TOKEN -> "BAL_ALLOW_TOKEN";
case BAL_ALLOW_VISIBLE_WINDOW -> "BAL_ALLOW_VISIBLE_WINDOW";
case BAL_BLOCK -> "BAL_BLOCK";
default -> throw new IllegalArgumentException("Unexpected value: " + balCode);
@@ -1042,7 +1055,9 @@
|| balCode == BAL_ALLOW_PENDING_INTENT
|| balCode == BAL_ALLOW_SAW_PERMISSION
|| balCode == BAL_ALLOW_VISIBLE_WINDOW
- || balCode == BAL_ALLOW_NON_APP_VISIBLE_WINDOW) {
+ || balCode == BAL_ALLOW_NON_APP_VISIBLE_WINDOW
+ || balCode == BAL_ALLOW_TOKEN
+ || balCode == BAL_ALLOW_BOUND_BY_FOREGROUND) {
return true;
}
}
@@ -1266,7 +1281,8 @@
|| balCode == BAL_ALLOW_PERMISSION
|| balCode == BAL_ALLOW_SAW_PERMISSION
|| balCode == BAL_ALLOW_VISIBLE_WINDOW
- || balCode == BAL_ALLOW_NON_APP_VISIBLE_WINDOW) {
+ || balCode == BAL_ALLOW_NON_APP_VISIBLE_WINDOW
+ || balCode == BAL_ALLOW_BOUND_BY_FOREGROUND) {
return;
}
@@ -1572,7 +1588,7 @@
}
if (balCode == BAL_ALLOW_VISIBLE_WINDOW || balCode == BAL_ALLOW_NON_APP_VISIBLE_WINDOW
- || balCode == BAL_ALLOW_FOREGROUND) {
+ || balCode == BAL_ALLOW_FOREGROUND || balCode == BAL_ALLOW_BOUND_BY_FOREGROUND) {
Task task = sourceRecord != null ? sourceRecord.getTask() : targetTask;
if (task != null && task.getDisplayArea() != null) {
joiner.add(prefix + "Tasks: ");
diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
index 478524b..4a870a3 100644
--- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
+++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
@@ -23,10 +23,13 @@
import static com.android.server.wm.ActivityTaskManagerService.ACTIVITY_BG_START_GRACE_PERIOD_MS;
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_DISALLOW;
+import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_BOUND_BY_FOREGROUND;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_FOREGROUND;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_GRACE_PERIOD;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_PERMISSION;
+import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_TOKEN;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_VISIBLE_WINDOW;
+import static com.android.window.flags.Flags.balImprovedMetrics;
import static java.util.Objects.requireNonNull;
@@ -110,8 +113,8 @@
}
// Allow if the flag was explicitly set.
if (isBackgroundStartAllowedByToken(uid, packageName, isCheckingForFgsStart)) {
- return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ true,
- "process allowed by token");
+ return new BalVerdict(balImprovedMetrics() ? BAL_ALLOW_TOKEN : BAL_ALLOW_PERMISSION,
+ /*background*/ true, "process allowed by token");
}
// Allow if the caller is bound by a UID that's currently foreground.
// But still respect the appSwitchState.
@@ -120,7 +123,8 @@
? appSwitchState != APP_SWITCH_DISALLOW && isBoundByForegroundUid()
: isBoundByForegroundUid();
if (allowBoundByForegroundUid) {
- return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, /*background*/ false,
+ return new BalVerdict(balImprovedMetrics() ? BAL_ALLOW_BOUND_BY_FOREGROUND
+ : BAL_ALLOW_VISIBLE_WINDOW, /*background*/ false,
"process bound by foreground uid");
}
// Allow if the caller has an activity in any foreground task.
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 4da8bbf..eb1a80b 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2583,8 +2583,12 @@
// Containers don't belong to the same hierarchy???
if (commonAncestor == null) {
- throw new IllegalArgumentException("No in the same hierarchy this="
- + thisParentChain + " other=" + otherParentChain);
+ final int thisZ = getPrefixOrderIndex();
+ final int otherZ = other.getPrefixOrderIndex();
+ Slog.w(TAG, "Compare not in the same hierarchy this="
+ + thisParentChain + " thisZ=" + thisZ + " other="
+ + otherParentChain + " otherZ=" + otherZ);
+ return Integer.compare(thisZ, otherZ);
}
// Children are always considered greater than their parents, so if one of the containers
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index 6a86379..961fb9a7 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -25,6 +25,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.notNull;
@@ -38,6 +39,7 @@
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.res.Configuration;
import android.hardware.input.IInputManager;
@@ -157,6 +159,7 @@
mockitoSession()
.initMocks(this)
.strictness(Strictness.LENIENT)
+ .spyStatic(InputMethodUtils.class)
.mockStatic(ServiceManager.class)
.mockStatic(SystemServerInitThreadPool.class)
.startMocking();
@@ -227,6 +230,10 @@
.thenReturn(TEST_IME_TARGET_INFO);
when(mMockInputMethodClient.asBinder()).thenReturn(mMockInputMethodBinder);
+ // This changes the real IME component state. Not appropriate to do in tests.
+ doNothing().when(() -> InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
+ any(PackageManager.class), anyList()));
+
// Used by lazy initializing draw IMS nav bar at InputMethodManagerService#systemRunning(),
// which is ok to be mocked out for now.
doReturn(null).when(() -> SystemServerInitThreadPool.submit(any(), anyString()));
@@ -243,9 +250,13 @@
Process.THREAD_PRIORITY_FOREGROUND,
true /* allowIo */);
mIoThread.start();
+
+ final var ioHandler = spy(Handler.createAsync(mIoThread.getLooper()));
+ doReturn(true).when(ioHandler).post(any());
+
mInputMethodManagerService = new InputMethodManagerService(mContext,
InputMethodManagerService.shouldEnableConcurrentMultiUserMode(mContext),
- mServiceThread.getLooper(), Handler.createAsync(mIoThread.getLooper()),
+ mServiceThread.getLooper(), ioHandler,
unusedUserId -> mMockInputMethodBindingController);
spyOn(mInputMethodManagerService);
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
index 9ca4f1d..81fb1a0 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
@@ -18,23 +18,12 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.content.pm.UserInfo;
-import android.os.ConditionVariable;
-import android.os.Handler;
-import android.os.Looper;
import android.platform.test.ravenwood.RavenwoodRule;
-import com.android.server.pm.UserManagerInternal;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -52,13 +41,8 @@
.setProvideMainThread(true).build();
@Mock
- private UserManagerInternal mMockUserManagerInternal;
-
- @Mock
private InputMethodManagerService mMockInputMethodManagerService;
- private Handler mHandler;
-
private IntFunction<InputMethodBindingController> mBindingControllerFactory;
@Before
@@ -66,7 +50,6 @@
MockitoAnnotations.initMocks(this);
SecureSettingsWrapper.startTestMode();
- mHandler = new Handler(Looper.getMainLooper());
mBindingControllerFactory = new IntFunction<InputMethodBindingController>() {
@Override
@@ -81,51 +64,20 @@
SecureSettingsWrapper.endTestMode();
}
- @Test
- public void testUserDataRepository_addsNewUserInfoOnUserCreatedEvent() {
- // Create UserDataRepository and capture the user lifecycle listener
- final var captor = ArgumentCaptor.forClass(UserManagerInternal.UserLifecycleListener.class);
- final var bindingControllerFactorySpy = spy(mBindingControllerFactory);
- final var repository = new UserDataRepository(mHandler,
- mMockUserManagerInternal, bindingControllerFactorySpy);
-
- verify(mMockUserManagerInternal, times(1)).addUserLifecycleListener(captor.capture());
- final var listener = captor.getValue();
-
- // Assert that UserDataRepository is empty and then call onUserCreated
- assertThat(collectUserData(repository)).isEmpty();
- final var userInfo = new UserInfo();
- userInfo.id = ANY_USER_ID;
- listener.onUserCreated(userInfo, /* unused token */ new Object());
- waitForIdle();
-
- // Assert UserDataRepository remains to be empty.
- assertThat(collectUserData(repository)).isEmpty();
- }
-
+ // TODO(b/352615651): Move this to end-to-end test.
@Test
public void testUserDataRepository_removesUserInfoOnUserRemovedEvent() {
- // Create UserDataRepository and capture the user lifecycle listener
- final var captor = ArgumentCaptor.forClass(UserManagerInternal.UserLifecycleListener.class);
- final var repository = new UserDataRepository(mHandler,
- mMockUserManagerInternal,
+ // Create UserDataRepository
+ final var repository = new UserDataRepository(
userId -> new InputMethodBindingController(userId, mMockInputMethodManagerService));
- verify(mMockUserManagerInternal, times(1)).addUserLifecycleListener(captor.capture());
- final var listener = captor.getValue();
-
// Add one UserData ...
- synchronized (ImfLock.class) {
- final var userData = repository.getOrCreate(ANY_USER_ID);
- assertThat(userData.mUserId).isEqualTo(ANY_USER_ID);
- }
+ final var userData = repository.getOrCreate(ANY_USER_ID);
+ assertThat(userData.mUserId).isEqualTo(ANY_USER_ID);
// ... and then call onUserRemoved
assertThat(collectUserData(repository)).hasSize(1);
- final var userInfo = new UserInfo();
- userInfo.id = ANY_USER_ID;
- listener.onUserRemoved(userInfo);
- waitForIdle();
+ repository.remove(ANY_USER_ID);
// Assert UserDataRepository is now empty
assertThat(collectUserData(repository)).isEmpty();
@@ -133,13 +85,10 @@
@Test
public void testGetOrCreate() {
- final var repository = new UserDataRepository(mHandler,
- mMockUserManagerInternal, mBindingControllerFactory);
+ final var repository = new UserDataRepository(mBindingControllerFactory);
- synchronized (ImfLock.class) {
- final var userData = repository.getOrCreate(ANY_USER_ID);
- assertThat(userData.mUserId).isEqualTo(ANY_USER_ID);
- }
+ final var userData = repository.getOrCreate(ANY_USER_ID);
+ assertThat(userData.mUserId).isEqualTo(ANY_USER_ID);
final var allUserData = collectUserData(repository);
assertThat(allUserData).hasSize(1);
@@ -151,15 +100,8 @@
private List<UserDataRepository.UserData> collectUserData(UserDataRepository repository) {
final var collected = new ArrayList<UserDataRepository.UserData>();
- synchronized (ImfLock.class) {
- repository.forAllUserData(userData -> collected.add(userData));
- }
+ repository.forAllUserData(userData -> collected.add(userData));
return collected;
}
- private void waitForIdle() {
- final var done = new ConditionVariable();
- mHandler.post(done::open);
- done.block();
- }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 419bcb8..e610a32 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -527,7 +527,7 @@
final ProcessRecord appRec = new ProcessRecord(mAms, info, info.processName, uid);
final ProcessStatsService tracker = mAms.mProcessStats;
- final IApplicationThread appThread = mock(IApplicationThread.class);
+ final ApplicationThreadDeferred appThread = mock(ApplicationThreadDeferred.class);
doReturn(mock(IBinder.class)).when(appThread).asBinder();
appRec.makeActive(appThread, tracker);
mAms.mProcessList.addProcessNameLocked(appRec);
@@ -701,7 +701,8 @@
final var wpc = fifoProc.getWindowProcessController();
spyOn(wpc);
doReturn(true).when(wpc).useFifoUiScheduling();
- fifoProc.makeActive(fifoProc.getThread(), mAms.mProcessStats);
+ fifoProc.makeActive(new ApplicationThreadDeferred(fifoProc.getThread()),
+ mAms.mProcessStats);
assertTrue(fifoProc.useFifoUiScheduling());
assertTrue(mAms.mSpecifiedFifoProcesses.contains(fifoProc));
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationThreadDeferredTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationThreadDeferredTest.java
new file mode 100644
index 0000000..8f8c1ac
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationThreadDeferredTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.IApplicationThread;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+
+/**
+ * Tests to verify that the ApplicationThreadDeferred properly defers binder calls to paused
+ * processes.
+ */
+@SmallTest
+public class ApplicationThreadDeferredTest {
+ private static final String TAG = "ApplicationThreadDeferredTest";
+
+ private void callDeferredApis(IApplicationThread thread) throws Exception {
+ thread.clearDnsCache();
+ thread.updateHttpProxy();
+ thread.updateTimeZone();
+ thread.scheduleLowMemory();
+ }
+
+ // Verify that the special APIs have been called count times.
+ private void verifyDeferredApis(IApplicationThread thread, int count) throws Exception {
+ verify(thread, times(count)).clearDnsCache();
+ verify(thread, times(count)).updateHttpProxy();
+ verify(thread, times(count)).updateTimeZone();
+ verify(thread, times(count)).scheduleLowMemory();
+ }
+
+ // Test the baseline behavior of IApplicationThread. If this test fails, all other tests are
+ // suspect.
+ @Test
+ public void testBaseline() throws Exception {
+ IApplicationThread thread = mock(IApplicationThread.class);
+ callDeferredApis(thread);
+ verifyDeferredApis(thread, 1);
+ }
+
+ // Test the baseline behavior of IApplicationThreadDeferred. If this test fails, all other
+ // tests are suspect.
+ @Test
+ public void testBaselineDeferred() throws Exception {
+ IApplicationThread thread = mock(ApplicationThreadDeferred.class);
+ callDeferredApis(thread);
+ verifyDeferredApis(thread, 1);
+ }
+
+ // Verify that a deferred thread behaves like a regular thread when it is not paused.
+ @Test
+ public void testDeferredUnpaused() throws Exception {
+ IApplicationThread base = mock(IApplicationThread.class);
+ ApplicationThreadDeferred thread = new ApplicationThreadDeferred(base, true);
+ callDeferredApis(thread);
+ verifyDeferredApis(base, 1);
+ }
+
+ // Verify that a paused deferred thread thread does not deliver any calls to its parent. Then
+ // unpause the thread and verify that the collapsed calls are forwarded.
+ @Test
+ public void testDeferredPaused() throws Exception {
+ IApplicationThread base = mock(IApplicationThread.class);
+ ApplicationThreadDeferred thread = new ApplicationThreadDeferred(base, true);
+ thread.onProcessPaused();
+ callDeferredApis(thread);
+ callDeferredApis(thread);
+ verifyDeferredApis(base, 0);
+ thread.onProcessUnpaused();
+ verifyDeferredApis(base, 1);
+ }
+
+ // TODO: [b/302724778] Remove manual JNI load
+ static {
+ System.loadLibrary("mockingservicestestjni");
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
index 80f7a06..93066d8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
@@ -189,7 +189,7 @@
private ProcessRecord makeActiveProcessRecord(ApplicationInfo ai, boolean wedge)
throws Exception {
- final IApplicationThread thread = mock(IApplicationThread.class);
+ final ApplicationThreadDeferred thread = mock(ApplicationThreadDeferred.class);
final IBinder threadBinder = new Binder();
doReturn(threadBinder).when(thread).asBinder();
doAnswer((invocation) -> {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 13ba1e5..3aaf2e5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -325,13 +325,13 @@
ProcessRecord.updateProcessRecordNodes(r);
mActiveProcesses.add(r);
- final IApplicationThread thread;
+ final ApplicationThreadDeferred thread;
if (dead) {
- thread = mock(IApplicationThread.class, (invocation) -> {
+ thread = mock(ApplicationThreadDeferred.class, (invocation) -> {
throw new DeadObjectException();
});
} else {
- thread = mock(IApplicationThread.class);
+ thread = mock(ApplicationThreadDeferred.class);
}
final IBinder threadBinder = new Binder();
doReturn(threadBinder).when(thread).asBinder();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
index 240ddf5..0796f41 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
@@ -760,7 +760,7 @@
ProcessStatsService processStatsService = new ProcessStatsService(
mock(ActivityManagerService.class), new File(Environment.getDataSystemCeDirectory(),
"procstats"));
- app.makeActive(mock(IApplicationThread.class), processStatsService);
+ app.makeActive(mock(ApplicationThreadDeferred.class), processStatsService);
return app;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 6366f24..1dbd532 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -3209,7 +3209,7 @@
final ProcessReceiverRecord receivers = app.mReceivers;
final ProcessProfileRecord profile = app.mProfile;
final ProcessProviderRecord providers = app.mProviders;
- app.makeActive(mock(IApplicationThread.class), mService.mProcessStats);
+ app.makeActive(mock(ApplicationThreadDeferred.class), mService.mProcessStats);
app.setLastActivityTime(mLastActivityTime);
app.setKilledByAm(mKilledByAm);
app.setIsolatedEntryPoint(mIsolatedEntryPoint);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
index 89c67d5..3572d23 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
@@ -193,7 +193,7 @@
private ProcessRecord makeActiveProcessRecord(ApplicationInfo ai)
throws Exception {
- final IApplicationThread thread = mock(IApplicationThread.class);
+ final ApplicationThreadDeferred thread = mock(ApplicationThreadDeferred.class);
final IBinder threadBinder = new Binder();
doReturn(threadBinder).when(thread).asBinder();
doAnswer((invocation) -> {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
index 5f12677..1ff4a27 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
@@ -529,7 +529,7 @@
@SuppressWarnings("GuardedBy")
private ProcessRecord addProcessRecord(int pid, int uid, int procState, int adj, int cap,
String packageName) {
- final IApplicationThread appThread = mock(IApplicationThread.class);
+ final ApplicationThreadDeferred appThread = mock(ApplicationThreadDeferred.class);
final IBinder threadBinder = mock(IBinder.class);
final ProcessRecord app = makeProcessRecord(pid, uid, uid, null, 0,
procState, adj, cap, 0L, 0L, packageName, packageName, mAms);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ServiceTimeoutTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ServiceTimeoutTest.java
index 7ec27be..3c43636 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ServiceTimeoutTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ServiceTimeoutTest.java
@@ -145,7 +145,7 @@
name, // processName
name, // packageName
mAms);
- app.makeActive(mock(IApplicationThread.class), mAms.mProcessStats);
+ app.makeActive(mock(ApplicationThreadDeferred.class), mAms.mProcessStats);
mProcessList.updateLruProcessLocked(app, false, null);
final long now = SystemClock.uptimeMillis();
diff --git a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
index bf47816..1decd36 100644
--- a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -151,7 +151,6 @@
private HintManagerService mService;
private ChannelConfig mConfig;
- private ApplicationInfo mApplicationInfo;
private static Answer<Long> fakeCreateWithConfig(Long ptr, Long sessionId) {
return new Answer<Long>() {
@@ -168,12 +167,12 @@
mConfig = new ChannelConfig();
mConfig.readFlagBitmask = 1;
mConfig.writeFlagBitmask = 2;
- mApplicationInfo = new ApplicationInfo();
- mApplicationInfo.category = ApplicationInfo.CATEGORY_GAME;
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.category = ApplicationInfo.CATEGORY_GAME;
when(mContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockPackageManager.getNameForUid(anyInt())).thenReturn(TEST_APP_NAME);
when(mMockPackageManager.getApplicationInfo(eq(TEST_APP_NAME), anyInt()))
- .thenReturn(mApplicationInfo);
+ .thenReturn(applicationInfo);
when(mNativeWrapperMock.halGetHintSessionPreferredRate())
.thenReturn(DEFAULT_HINT_PREFERRED_RATE);
when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_A),
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java
index ff1308c..1c8cb8f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeEventLoggerFake.java
@@ -137,4 +137,9 @@
checkInRange(i);
return mChanges.get(i).getActiveRuleTypes();
}
+
+ public int getChangeOrigin(int i) throws IllegalArgumentException {
+ checkInRange(i);
+ return mChanges.get(i).getChangeOrigin();
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 7bb633e..4bbbc2b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -818,7 +818,7 @@
// 1. Current ringer is normal
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
// Set zen to priority-only with all notification sounds muted (so ringer will be muted)
- Policy totalSilence = new Policy(0,0,0);
+ Policy totalSilence = new Policy(0, 0, 0);
mZenModeHelper.setNotificationPolicy(totalSilence, UPDATE_ORIGIN_APP, 1);
mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
@@ -873,7 +873,7 @@
// even when ringer is muted (since all ringer sounds cannot bypass DND),
// system stream is still affected by ringer mode
- mZenModeHelper.setNotificationPolicy(new Policy(0,0,0), UPDATE_ORIGIN_APP, 1);
+ mZenModeHelper.setNotificationPolicy(new Policy(0, 0, 0), UPDATE_ORIGIN_APP, 1);
mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, Uri.EMPTY,
UPDATE_ORIGIN_APP, "test", "caller", 1);
ZenModeHelper.RingerModeDelegate ringerModeDelegateRingerNotMuted =
@@ -1065,9 +1065,10 @@
@Test
public void testParcelConfig() {
mZenModeHelper.setNotificationPolicy(new Policy(PRIORITY_CATEGORY_EVENTS
- | PRIORITY_CATEGORY_MESSAGES | PRIORITY_CATEGORY_REPEAT_CALLERS
- | PRIORITY_CATEGORY_CONVERSATIONS, PRIORITY_SENDERS_STARRED,
- PRIORITY_SENDERS_STARRED, 0, CONVERSATION_SENDERS_ANYONE), UPDATE_ORIGIN_UNKNOWN,
+ | PRIORITY_CATEGORY_MESSAGES | PRIORITY_CATEGORY_REPEAT_CALLERS
+ | PRIORITY_CATEGORY_CONVERSATIONS, PRIORITY_SENDERS_STARRED,
+ PRIORITY_SENDERS_STARRED, 0, CONVERSATION_SENDERS_ANYONE),
+ UPDATE_ORIGIN_UNKNOWN,
1);
mZenModeHelper.setManualZenRuleDeviceEffects(new ZenDeviceEffects.Builder()
.setShouldDimWallpaper(true)
@@ -1085,13 +1086,14 @@
@Test
public void testWriteXml() throws Exception {
mZenModeHelper.setNotificationPolicy(new Policy(PRIORITY_CATEGORY_EVENTS
- | PRIORITY_CATEGORY_MESSAGES | PRIORITY_CATEGORY_REPEAT_CALLERS
- | PRIORITY_CATEGORY_CONVERSATIONS, PRIORITY_SENDERS_STARRED,
- PRIORITY_SENDERS_STARRED, SUPPRESSED_EFFECT_BADGE, CONVERSATION_SENDERS_ANYONE),
+ | PRIORITY_CATEGORY_MESSAGES | PRIORITY_CATEGORY_REPEAT_CALLERS
+ | PRIORITY_CATEGORY_CONVERSATIONS, PRIORITY_SENDERS_STARRED,
+ PRIORITY_SENDERS_STARRED, SUPPRESSED_EFFECT_BADGE,
+ CONVERSATION_SENDERS_ANYONE),
UPDATE_ORIGIN_UNKNOWN, 1);
mZenModeHelper.setManualZenRuleDeviceEffects(new ZenDeviceEffects.Builder()
- .setShouldDimWallpaper(true)
- .setShouldDisplayGrayscale(true)
+ .setShouldDimWallpaper(true)
+ .setShouldDisplayGrayscale(true)
.build(), UPDATE_ORIGIN_UNKNOWN, "test", 1);
mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, Uri.EMPTY,
UPDATE_ORIGIN_UNKNOWN, "test", "me", 1);
@@ -2210,7 +2212,7 @@
customDefaultRule.name = "Schedule Default Rule";
customDefaultRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
ScheduleInfo scheduleInfo = new ScheduleInfo();
- scheduleInfo.days = new int[] { Calendar.SUNDAY };
+ scheduleInfo.days = new int[]{Calendar.SUNDAY};
scheduleInfo.startHour = 18;
scheduleInfo.endHour = 19;
customDefaultRule.conditionId = ZenModeConfig.toScheduleConditionId(scheduleInfo);
@@ -3027,7 +3029,7 @@
// Turn zen mode on (to important_interruptions)
// Need to additionally call the looper in order to finish the post-apply-config process
mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
- Flags.modesApi() ? UPDATE_ORIGIN_USER: UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null,
+ Flags.modesApi() ? UPDATE_ORIGIN_USER : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null,
Process.SYSTEM_UID);
// Now turn zen mode off, but via a different package UID -- this should get registered as
@@ -3060,6 +3062,9 @@
assertTrue(mZenModeEventLogger.getIsUserAction(0));
assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(0));
checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(0));
+ // change origin should be populated only under modes_ui
+ assertThat(mZenModeEventLogger.getChangeOrigin(0)).isEqualTo(
+ (Flags.modesApi() && Flags.modesUi()) ? UPDATE_ORIGIN_USER : 0);
// and from turning zen mode off:
// - event ID: DND_TURNED_OFF
@@ -3082,6 +3087,8 @@
} else {
checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(1));
}
+ assertThat(mZenModeEventLogger.getChangeOrigin(1)).isEqualTo(
+ Flags.modesUi() ? UPDATE_ORIGIN_APP : 0);
}
@Test
@@ -3098,17 +3105,21 @@
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule,
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
+ UPDATE_ORIGIN_APP, "test", CUSTOM_PKG_UID);
// Event 1: Mimic the rule coming on automatically by setting the Condition to STATE_TRUE
+ // Note that pre-modes_ui, this event serves as a test that automatic changes to an app's
+ // that look like they're coming from the system are attributed to the app, but when
+ // modes_ui is true, we opt to trust the provided change origin.
mZenModeHelper.setAutomaticZenRuleState(id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ Flags.modesUi() ? UPDATE_ORIGIN_APP : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+ CUSTOM_PKG_UID);
// Event 2: "User" turns off the automatic rule (sets it to not enabled)
zenRule.setEnabled(false);
mZenModeHelper.updateAutomaticZenRule(id, zenRule,
- Flags.modesApi() ? UPDATE_ORIGIN_USER: UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
+ Flags.modesApi() ? UPDATE_ORIGIN_USER : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
Process.SYSTEM_UID);
AutomaticZenRule systemRule = new AutomaticZenRule("systemRule",
@@ -3118,7 +3129,7 @@
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
String systemId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), systemRule,
- Flags.modesApi() ? UPDATE_ORIGIN_USER: UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test",
+ Flags.modesApi() ? UPDATE_ORIGIN_USER : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test",
Process.SYSTEM_UID);
// Event 3: turn on the system rule
@@ -3128,7 +3139,7 @@
// Event 4: "User" deletes the rule
mZenModeHelper.removeAutomaticZenRule(systemId,
- Flags.modesApi() ? UPDATE_ORIGIN_USER: UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
+ Flags.modesApi() ? UPDATE_ORIGIN_USER : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "",
Process.SYSTEM_UID);
// In total, this represents 4 events
assertEquals(4, mZenModeEventLogger.numLoggedChanges());
@@ -3151,9 +3162,13 @@
assertFalse(mZenModeEventLogger.getIsUserAction(0));
assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(0));
checkDndProtoMatchesDefaultZenConfig(mZenModeEventLogger.getPolicyProto(0));
+ assertThat(mZenModeEventLogger.getChangeOrigin(0)).isEqualTo(
+ Flags.modesUi() ? UPDATE_ORIGIN_APP : 0);
// When the automatic rule is disabled, this should turn off zen mode and also count as a
// user action. We don't care what the consolidated policy is when DND turns off.
+ // When modes_ui is true, this event should look like a user action attributed to the
+ // specific app.
assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_OFF.getId(),
mZenModeEventLogger.getEventId(1));
assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeEventLogger.getPrevZenMode(1));
@@ -3161,12 +3176,15 @@
assertEquals(DNDProtoEnums.AUTOMATIC_RULE, mZenModeEventLogger.getChangedRuleType(1));
assertEquals(0, mZenModeEventLogger.getNumRulesActive(1));
assertTrue(mZenModeEventLogger.getIsUserAction(1));
- assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(1));
+ assertThat(mZenModeEventLogger.getPackageUid(1)).isEqualTo(
+ Flags.modesUi() ? CUSTOM_PKG_UID : Process.SYSTEM_UID);
if (Flags.modesApi()) {
assertThat(mZenModeEventLogger.getPolicyProto(1)).isNull();
} else {
checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(1));
}
+ assertThat(mZenModeEventLogger.getChangeOrigin(1)).isEqualTo(
+ Flags.modesUi() ? UPDATE_ORIGIN_USER : 0);
// When the system rule is enabled, this counts as an automatic action that comes from the
// system and turns on DND
@@ -3176,6 +3194,8 @@
assertEquals(1, mZenModeEventLogger.getNumRulesActive(2));
assertFalse(mZenModeEventLogger.getIsUserAction(2));
assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(2));
+ assertThat(mZenModeEventLogger.getChangeOrigin(2)).isEqualTo(
+ Flags.modesUi() ? UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI : 0);
// When the system rule is deleted, we consider this a user action that turns DND off
// (again)
@@ -3185,6 +3205,8 @@
assertEquals(0, mZenModeEventLogger.getNumRulesActive(3));
assertTrue(mZenModeEventLogger.getIsUserAction(3));
assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(3));
+ assertThat(mZenModeEventLogger.getChangeOrigin(3)).isEqualTo(
+ Flags.modesUi() ? UPDATE_ORIGIN_USER : 0);
}
@Test
@@ -3238,6 +3260,8 @@
assertEquals(1, mZenModeEventLogger.getNumRulesActive(0));
assertTrue(mZenModeEventLogger.getIsUserAction(0));
assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(0));
+ assertThat(mZenModeEventLogger.getChangeOrigin(0)).isEqualTo(
+ Flags.modesUi() ? UPDATE_ORIGIN_USER : 0);
// Automatic rule turned off automatically by app:
// - event ID: DND_TURNED_OFF
@@ -3249,6 +3273,8 @@
assertEquals(0, mZenModeEventLogger.getNumRulesActive(1));
assertFalse(mZenModeEventLogger.getIsUserAction(1));
assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(1));
+ assertThat(mZenModeEventLogger.getChangeOrigin(1)).isEqualTo(
+ Flags.modesUi() ? UPDATE_ORIGIN_APP : 0);
// Automatic rule turned on automatically by app:
// - event ID: DND_TURNED_ON
@@ -3261,6 +3287,8 @@
assertEquals(1, mZenModeEventLogger.getNumRulesActive(2));
assertFalse(mZenModeEventLogger.getIsUserAction(2));
assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(2));
+ assertThat(mZenModeEventLogger.getChangeOrigin(2)).isEqualTo(
+ Flags.modesUi() ? UPDATE_ORIGIN_APP : 0);
// Automatic rule turned off automatically by the user:
// - event ID: DND_TURNED_ON
@@ -3272,6 +3300,8 @@
assertEquals(0, mZenModeEventLogger.getNumRulesActive(3));
assertTrue(mZenModeEventLogger.getIsUserAction(3));
assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(3));
+ assertThat(mZenModeEventLogger.getChangeOrigin(3)).isEqualTo(
+ Flags.modesUi() ? UPDATE_ORIGIN_USER : 0);
}
@Test
@@ -3335,7 +3365,7 @@
AutomaticZenRule zenRule = new AutomaticZenRule("name",
null,
- new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+ new ComponentName("android", "ScheduleConditionProvider"),
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
@@ -3345,7 +3375,7 @@
// Rule 2, same as rule 1
AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
null,
- new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+ new ComponentName("android", "ScheduleConditionProvider"),
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
null,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
@@ -3395,7 +3425,7 @@
assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeEventLogger.getNewZenMode(0));
assertEquals(1, mZenModeEventLogger.getNumRulesActive(0));
assertFalse(mZenModeEventLogger.getIsUserAction(0));
- assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(0));
+ assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(0));
checkDndProtoMatchesDefaultZenConfig(mZenModeEventLogger.getPolicyProto(0));
// Event 2: rule 2 turns on. This should not change anything about the policy, so the only
@@ -3404,7 +3434,7 @@
mZenModeEventLogger.getEventId(1));
assertEquals(2, mZenModeEventLogger.getNumRulesActive(1));
assertFalse(mZenModeEventLogger.getIsUserAction(1));
- assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(1));
+ assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(1));
checkDndProtoMatchesDefaultZenConfig(mZenModeEventLogger.getPolicyProto(1));
// Event 3: rule 3 turns on. This should trigger a policy change, and be classified as such,
@@ -3482,9 +3512,11 @@
// Turn on rule 1; call looks like it's from the system. Because setting a condition is
// typically an automatic (non-user-initiated) action, expect the calling UID to be
// re-evaluated to the one associated with CUSTOM_PKG_NAME.
+ // When modes_ui is true: we expect the change origin to be the source of truth.
mZenModeHelper.setAutomaticZenRuleState(id,
new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
+ Flags.modesUi() ? UPDATE_ORIGIN_APP : UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+ Process.SYSTEM_UID);
// Second: turn on rule 2. This is a system-owned rule and the UID should not be modified
// (nor even looked up; the mock PackageManager won't handle "android" as input).
@@ -3493,7 +3525,7 @@
UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID);
// Disable rule 1. Because this looks like a user action, the UID should not be modified
- // from the system-provided one.
+ // from the system-provided one unless modes_ui is true.
zenRule.setEnabled(false);
mZenModeHelper.updateAutomaticZenRule(id, zenRule,
UPDATE_ORIGIN_USER, "", Process.SYSTEM_UID);
@@ -3504,6 +3536,7 @@
// Change rule 2's condition, but from some other UID. Since it doesn't look like it's from
// the system, we keep the UID info.
+ // Note that this probably shouldn't be able to occur in real scenarios.
mZenModeHelper.setAutomaticZenRuleState(id2,
new Condition(zenRule2.getConditionId(), "", STATE_FALSE),
UPDATE_ORIGIN_APP, 12345);
@@ -3528,11 +3561,13 @@
assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(1));
// Third event: disable rule 1. This looks like a user action so UID should be left alone.
+ // When modes_ui is true, we assign log this user action with the app that owns the rule.
assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_ACTIVE_RULES_CHANGED.getId(),
mZenModeEventLogger.getEventId(2));
assertEquals(DNDProtoEnums.AUTOMATIC_RULE, mZenModeEventLogger.getChangedRuleType(2));
assertTrue(mZenModeEventLogger.getIsUserAction(2));
- assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(2));
+ assertThat(mZenModeEventLogger.getPackageUid(2)).isEqualTo(
+ Flags.modesUi() ? CUSTOM_PKG_UID : Process.SYSTEM_UID);
// Fourth event: turns on manual mode. Doesn't change effective policy so this is just a
// change in active rules. Confirm that the package UID is left unchanged.
@@ -6202,7 +6237,7 @@
public void setManualZenRuleDeviceEffects_noPreexistingMode() {
ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
.setShouldDimWallpaper(true)
- .build();
+ .build();
mZenModeHelper.setManualZenRuleDeviceEffects(effects, UPDATE_ORIGIN_USER, "settings", 1000);
assertThat(mZenModeHelper.getConfig().manualRule).isNotNull();
@@ -6339,21 +6374,21 @@
private static final Correspondence<ZenRule, ZenRule> IGNORE_METADATA =
Correspondence.transforming(zr -> {
- Parcel p = Parcel.obtain();
- try {
- zr.writeToParcel(p, 0);
- p.setDataPosition(0);
- ZenRule copy = new ZenRule(p);
- copy.creationTime = 0;
- copy.userModifiedFields = 0;
- copy.zenPolicyUserModifiedFields = 0;
- copy.zenDeviceEffectsUserModifiedFields = 0;
- return copy;
- } finally {
- p.recycle();
- }
- },
- "Ignoring timestamp and userModifiedFields");
+ Parcel p = Parcel.obtain();
+ try {
+ zr.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ ZenRule copy = new ZenRule(p);
+ copy.creationTime = 0;
+ copy.userModifiedFields = 0;
+ copy.zenPolicyUserModifiedFields = 0;
+ copy.zenDeviceEffectsUserModifiedFields = 0;
+ return copy;
+ } finally {
+ p.recycle();
+ }
+ },
+ "Ignoring timestamp and userModifiedFields");
private ZenRule expectedImplicitRule(String ownerPkg, int zenMode, ZenPolicy policy,
@Nullable Boolean conditionActive) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundLaunchProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundLaunchProcessControllerTests.java
index a4df034..c9c7e92 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackgroundLaunchProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundLaunchProcessControllerTests.java
@@ -18,9 +18,11 @@
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_DISALLOW;
+import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_BOUND_BY_FOREGROUND;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_FOREGROUND;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_GRACE_PERIOD;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_PERMISSION;
+import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_TOKEN;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_VISIBLE_WINDOW;
import static com.android.server.wm.BackgroundActivityStartController.BAL_BLOCK;
@@ -30,12 +32,18 @@
import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.filters.SmallTest;
import com.android.server.wm.BackgroundActivityStartController.BalVerdict;
+import com.android.window.flags.Flags;
+import org.junit.ClassRule;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -44,6 +52,7 @@
import java.util.HashSet;
import java.util.Set;
+
/**
* Tests for the {@link BackgroundLaunchProcessController} class.
*
@@ -55,6 +64,10 @@
@RunWith(JUnit4.class)
public class BackgroundLaunchProcessControllerTests {
+
+ @ClassRule public static final SetFlagsRule.ClassRule mClassRule = new SetFlagsRule.ClassRule();
+ @Rule public final SetFlagsRule mSetFlagsRule = mClassRule.createSetFlagsRule();
+
Set<IBinder> mActivityStartAllowed = new HashSet<>();
Set<Integer> mHasActiveVisibleWindow = new HashSet<>();
@@ -113,7 +126,8 @@
}
@Test
- public void testAllowedByTokenNoCallback() {
+ @DisableFlags(Flags.FLAG_BAL_IMPROVED_METRICS)
+ public void testAllowedByTokenNoCallbackOld() {
mController = new BackgroundLaunchProcessController(mHasActiveVisibleWindow::contains,
null);
Binder token = new Binder();
@@ -130,7 +144,26 @@
}
@Test
- public void testAllowedByToken() {
+ @EnableFlags(Flags.FLAG_BAL_IMPROVED_METRICS)
+ public void testAllowedByTokenNoCallback() {
+ mController = new BackgroundLaunchProcessController(mHasActiveVisibleWindow::contains,
+ null);
+ Binder token = new Binder();
+ mActivityStartAllowed.add(token);
+ mController.addOrUpdateAllowBackgroundStartPrivileges(token,
+ BackgroundStartPrivileges.ALLOW_BAL);
+ BalVerdict balVerdict = mController.areBackgroundActivityStartsAllowed(
+ mPid, mUid, mPackageName,
+ mAppSwitchState, mIsCheckingForFgsStart,
+ mHasActivityInVisibleTask, mHasBackgroundActivityStartPrivileges,
+ mLastStopAppSwitchesTime, mLastActivityLaunchTime,
+ mLastActivityFinishTime);
+ assertThat(balVerdict.getCode()).isEqualTo(BAL_ALLOW_TOKEN);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_BAL_IMPROVED_METRICS)
+ public void testAllowedByTokenOld() {
Binder token = new Binder();
mActivityStartAllowed.add(token);
mController.addOrUpdateAllowBackgroundStartPrivileges(token,
@@ -145,7 +178,24 @@
}
@Test
- public void testBoundByForeground() {
+ @EnableFlags(Flags.FLAG_BAL_IMPROVED_METRICS)
+ public void testAllowedByToken() {
+ Binder token = new Binder();
+ mActivityStartAllowed.add(token);
+ mController.addOrUpdateAllowBackgroundStartPrivileges(token,
+ BackgroundStartPrivileges.ALLOW_BAL);
+ BalVerdict balVerdict = mController.areBackgroundActivityStartsAllowed(
+ mPid, mUid, mPackageName,
+ mAppSwitchState, mIsCheckingForFgsStart,
+ mHasActivityInVisibleTask, mHasBackgroundActivityStartPrivileges,
+ mLastStopAppSwitchesTime, mLastActivityLaunchTime,
+ mLastActivityFinishTime);
+ assertThat(balVerdict.getCode()).isEqualTo(BAL_ALLOW_TOKEN);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_BAL_IMPROVED_METRICS)
+ public void testBoundByForegroundOld() {
mAppSwitchState = APP_SWITCH_ALLOW;
mController.addBoundClientUid(999, "visible.package", Context.BIND_ALLOW_ACTIVITY_STARTS);
mHasActiveVisibleWindow.add(999);
@@ -159,6 +209,21 @@
}
@Test
+ @EnableFlags(Flags.FLAG_BAL_IMPROVED_METRICS)
+ public void testBoundByForeground() {
+ mAppSwitchState = APP_SWITCH_ALLOW;
+ mController.addBoundClientUid(999, "visible.package", Context.BIND_ALLOW_ACTIVITY_STARTS);
+ mHasActiveVisibleWindow.add(999);
+ BalVerdict balVerdict = mController.areBackgroundActivityStartsAllowed(
+ mPid, mUid, mPackageName,
+ mAppSwitchState, mIsCheckingForFgsStart,
+ mHasActivityInVisibleTask, mHasBackgroundActivityStartPrivileges,
+ mLastStopAppSwitchesTime, mLastActivityLaunchTime,
+ mLastActivityFinishTime);
+ assertThat(balVerdict.getCode()).isEqualTo(BAL_ALLOW_BOUND_BY_FOREGROUND);
+ }
+
+ @Test
public void testForegroundTask() {
mAppSwitchState = APP_SWITCH_ALLOW;
mHasActivityInVisibleTask = true;
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index d6d8339..d29505f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -274,17 +274,28 @@
@Test
public void testAttachApplication() {
- final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setProcessName("testAttach")
+ .setCreateTask(true).build();
+ final ActivityRecord topActivity = new ActivityBuilder(mAtm).setProcessName("testAttach")
+ .setUseProcess(activity.app).setTask(activity.getTask()).build();
activity.detachFromProcess();
- mAtm.startProcessAsync(activity, false /* knownToBeDead */,
+ topActivity.detachFromProcess();
+ mAtm.startProcessAsync(topActivity, false /* knownToBeDead */,
true /* isTop */, "test" /* hostingType */);
+ // Even if the activity is added after topActivity, the start order should still follow
+ // z-order, i.e. the topActivity will be started first.
+ mAtm.startProcessAsync(activity, false /* knownToBeDead */,
+ false /* isTop */, "test" /* hostingType */);
+ assertEquals(2, mAtm.mStartingProcessActivities.size());
+ assertEquals("Top record must be at the tail to start first",
+ topActivity, mAtm.mStartingProcessActivities.get(1));
final WindowProcessController proc = mSystemServicesTestRule.addProcess(
activity.packageName, activity.processName,
6789 /* pid */, activity.info.applicationInfo.uid);
try {
mRootWindowContainer.attachApplication(proc);
- verify(mSupervisor).realStartActivityLocked(eq(activity), eq(proc), anyBoolean(),
- anyBoolean());
+ verify(mSupervisor).realStartActivityLocked(eq(topActivity), eq(proc),
+ anyBoolean(), anyBoolean());
} catch (RemoteException e) {
e.rethrowAsRuntimeException();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 45dbea2..401964c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -805,18 +805,11 @@
final TestWindowContainer root2 = builder.setLayer(0).build();
+ assertEquals("Roots have the same z-order", 0, root.compareTo(root2));
assertEquals(0, root.compareTo(root));
assertEquals(-1, child1.compareTo(child2));
assertEquals(1, child2.compareTo(child1));
- boolean inTheSameTree = true;
- try {
- root.compareTo(root2);
- } catch (IllegalArgumentException e) {
- inTheSameTree = false;
- }
- assertFalse(inTheSameTree);
-
assertEquals(-1, child1.compareTo(child11));
assertEquals(1, child21.compareTo(root));
assertEquals(1, child21.compareTo(child12));
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index b518c60d..aebae4e 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -50,7 +50,6 @@
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
@@ -2636,9 +2635,9 @@
if (resultCode == SATELLITE_RESULT_SUCCESS) {
if (resultData.containsKey(KEY_REQUEST_PROVISION_SUBSCRIBER_ID_TOKEN)) {
List<ProvisionSubscriberId> list =
- Collections.singletonList(resultData.getParcelable(
+ resultData.getParcelableArrayList(
KEY_REQUEST_PROVISION_SUBSCRIBER_ID_TOKEN,
- ProvisionSubscriberId.class));
+ ProvisionSubscriberId.class);
executor.execute(() -> Binder.withCleanCallingIdentity(() ->
callback.onResult(list)));
} else {
@@ -2692,13 +2691,13 @@
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
if (resultCode == SATELLITE_RESULT_SUCCESS) {
- if (resultData.containsKey(KEY_SATELLITE_PROVISIONED)) {
+ if (resultData.containsKey(KEY_IS_SATELLITE_PROVISIONED)) {
boolean isIsProvisioned =
- resultData.getBoolean(KEY_SATELLITE_PROVISIONED);
+ resultData.getBoolean(KEY_IS_SATELLITE_PROVISIONED);
executor.execute(() -> Binder.withCleanCallingIdentity(() ->
callback.onResult(isIsProvisioned)));
} else {
- loge("KEY_REQUEST_PROVISION_TOKENS does not exist.");
+ loge("KEY_IS_SATELLITE_PROVISIONED does not exist.");
executor.execute(() -> Binder.withCleanCallingIdentity(() ->
callback.onError(new SatelliteException(
SATELLITE_RESULT_REQUEST_FAILED))));
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 7be52ea..3dbda7a 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3411,7 +3411,7 @@
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- void requestProvisionSubscriberIds(in ResultReceiver receiver);
+ void requestProvisionSubscriberIds(in ResultReceiver result);
/**
* Request to get provisioned status for given a satellite subscriber id.
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index 9a5e88b..238f2af 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -165,4 +165,37 @@
Corners.RIGHT_BOTTOM -> Pair(windowRect.right, windowRect.bottom)
}
}
+
+ /** Exit desktop mode by dragging the app handle to the top drag zone. */
+ fun exitDesktopWithDragToTopDragZone(
+ wmHelper: WindowManagerStateHelper,
+ device: UiDevice,
+ ) {
+ dragAppWindowToTopDragZone(wmHelper, device)
+ waitForTransitionToFullscreen(wmHelper)
+ }
+
+ private fun dragAppWindowToTopDragZone(wmHelper: WindowManagerStateHelper, device: UiDevice) {
+ val windowRect = wmHelper.getWindowRegion(innerHelper).bounds
+ val displayRect =
+ wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
+ ?: throw IllegalStateException("Default display is null")
+
+ val startX = windowRect.centerX()
+ val endX = displayRect.centerX()
+ val startY = windowRect.top
+ val endY = 0 // top of the screen
+
+ // drag the app window to top drag zone
+ device.drag(startX, startY, endX, endY, 100)
+ }
+
+ /** Wait for transition to full screen to finish. */
+ private fun waitForTransitionToFullscreen(wmHelper: WindowManagerStateHelper) {
+ wmHelper
+ .StateSyncBuilder()
+ .withFullScreenApp(innerHelper)
+ .withAppTransitionIdle()
+ .waitForAndVerify()
+ }
}
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
index 8433071..f385179 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
@@ -54,7 +54,6 @@
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -72,7 +71,6 @@
private static final ComponentName TEST_ACTIVITY = new ComponentName(
getInstrumentation().getTargetContext().getPackageName(),
MainActivity.class.getName());
- private static final long WAIT_TIME_MS = 3000L;
private final Context mContext = getInstrumentation().getTargetContext();
private final InputMethodManager mInputMethodManager =
mContext.getSystemService(InputMethodManager.class);
@@ -111,7 +109,6 @@
assertPassengerImeHidden();
}
- @Ignore("b/350562427")
@Test
public void passengerShowImeNotAffectDriver() throws Exception {
assertDriverImeHidden();
@@ -259,8 +256,6 @@
float[] driverEditTextCenter = mActivity.getEditTextCenter();
SystemUtil.runShellCommand(mUiAutomation, String.format("input tap %f %f",
driverEditTextCenter[0], driverEditTextCenter[1]));
- // TODO(b/350562427): get rid of Thread.sleep().
- Thread.sleep(WAIT_TIME_MS);
}
private void movePassengerDisplayToTop() throws Exception {
@@ -276,8 +271,6 @@
final int passengerDisplayId = receivedBundle.getInt(KEY_DISPLAY_ID);
SystemUtil.runShellCommand(mUiAutomation, String.format("input -d %d tap %f %f",
passengerDisplayId, passengerEditTextCenter[0], passengerEditTextCenter[1]));
- // TODO(b/350562427): get rid of Thread.sleep().
- Thread.sleep(WAIT_TIME_MS);
}
/**
diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp
index 02e4beae..8ae55b8 100644
--- a/tools/aapt2/util/Files.cpp
+++ b/tools/aapt2/util/Files.cpp
@@ -189,7 +189,7 @@
base->append(part.data(), part.size());
}
-std::string BuildPath(std::vector<const StringPiece>&& args) {
+std::string BuildPath(const std::vector<StringPiece>& args) {
if (args.empty()) {
return "";
}
diff --git a/tools/aapt2/util/Files.h b/tools/aapt2/util/Files.h
index 42eeaf2..c1a42446 100644
--- a/tools/aapt2/util/Files.h
+++ b/tools/aapt2/util/Files.h
@@ -60,7 +60,7 @@
void AppendPath(std::string* base, android::StringPiece part);
// Concatenates the list of paths and separates each part with the directory separator.
-std::string BuildPath(std::vector<const android::StringPiece>&& args);
+std::string BuildPath(const std::vector<android::StringPiece>& args);
// Makes all the directories in `path`. The last element in the path is interpreted as a directory.
bool mkdirs(const std::string& path);