Merge "Small refactor to remove unecessary inheritance" into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 457fdd8..aafa1f6 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -448,6 +448,12 @@
bug: "292269949"
}
+flag {
+ name: "enable_state_manager_proto_log"
+ namespace: "launcher"
+ description: "Enables tracking state manager logs in ProtoLog"
+ bug: "292269949"
+}
flag {
name: "coordinate_workspace_scale"
diff --git a/quickstep/res/layout/customizable_taskbar.xml b/quickstep/res/layout/customizable_taskbar.xml
index e1a80ae..d988cbc 100644
--- a/quickstep/res/layout/customizable_taskbar.xml
+++ b/quickstep/res/layout/customizable_taskbar.xml
@@ -51,20 +51,26 @@
android:layout_width="match_parent"
android:layout_height="match_parent"/>
- <com.android.launcher3.taskbar.bubbles.BubbleBarView
- android:id="@+id/taskbar_bubbles"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/bubblebar_size_with_pointer"
- android:layout_gravity="bottom|end"
- android:layout_marginHorizontal="@dimen/transient_taskbar_bottom_margin"
- android:paddingTop="@dimen/bubblebar_pointer_visible_size"
- android:paddingEnd="@dimen/taskbar_icon_spacing"
- android:paddingStart="@dimen/taskbar_icon_spacing"
- android:visibility="gone"
- android:gravity="center"
- android:clipChildren="false"
- android:elevation="@dimen/bubblebar_elevation"
- />
+ <FrameLayout
+ android:id="@+id/taskbar_bubbles_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="false">
+
+ <com.android.launcher3.taskbar.bubbles.BubbleBarView
+ android:id="@+id/taskbar_bubbles"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/bubblebar_size_with_pointer"
+ android:layout_gravity="bottom|end"
+ android:layout_marginHorizontal="@dimen/transient_taskbar_bottom_margin"
+ android:paddingTop="@dimen/bubblebar_pointer_visible_size"
+ android:paddingEnd="@dimen/taskbar_icon_spacing"
+ android:paddingStart="@dimen/taskbar_icon_spacing"
+ android:visibility="gone"
+ android:gravity="center"
+ android:clipChildren="false"
+ android:elevation="@dimen/bubblebar_elevation" />
+ </FrameLayout>
<com.android.launcher3.taskbar.navbutton.NearestTouchFrame
android:id="@+id/navbuttons_view"
diff --git a/quickstep/res/layout/taskbar.xml b/quickstep/res/layout/taskbar.xml
index e8f3d9d..54f9ae8 100644
--- a/quickstep/res/layout/taskbar.xml
+++ b/quickstep/res/layout/taskbar.xml
@@ -35,17 +35,24 @@
android:layout_width="match_parent"
android:layout_height="match_parent"/>
- <com.android.launcher3.taskbar.bubbles.BubbleBarView
- android:id="@+id/taskbar_bubbles"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/bubblebar_size_with_pointer"
- android:layout_marginHorizontal="@dimen/transient_taskbar_bottom_margin"
- android:paddingTop="@dimen/bubblebar_pointer_visible_size"
- android:visibility="gone"
- android:gravity="center"
- android:layout_gravity="bottom"
- android:clipChildren="false"
- android:elevation="@dimen/bubblebar_elevation" />
+ <FrameLayout
+ android:id="@+id/taskbar_bubbles_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="false">
+
+ <com.android.launcher3.taskbar.bubbles.BubbleBarView
+ android:id="@+id/taskbar_bubbles"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/bubblebar_size_with_pointer"
+ android:layout_marginHorizontal="@dimen/transient_taskbar_bottom_margin"
+ android:paddingTop="@dimen/bubblebar_pointer_visible_size"
+ android:visibility="gone"
+ android:gravity="center"
+ android:layout_gravity="bottom"
+ android:clipChildren="false"
+ android:elevation="@dimen/bubblebar_elevation" />
+ </FrameLayout>
<com.android.launcher3.taskbar.navbutton.NearestTouchFrame
android:id="@+id/navbuttons_view"
diff --git a/quickstep/res/layout/transient_taskbar.xml b/quickstep/res/layout/transient_taskbar.xml
index f3c3383..3ec8046 100644
--- a/quickstep/res/layout/transient_taskbar.xml
+++ b/quickstep/res/layout/transient_taskbar.xml
@@ -38,18 +38,24 @@
android:layout_width="match_parent"
android:layout_height="match_parent"/>
- <com.android.launcher3.taskbar.bubbles.BubbleBarView
- android:id="@+id/taskbar_bubbles"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/bubblebar_size_with_pointer"
- android:layout_gravity="bottom|end"
- android:layout_marginHorizontal="@dimen/transient_taskbar_bottom_margin"
- android:paddingTop="@dimen/bubblebar_pointer_visible_size"
- android:visibility="gone"
- android:gravity="center"
- android:clipChildren="false"
- android:elevation="@dimen/bubblebar_elevation"
- />
+ <FrameLayout
+ android:id="@+id/taskbar_bubbles_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="false">
+
+ <com.android.launcher3.taskbar.bubbles.BubbleBarView
+ android:id="@+id/taskbar_bubbles"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/bubblebar_size_with_pointer"
+ android:layout_gravity="bottom|end"
+ android:layout_marginHorizontal="@dimen/transient_taskbar_bottom_margin"
+ android:paddingTop="@dimen/bubblebar_pointer_visible_size"
+ android:visibility="gone"
+ android:gravity="center"
+ android:clipChildren="false"
+ android:elevation="@dimen/bubblebar_elevation" />
+ </FrameLayout>
<com.android.launcher3.taskbar.navbutton.NearestTouchFrame
android:id="@+id/navbuttons_view"
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 24e4d2b..1b9614a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -71,6 +71,7 @@
import android.view.View;
import android.view.WindowInsets;
import android.view.WindowManager;
+import android.widget.FrameLayout;
import android.widget.Toast;
import android.window.RemoteTransition;
@@ -268,8 +269,10 @@
NearestTouchFrame navButtonsView = mDragLayer.findViewById(R.id.navbuttons_view);
StashedHandleView stashedHandleView = mDragLayer.findViewById(R.id.stashed_handle);
BubbleBarView bubbleBarView = null;
+ FrameLayout bubbleBarContainer = null;
if (isTransientTaskbar || Flags.enableBubbleBarInPersistentTaskBar()) {
bubbleBarView = mDragLayer.findViewById(R.id.taskbar_bubbles);
+ bubbleBarContainer = mDragLayer.findViewById(R.id.taskbar_bubbles_container);
}
StashedHandleView bubbleHandleView = mDragLayer.findViewById(R.id.stashed_bubble_handle);
@@ -296,7 +299,7 @@
: new PersistentBubbleStashController(dimensionsProvider);
bubbleControllersOptional = Optional.of(new BubbleControllers(
new BubbleBarController(this, bubbleBarView),
- new BubbleBarViewController(this, bubbleBarView),
+ new BubbleBarViewController(this, bubbleBarView, bubbleBarContainer),
bubbleStashController,
bubbleHandleController,
new BubbleDragController(this),
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index 219a24a..4a85acc 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -195,11 +195,12 @@
};
if (taskbarDesktopModeController.getAreDesktopTasksVisible()) {
- mCornerRoundness.updateValue(taskbarDesktopModeController.getTaskbarCornerRoundness(
- mSharedState.showCornerRadiusInDesktopMode));
+ mCornerRoundness.value = taskbarDesktopModeController.getTaskbarCornerRoundness(
+ mSharedState.showCornerRadiusInDesktopMode);
} else {
- mCornerRoundness.updateValue(TaskbarBackgroundRenderer.MAX_ROUNDNESS);
+ mCornerRoundness.value = TaskbarBackgroundRenderer.MAX_ROUNDNESS;
}
+ updateCornerRoundness();
onPostInit();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index fc66bec..707d4b3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -786,6 +786,9 @@
}
protected void stashHotseat(boolean stash) {
+ // align taskbar with the hotseat icons before performing any animation
+ mControllers.taskbarViewController.setLauncherIconAlignment(/* alignmentRatio = */ 1,
+ mLauncher.getDeviceProfile());
TaskbarStashController stashController = mControllers.taskbarStashController;
stashController.updateStateForFlag(FLAG_STASHED_FOR_BUBBLES, stash);
Runnable swapHotseatWithTaskbar = new Runnable() {
@@ -898,19 +901,6 @@
mHotseatTranslationXAnimation.cancel();
mHotseatTranslationXAnimation = null;
}
- Runnable postAnimationAction = new Runnable() {
- @Override
- public void run() {
- mHotseatTranslationXAnimation = null;
- // We only need to align the task bar when on launcher home screen
- if (mControllers.taskbarStashController.isOnHome()) {
- mControllers.taskbarViewController.setLauncherIconAlignment(
- /* alignmentRatio = */ 1,
- mLauncher.getDeviceProfile()
- );
- }
- }
- };
Hotseat hotseat = mLauncher.getHotseat();
AnimatorSet translationXAnimation = new AnimatorSet();
MultiProperty iconsTranslationX = mLauncher.getHotseat()
@@ -933,14 +923,12 @@
}
}
if (!animate) {
- postAnimationAction.run();
return;
}
mHotseatTranslationXAnimation = translationXAnimation;
translationXAnimation.setStartDelay(FADE_OUT_ANIM_POSITION_DURATION_MS);
translationXAnimation.setDuration(FADE_IN_ANIM_ALPHA_DURATION_MS);
translationXAnimation.setInterpolator(Interpolators.EMPHASIZED);
- translationXAnimation.addListener(AnimatorListeners.forEndCallback(postAnimationAction));
translationXAnimation.start();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarSwipeController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarSwipeController.kt
index bc562a6..2d3642b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarSwipeController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarSwipeController.kt
@@ -21,15 +21,14 @@
import androidx.annotation.VisibleForTesting
import androidx.core.animation.doOnEnd
import androidx.dynamicanimation.animation.SpringForce
-import com.android.launcher3.R
import com.android.launcher3.anim.AnimatedFloat
import com.android.launcher3.anim.SpringAnimationBuilder
import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.launcher3.taskbar.TaskbarThresholdUtils
-import com.android.launcher3.taskbar.bubbles.BubbleBarSwipeController.StartState.COLLAPSED
-import com.android.launcher3.taskbar.bubbles.BubbleBarSwipeController.StartState.EXPANDED
-import com.android.launcher3.taskbar.bubbles.BubbleBarSwipeController.StartState.STASHED
-import com.android.launcher3.taskbar.bubbles.BubbleBarSwipeController.StartState.UNKNOWN
+import com.android.launcher3.taskbar.bubbles.BubbleBarSwipeController.BarState.COLLAPSED
+import com.android.launcher3.taskbar.bubbles.BubbleBarSwipeController.BarState.EXPANDED
+import com.android.launcher3.taskbar.bubbles.BubbleBarSwipeController.BarState.STASHED
+import com.android.launcher3.taskbar.bubbles.BubbleBarSwipeController.BarState.UNKNOWN
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController
import com.android.launcher3.touch.OverScroll
@@ -46,11 +45,9 @@
private val animatedSwipeTranslation = AnimatedFloat(this::onSwipeUpdate)
private val unstashThreshold: Int
- private val expandThreshold: Int
private val maxOverscroll: Int
- private val stashThreshold: Int
- private var swipeState: SwipeState = SwipeState()
+ private var swipeState: SwipeState = SwipeState(startState = UNKNOWN)
constructor(tac: TaskbarActivityContext) : this(tac, DefaultDimensionProvider(tac))
@@ -58,9 +55,7 @@
constructor(context: Context, dimensionProvider: DimensionProvider) {
this.context = context
unstashThreshold = dimensionProvider.unstashThreshold
- expandThreshold = dimensionProvider.expandThreshold
maxOverscroll = dimensionProvider.maxOverscroll
- stashThreshold = dimensionProvider.stashThreshold
}
fun init(bubbleControllers: BubbleControllers) {
@@ -80,7 +75,7 @@
bubbleStashController.isBubbleBarVisible() -> COLLAPSED
else -> UNKNOWN
}
- swipeState = SwipeState(startState = startState)
+ swipeState = SwipeState(startState = startState, currentState = startState)
}
/** Update swipe distance to [dy] */
@@ -90,46 +85,25 @@
}
animatedSwipeTranslation.updateValue(dy)
- val prevState = swipeState
- // We can pass unstash threshold once per gesture, keep it true if it happened once
- val passedUnstashThreshold = isUnstash(dy) || prevState.passedUnstashThreshold
- // Expand happens at the end of the gesture, always keep the current value
- val passedExpandThreshold = isExpand(dy)
- // Stash happens at the end of the gesture, always keep the current value
- val passedStashThreshold = isStash(dy)
-
- if (
- passedUnstashThreshold != prevState.passedUnstashThreshold ||
- passedExpandThreshold != prevState.passedExpandThreshold ||
- passedStashThreshold != prevState.passedStashThreshold
- ) {
- swipeState =
- swipeState.copy(
- passedUnstashThreshold = passedUnstashThreshold,
- passedExpandThreshold = passedExpandThreshold,
- passedStashThreshold = passedStashThreshold,
- )
- }
-
- if (
- swipeState.startState == STASHED &&
- swipeState.passedUnstashThreshold &&
- !prevState.passedUnstashThreshold
- ) {
- bubbleStashController.showBubbleBar(expandBubbles = false)
+ swipeState.passedUnstash = isUnstash(dy)
+ // Tracking swipe gesture if we pass unstash threshold at least once during gesture
+ swipeState.isSwipe = swipeState.isSwipe || swipeState.passedUnstash
+ when {
+ canUnstash() && swipeState.passedUnstash -> {
+ swipeState.currentState = COLLAPSED
+ bubbleStashController.showBubbleBar(expandBubbles = false)
+ }
+ canStash() && !swipeState.passedUnstash -> {
+ swipeState.currentState = STASHED
+ bubbleStashController.stashBubbleBar()
+ }
}
}
/** Finish tracking swipe gesture. Animate views back to resting state */
fun finish() {
- when {
- swipeState.passedExpandThreshold &&
- swipeState.startState in setOf(STASHED, COLLAPSED) -> {
- bubbleStashController.showBubbleBar(expandBubbles = true)
- }
- swipeState.passedStashThreshold && swipeState.startState == COLLAPSED -> {
- bubbleStashController.stashBubbleBar()
- }
+ if (swipeState.passedUnstash && swipeState.startState in setOf(STASHED, COLLAPSED)) {
+ bubbleStashController.showBubbleBar(expandBubbles = true)
}
if (animatedSwipeTranslation.value == 0f) {
reset()
@@ -140,15 +114,21 @@
/** Returns `true` if we are tracking a swipe gesture */
fun isSwipeGesture(): Boolean {
- return swipeState.passedUnstashThreshold ||
- swipeState.passedExpandThreshold ||
- swipeState.passedStashThreshold
+ return swipeState.isSwipe
}
private fun canHandleSwipe(dy: Float): Boolean {
return when (swipeState.startState) {
- STASHED -> dy < 0 // stashed bar only handles swipe up
- COLLAPSED -> true // collapsed bar can be swiped in either direction
+ STASHED -> {
+ if (swipeState.currentState == COLLAPSED) {
+ // if we have unstashed the bar, allow swipe in both directions
+ true
+ } else {
+ // otherwise, only allow swipe up on stash handle
+ dy < 0
+ }
+ }
+ COLLAPSED -> dy < 0 // collapsed bar can only be swiped up
UNKNOWN,
EXPANDED -> false // expanded bar can't be swiped
}
@@ -158,12 +138,13 @@
return dy < -unstashThreshold
}
- private fun isExpand(dy: Float): Boolean {
- return dy < -expandThreshold
+ private fun canStash(): Boolean {
+ // Only allow stashing if we started from stashed state
+ return swipeState.startState == STASHED && swipeState.currentState == COLLAPSED
}
- private fun isStash(dy: Float): Boolean {
- return dy > stashThreshold
+ private fun canUnstash(): Boolean {
+ return swipeState.currentState == STASHED
}
private fun reset() {
@@ -175,7 +156,7 @@
}
}
springAnimation = null
- swipeState = SwipeState()
+ swipeState = SwipeState(startState = UNKNOWN)
}
private fun onSwipeUpdate(value: Float) {
@@ -197,13 +178,13 @@
}
internal data class SwipeState(
- val startState: StartState = UNKNOWN,
- val passedUnstashThreshold: Boolean = false,
- val passedExpandThreshold: Boolean = false,
- val passedStashThreshold: Boolean = false,
+ val startState: BarState,
+ var currentState: BarState = UNKNOWN,
+ var passedUnstash: Boolean = false,
+ var isSwipe: Boolean = false,
)
- internal enum class StartState {
+ internal enum class BarState {
UNKNOWN,
STASHED,
COLLAPSED,
@@ -214,17 +195,13 @@
@VisibleForTesting
interface DimensionProvider {
val unstashThreshold: Int
- val expandThreshold: Int
val maxOverscroll: Int
- val stashThreshold: Int
}
private class DefaultDimensionProvider(taskbarActivityContext: TaskbarActivityContext) :
DimensionProvider {
override val unstashThreshold: Int
- override val expandThreshold: Int
override val maxOverscroll: Int
- override val stashThreshold: Int
init {
val resources = taskbarActivityContext.resources
@@ -233,14 +210,7 @@
resources,
taskbarActivityContext.deviceProfile,
)
- // TODO(325673340): review threshold with ux
- expandThreshold =
- TaskbarThresholdUtils.getAppWindowThreshold(
- resources,
- taskbarActivityContext.deviceProfile,
- )
maxOverscroll = taskbarActivityContext.deviceProfile.heightPx - unstashThreshold
- stashThreshold = resources.getDimensionPixelSize(R.dimen.taskbar_to_nav_threshold)
}
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 7fed381..c5d649e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -1492,6 +1492,38 @@
return bubbles;
}
+ /**
+ * Returns the distance between the top left corner of the bubble bar to the center of the dot
+ * of the selected bubble.
+ */
+ PointF getSelectedBubbleDotDistanceFromTopLeft() {
+ if (mSelectedBubbleView == null) {
+ return new PointF(0, 0);
+ }
+ final int indexOfSelectedBubble = indexOfChild(mSelectedBubbleView);
+ final boolean onLeft = mBubbleBarLocation.isOnLeft(isLayoutRtl());
+ final float selectedBubbleTx = isExpanded()
+ ? getExpandedBubbleTranslationX(indexOfSelectedBubble, getChildCount(), onLeft)
+ : getCollapsedBubbleTranslationX(indexOfSelectedBubble, getChildCount(), onLeft);
+ PointF selectedBubbleDotCenter = mSelectedBubbleView.getDotCenter();
+
+ return new PointF(
+ selectedBubbleTx + selectedBubbleDotCenter.x,
+ mBubbleBarPadding + mPointerSize + selectedBubbleDotCenter.y);
+ }
+
+ int getSelectedBubbleDotColor() {
+ return mSelectedBubbleView == null ? 0 : mSelectedBubbleView.getDotColor();
+ }
+
+ int getPointerSize() {
+ return mPointerSize;
+ }
+
+ float getBubbleElevation() {
+ return mBubbleElevation;
+ }
+
/** Interface for BubbleBarView to communicate with its controller. */
interface Controller {
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index c164eec..69e1d43 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -29,6 +29,7 @@
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
+import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -42,6 +43,7 @@
import com.android.launcher3.taskbar.TaskbarInsetsController;
import com.android.launcher3.taskbar.TaskbarStashController;
import com.android.launcher3.taskbar.bubbles.animation.BubbleBarViewAnimator;
+import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutPositioner;
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.MultiValueAlpha;
@@ -63,6 +65,8 @@
private static final float APP_ICON_SMALL_DP = 44f;
private static final float APP_ICON_MEDIUM_DP = 48f;
private static final float APP_ICON_LARGE_DP = 52f;
+ /** The dot size is defined as a percentage of the icon size. */
+ private static final float DOT_TO_BUBBLE_SIZE_RATIO = 0.228f;
private final SystemUiProxy mSystemUiProxy;
private final TaskbarActivityContext mActivity;
private final BubbleBarView mBarView;
@@ -119,7 +123,8 @@
@Nullable
private BubbleBarBoundsChangeListener mBoundsChangeListener;
- public BubbleBarViewController(TaskbarActivityContext activity, BubbleBarView barView) {
+ public BubbleBarViewController(TaskbarActivityContext activity, BubbleBarView barView,
+ FrameLayout bubbleBarContainer) {
mActivity = activity;
mBarView = barView;
mSystemUiProxy = SystemUiProxy.INSTANCE.get(mActivity);
@@ -208,6 +213,59 @@
};
}
+ private BubbleBarFlyoutPositioner createFlyoutPositioner() {
+ return new BubbleBarFlyoutPositioner() {
+
+ @Override
+ public boolean isOnLeft() {
+ return mBarView.getBubbleBarLocation().isOnLeft(mBarView.isLayoutRtl());
+ }
+
+ @Override
+ public float getTargetTy() {
+ return mBarView.getTranslationY() - mBarView.getHeight();
+ }
+
+ @Override
+ @NonNull
+ public PointF getDistanceToCollapsedPosition() {
+ // the flyout animates from the selected bubble dot. calculate the distance it needs
+ // to translate itself to its starting position.
+ PointF distanceToDotCenter = mBarView.getSelectedBubbleDotDistanceFromTopLeft();
+
+ // if we're gravitating left, return the distance between the top left corner of the
+ // bubble bar and the bottom left corner of the dot.
+ // if we're gravitating right, return the distance between the top right corner of
+ // the bubble bar and the bottom right corner of the dot.
+ float distanceX = isOnLeft()
+ ? distanceToDotCenter.x - getCollapsedSize() / 2
+ : mBarView.getWidth() - distanceToDotCenter.x - getCollapsedSize() / 2;
+ float distanceY = distanceToDotCenter.y + getCollapsedSize() / 2;
+ return new PointF(distanceX, distanceY);
+ }
+
+ @Override
+ public float getCollapsedSize() {
+ return mIconSize * DOT_TO_BUBBLE_SIZE_RATIO;
+ }
+
+ @Override
+ public int getCollapsedColor() {
+ return mBarView.getSelectedBubbleDotColor();
+ }
+
+ @Override
+ public float getCollapsedElevation() {
+ return mBarView.getBubbleElevation();
+ }
+
+ @Override
+ public float getDistanceToRevealTriangle() {
+ return getDistanceToCollapsedPosition().y - mBarView.getPointerSize();
+ }
+ };
+ }
+
private void onBubbleClicked(BubbleView bubbleView) {
bubbleView.markSeen();
BubbleBarItem bubble = bubbleView.getBubble();
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
index 561df5c..707655c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
@@ -22,6 +22,7 @@
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Path;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
@@ -67,8 +68,7 @@
private float mAnimatingToDotScale;
// The current scale value of the dot
private float mDotScale;
-
- private boolean mProvideShadowOutline = true;
+ private boolean mDotSuppressedForBubbleUpdate = false;
// TODO: (b/273310265) handle RTL
// Whether the bubbles are positioned on the left or right side of the screen
@@ -300,6 +300,10 @@
}
void updateDotVisibility(boolean animate) {
+ if (mDotSuppressedForBubbleUpdate) {
+ // if the dot is suppressed for
+ return;
+ }
final float targetScale = hasUnseenContent() ? 1f : 0f;
if (animate) {
animateDotScale(targetScale);
@@ -317,6 +321,20 @@
}
}
+ /**
+ * Suppresses or un-suppresses drawing the dot due to an update for this bubble.
+ *
+ * <p>If the dot is being suppressed and is already visible, it remains visible because it is
+ * used as a starting point for the animation. If the dot is being unsuppressed, it is
+ * redrawn if needed.
+ */
+ public void suppressDotForBubbleUpdate(boolean suppress) {
+ mDotSuppressedForBubbleUpdate = suppress;
+ if (!suppress) {
+ showDotIfNeeded(/* animate= */ false);
+ }
+ }
+
boolean hasUnseenContent() {
return mBubble != null
&& mBubble instanceof BubbleBarBubble
@@ -353,8 +371,8 @@
}
void showDotIfNeeded(boolean animate) {
- // only show the dot if we have unseen content
- if (!hasUnseenContent()) {
+ // only show the dot if we have unseen content and it's not suppressed
+ if (!hasUnseenContent() || mDotSuppressedForBubbleUpdate) {
return;
}
if (animate) {
@@ -406,6 +424,23 @@
}).start();
}
+ /**
+ * Returns the distance from the top left corner of this bubble view to the center of its dot.
+ */
+ public PointF getDotCenter() {
+ float[] dotPosition =
+ mOnLeft ? mDotRenderer.getLeftDotPosition() : mDotRenderer.getRightDotPosition();
+ getDrawingRect(mTempBounds);
+ float dotCenterX = mTempBounds.width() * dotPosition[0];
+ float dotCenterY = mTempBounds.height() * dotPosition[1];
+ return new PointF(dotCenterX, dotCenterY);
+ }
+
+ /** Returns the dot color. */
+ public int getDotColor() {
+ return mDotColor;
+ }
+
@Override
public String toString() {
String toString = mBubble != null ? mBubble.getKey() : "null";
diff --git a/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java b/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
index eef1f96..341c868 100644
--- a/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
+++ b/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
@@ -20,6 +20,7 @@
import com.android.launcher3.dagger.LauncherBaseAppComponent;
import com.android.launcher3.model.WellbeingModel;
import com.android.quickstep.logging.SettingsChangeLogger;
+import com.android.quickstep.util.AsyncClockEventDelegate;
/**
* Launcher Quickstep base component for Dagger injection.
@@ -33,4 +34,6 @@
SettingsChangeLogger getSettingsChangeLogger();
WellbeingModel getWellbeingModel();
+
+ AsyncClockEventDelegate getAsyncClockEventDelegate();
}
diff --git a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
index b53650e..44b8b8d 100644
--- a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
+++ b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
@@ -34,6 +34,7 @@
import com.android.quickstep.task.viewmodel.TaskContainerData
import com.android.quickstep.task.viewmodel.TaskOverlayViewModel
import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
+import com.android.quickstep.task.viewmodel.TaskThumbnailViewModelImpl
import com.android.quickstep.task.viewmodel.TaskViewData
import com.android.quickstep.task.viewmodel.TaskViewModel
import com.android.quickstep.views.TaskViewType
@@ -180,7 +181,7 @@
TaskContainerData::class.java -> TaskContainerData()
TaskThumbnailViewData::class.java -> TaskThumbnailViewData()
TaskThumbnailViewModel::class.java ->
- TaskThumbnailViewModel(
+ TaskThumbnailViewModelImpl(
recentsViewData = inject(),
taskViewData = inject(scopeId, extras),
taskContainerData = inject(scopeId),
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
index 4970685..f55462a 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
@@ -10,144 +10,40 @@
* 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 goveryning permissions and
+ * See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep.task.viewmodel
-import android.annotation.ColorInt
-import android.app.ActivityTaskManager.INVALID_TASK_ID
import android.graphics.Matrix
-import android.util.Log
-import androidx.core.graphics.ColorUtils
-import com.android.quickstep.recents.data.RecentTasksRepository
-import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
-import com.android.quickstep.recents.usecase.ThumbnailPositionState
-import com.android.quickstep.recents.viewmodel.RecentsViewData
-import com.android.quickstep.task.thumbnail.SplashAlphaUseCase
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.SnapshotSplash
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
-import com.android.systemui.shared.recents.model.Task
-import kotlin.math.max
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.flow.StateFlow
-@OptIn(ExperimentalCoroutinesApi::class)
-class TaskThumbnailViewModel(
- recentsViewData: RecentsViewData,
- taskViewData: TaskViewData,
- taskContainerData: TaskContainerData,
- private val tasksRepository: RecentTasksRepository,
- private val getThumbnailPositionUseCase: GetThumbnailPositionUseCase,
- private val splashAlphaUseCase: SplashAlphaUseCase,
-) {
- private val task = MutableStateFlow<Flow<Task?>>(flowOf(null))
- private val splashProgress = MutableStateFlow(flowOf(0f))
- private var taskId: Int = INVALID_TASK_ID
-
+/** ViewModel for representing TaskThumbnails */
+interface TaskThumbnailViewModel {
/**
* Progress for changes in corner radius. progress: 0 = overview corner radius; 1 = fullscreen
* corner radius.
*/
- val cornerRadiusProgress =
- if (taskViewData.isOutlineFormedByThumbnailView) recentsViewData.fullscreenProgress
- else MutableStateFlow(1f).asStateFlow()
+ val cornerRadiusProgress: StateFlow<Float>
- val inheritedScale =
- combine(recentsViewData.scale, taskViewData.scale) { recentsScale, taskScale ->
- recentsScale * taskScale
- }
+ /** The accumulated View.scale value for parent Views up to and including RecentsView */
+ val inheritedScale: Flow<Float>
- val dimProgress: Flow<Float> =
- combine(taskContainerData.taskMenuOpenProgress, recentsViewData.tintAmount) {
- taskMenuOpenProgress,
- tintAmount ->
- max(taskMenuOpenProgress * MAX_SCRIM_ALPHA, tintAmount)
- }
- val splashAlpha = splashProgress.flatMapLatest { it }
+ /** Provides the level of dimming that the View should have */
+ val dimProgress: Flow<Float>
- private val isLiveTile =
- combine(
- task.flatMapLatest { it }.map { it?.key?.id }.distinctUntilChanged(),
- recentsViewData.runningTaskIds,
- recentsViewData.runningTaskShowScreenshot,
- ) { taskId, runningTaskIds, runningTaskShowScreenshot ->
- runningTaskIds.contains(taskId) && !runningTaskShowScreenshot
- }
- .distinctUntilChanged()
+ /** Provides the alpha of the splash icon */
+ val splashAlpha: Flow<Float>
- val uiState: Flow<TaskThumbnailUiState> =
- combine(task.flatMapLatest { it }, isLiveTile) { taskVal, isRunning ->
- // TODO(b/369339561) This log is firing a lot. Reduce emissions from TasksRepository
- // then re-enable this log.
- // Log.d(
- // TAG,
- // "Received task and / or live tile update. taskVal: $taskVal"
- // + " isRunning: $isRunning.",
- // )
- when {
- taskVal == null -> Uninitialized
- isRunning -> LiveTile
- isBackgroundOnly(taskVal) ->
- BackgroundOnly(taskVal.colorBackground.removeAlpha())
- isSnapshotSplashState(taskVal) ->
- SnapshotSplash(createSnapshotState(taskVal), taskVal.icon)
- else -> Uninitialized
- }
- }
- .distinctUntilChanged()
+ /** Provides the UiState by which the task thumbnail can be represented */
+ val uiState: Flow<TaskThumbnailUiState>
- fun bind(taskId: Int) {
- Log.d(TAG, "bind taskId: $taskId")
- this.taskId = taskId
- task.value = tasksRepository.getTaskDataById(taskId)
- splashProgress.value = splashAlphaUseCase.execute(taskId)
- }
+ /** Attaches this ViewModel to a specific task id for it to provide data from. */
+ fun bind(taskId: Int)
- fun getThumbnailPositionState(width: Int, height: Int, isRtl: Boolean): Matrix {
- return runBlocking {
- when (
- val thumbnailPositionState =
- getThumbnailPositionUseCase.run(taskId, width, height, isRtl)
- ) {
- is ThumbnailPositionState.MatrixScaling -> thumbnailPositionState.matrix
- is ThumbnailPositionState.MissingThumbnail -> Matrix.IDENTITY_MATRIX
- }
- }
- }
-
- private fun isBackgroundOnly(task: Task): Boolean = task.isLocked || task.thumbnail == null
-
- private fun isSnapshotSplashState(task: Task): Boolean {
- val thumbnailPresent = task.thumbnail?.thumbnail != null
- val taskLocked = task.isLocked
-
- return thumbnailPresent && !taskLocked
- }
-
- private fun createSnapshotState(task: Task): Snapshot {
- val thumbnailData = task.thumbnail
- val bitmap = thumbnailData?.thumbnail!!
- return Snapshot(bitmap, thumbnailData.rotation, task.colorBackground.removeAlpha())
- }
-
- @ColorInt private fun Int.removeAlpha(): Int = ColorUtils.setAlphaComponent(this, 0xff)
-
- private companion object {
- const val MAX_SCRIM_ALPHA = 0.4f
- const val TAG = "TaskThumbnailViewModel"
- }
+ /** Returns a Matrix which can be applied to the snapshot */
+ fun getThumbnailPositionState(width: Int, height: Int, isRtl: Boolean): Matrix
}
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
new file mode 100644
index 0000000..bd47cec
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.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 goveryning permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.task.viewmodel
+
+import android.annotation.ColorInt
+import android.app.ActivityTaskManager.INVALID_TASK_ID
+import android.graphics.Matrix
+import android.util.Log
+import androidx.core.graphics.ColorUtils
+import com.android.quickstep.recents.data.RecentTasksRepository
+import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
+import com.android.quickstep.recents.usecase.ThumbnailPositionState
+import com.android.quickstep.recents.viewmodel.RecentsViewData
+import com.android.quickstep.task.thumbnail.SplashAlphaUseCase
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.SnapshotSplash
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
+import com.android.systemui.shared.recents.model.Task
+import kotlin.math.max
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.runBlocking
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class TaskThumbnailViewModelImpl(
+ recentsViewData: RecentsViewData,
+ taskViewData: TaskViewData,
+ taskContainerData: TaskContainerData,
+ private val tasksRepository: RecentTasksRepository,
+ private val getThumbnailPositionUseCase: GetThumbnailPositionUseCase,
+ private val splashAlphaUseCase: SplashAlphaUseCase,
+) : TaskThumbnailViewModel {
+ private val task = MutableStateFlow<Flow<Task?>>(flowOf(null))
+ private val splashProgress = MutableStateFlow(flowOf(0f))
+ private var taskId: Int = INVALID_TASK_ID
+
+ override val cornerRadiusProgress =
+ if (taskViewData.isOutlineFormedByThumbnailView) recentsViewData.fullscreenProgress
+ else MutableStateFlow(1f).asStateFlow()
+
+ override val inheritedScale =
+ combine(recentsViewData.scale, taskViewData.scale) { recentsScale, taskScale ->
+ recentsScale * taskScale
+ }
+
+ override val dimProgress: Flow<Float> =
+ combine(taskContainerData.taskMenuOpenProgress, recentsViewData.tintAmount) {
+ taskMenuOpenProgress,
+ tintAmount ->
+ max(taskMenuOpenProgress * MAX_SCRIM_ALPHA, tintAmount)
+ }
+ override val splashAlpha = splashProgress.flatMapLatest { it }
+
+ private val isLiveTile =
+ combine(
+ task.flatMapLatest { it }.map { it?.key?.id }.distinctUntilChanged(),
+ recentsViewData.runningTaskIds,
+ recentsViewData.runningTaskShowScreenshot,
+ ) { taskId, runningTaskIds, runningTaskShowScreenshot ->
+ runningTaskIds.contains(taskId) && !runningTaskShowScreenshot
+ }
+ .distinctUntilChanged()
+
+ override val uiState: Flow<TaskThumbnailUiState> =
+ combine(task.flatMapLatest { it }, isLiveTile) { taskVal, isRunning ->
+ // TODO(b/369339561) This log is firing a lot. Reduce emissions from TasksRepository
+ // then re-enable this log.
+ // Log.d(
+ // TAG,
+ // "Received task and / or live tile update. taskVal: $taskVal"
+ // + " isRunning: $isRunning.",
+ // )
+ when {
+ taskVal == null -> Uninitialized
+ isRunning -> LiveTile
+ isBackgroundOnly(taskVal) ->
+ BackgroundOnly(taskVal.colorBackground.removeAlpha())
+ isSnapshotSplashState(taskVal) ->
+ SnapshotSplash(createSnapshotState(taskVal), taskVal.icon)
+ else -> Uninitialized
+ }
+ }
+ .distinctUntilChanged()
+
+ override fun bind(taskId: Int) {
+ Log.d(TAG, "bind taskId: $taskId")
+ this.taskId = taskId
+ task.value = tasksRepository.getTaskDataById(taskId)
+ splashProgress.value = splashAlphaUseCase.execute(taskId)
+ }
+
+ override fun getThumbnailPositionState(width: Int, height: Int, isRtl: Boolean): Matrix {
+ return runBlocking {
+ when (
+ val thumbnailPositionState =
+ getThumbnailPositionUseCase.run(taskId, width, height, isRtl)
+ ) {
+ is ThumbnailPositionState.MatrixScaling -> thumbnailPositionState.matrix
+ is ThumbnailPositionState.MissingThumbnail -> Matrix.IDENTITY_MATRIX
+ }
+ }
+ }
+
+ private fun isBackgroundOnly(task: Task): Boolean = task.isLocked || task.thumbnail == null
+
+ private fun isSnapshotSplashState(task: Task): Boolean {
+ val thumbnailPresent = task.thumbnail?.thumbnail != null
+ val taskLocked = task.isLocked
+
+ return thumbnailPresent && !taskLocked
+ }
+
+ private fun createSnapshotState(task: Task): Snapshot {
+ val thumbnailData = task.thumbnail
+ val bitmap = thumbnailData?.thumbnail!!
+ return Snapshot(bitmap, thumbnailData.rotation, task.colorBackground.removeAlpha())
+ }
+
+ @ColorInt private fun Int.removeAlpha(): Int = ColorUtils.setAlphaComponent(this, 0xff)
+
+ private companion object {
+ const val MAX_SCRIM_ALPHA = 0.4f
+ const val TAG = "TaskThumbnailViewModel"
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java b/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java
index 38ae303..4a84b1b 100644
--- a/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java
+++ b/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java
@@ -32,23 +32,33 @@
import androidx.annotation.WorkerThread;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.dagger.LauncherBaseAppComponent;
+import com.android.launcher3.util.DaggerSingletonObject;
+import com.android.launcher3.util.DaggerSingletonTracker;
+import com.android.launcher3.util.ExecutorUtil;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.SettingsCache.OnChangeListener;
import com.android.launcher3.util.SimpleBroadcastReceiver;
+import com.android.quickstep.dagger.QuickstepBaseAppComponent;
import java.util.ArrayList;
import java.util.List;
+import javax.inject.Inject;
+
/**
* Extension of {@link ClockEventDelegate} to support async event registration
*/
+@LauncherAppSingleton
public class AsyncClockEventDelegate extends ClockEventDelegate
implements OnChangeListener, SafeCloseable {
- public static final MainThreadInitializedObject<AsyncClockEventDelegate> INSTANCE =
- new MainThreadInitializedObject<>(AsyncClockEventDelegate::new);
+ public static final DaggerSingletonObject<AsyncClockEventDelegate> INSTANCE =
+ new DaggerSingletonObject<>(QuickstepBaseAppComponent::getAsyncClockEventDelegate);
private final Context mContext;
private final SimpleBroadcastReceiver mReceiver =
@@ -61,10 +71,12 @@
private boolean mFormatRegistered = false;
private boolean mDestroyed = false;
- private AsyncClockEventDelegate(Context context) {
+ @Inject
+ AsyncClockEventDelegate(@ApplicationContext Context context, DaggerSingletonTracker tracker) {
super(context);
mContext = context;
mReceiver.register(mContext, ACTION_TIME_CHANGED, ACTION_TIMEZONE_CHANGED);
+ ExecutorUtil.executeSyncOnMainOrFail(() -> tracker.addCloseable(this));
}
@Override
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index 9335e7e..a5be89a 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -30,7 +30,6 @@
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.content.Context;
-import android.content.SharedPreferences;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.PointF;
@@ -45,6 +44,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherPrefChangeListener;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.touch.PagedOrientationHandler;
@@ -66,8 +66,7 @@
* This class has initial default state assuming the device and foreground app have
* no ({@link Surface#ROTATION_0} rotation.
*/
-public class RecentsOrientedState implements
- SharedPreferences.OnSharedPreferenceChangeListener {
+public class RecentsOrientedState implements LauncherPrefChangeListener {
private static final String TAG = "RecentsOrientedState";
private static final boolean DEBUG = false;
@@ -283,7 +282,7 @@
}
@Override
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
+ public void onPrefChanged(String s) {
if (LauncherPrefs.ALLOW_ROTATION.getSharedPrefKey().equals(s)) {
updateHomeRotationSetting();
}
diff --git a/quickstep/src_protolog/com/android/launcher3/util/StateManagerProtoLogProxy.java b/quickstep/src_protolog/com/android/launcher3/util/StateManagerProtoLogProxy.java
new file mode 100644
index 0000000..bc989dc
--- /dev/null
+++ b/quickstep/src_protolog/com/android/launcher3/util/StateManagerProtoLogProxy.java
@@ -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.launcher3.util;
+
+import static com.android.launcher3.Flags.enableStateManagerProtoLog;
+import static com.android.quickstep.util.QuickstepProtoLogGroup.LAUNCHER_STATE_MANAGER;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.protolog.ProtoLog;
+
+/**
+ * Proxy class used for StateManager ProtoLog support.
+ */
+public class StateManagerProtoLogProxy {
+
+ public static void logGoToState(
+ @NonNull Object fromState, @NonNull Object toState, @NonNull String trace) {
+ if (!enableStateManagerProtoLog()) return;
+ ProtoLog.d(LAUNCHER_STATE_MANAGER,
+ "StateManager.goToState: fromState: %s, toState: %s, partial trace:\n%s",
+ fromState,
+ toState,
+ trace);
+ }
+
+ public static void logCreateAtomicAnimation(
+ @NonNull Object fromState, @NonNull Object toState, @NonNull String trace) {
+ if (!enableStateManagerProtoLog()) return;
+ ProtoLog.d(LAUNCHER_STATE_MANAGER, "StateManager.createAtomicAnimation: "
+ + "fromState: %s, toState: %s, partial trace:\n%s",
+ fromState,
+ toState,
+ trace);
+ }
+
+ public static void logOnStateTransitionStart(@NonNull Object state) {
+ if (!enableStateManagerProtoLog()) return;
+ ProtoLog.d(LAUNCHER_STATE_MANAGER, "StateManager.onStateTransitionStart: state: %s", state);
+ }
+
+ public static void logOnStateTransitionEnd(@NonNull Object state) {
+ if (!enableStateManagerProtoLog()) return;
+ ProtoLog.d(LAUNCHER_STATE_MANAGER, "StateManager.onStateTransitionEnd: state: %s", state);
+ }
+
+ public static void logCancelAnimation(boolean animationOngoing, @NonNull String trace) {
+ if (!enableStateManagerProtoLog()) return;
+ ProtoLog.d(LAUNCHER_STATE_MANAGER,
+ "StateManager.cancelAnimation: animation ongoing: %b, partial trace:\n%s",
+ animationOngoing,
+ trace);
+ }
+}
diff --git a/quickstep/src_protolog/com/android/quickstep/util/QuickstepProtoLogGroup.java b/quickstep/src_protolog/com/android/quickstep/util/QuickstepProtoLogGroup.java
index 7b81b9a..bb02a11 100644
--- a/quickstep/src_protolog/com/android/quickstep/util/QuickstepProtoLogGroup.java
+++ b/quickstep/src_protolog/com/android/quickstep/util/QuickstepProtoLogGroup.java
@@ -27,7 +27,8 @@
public enum QuickstepProtoLogGroup implements IProtoLogGroup {
ACTIVE_GESTURE_LOG(true, true, false, "ActiveGestureLog"),
- RECENTS_WINDOW(true, true, Constants.DEBUG_RECENTS_WINDOW, "RecentsWindow");
+ RECENTS_WINDOW(true, true, Constants.DEBUG_RECENTS_WINDOW, "RecentsWindow"),
+ LAUNCHER_STATE_MANAGER(true, true, Constants.DEBUG_STATE_MANAGER, "LauncherStateManager");
private final boolean mEnabled;
private volatile boolean mLogToProto;
@@ -97,6 +98,7 @@
private static final class Constants {
private static final boolean DEBUG_RECENTS_WINDOW = false;
+ private static final boolean DEBUG_STATE_MANAGER = true; // b/279059025, b/325463989
private static final int LOG_START_ID =
(int) (UUID.nameUUIDFromBytes(QuickstepProtoLogGroup.class.getName().getBytes())
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/FakeTaskThumbnailViewModel.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/FakeTaskThumbnailViewModel.kt
new file mode 100644
index 0000000..ff5d8bd
--- /dev/null
+++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/FakeTaskThumbnailViewModel.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.task.thumbnail
+
+import android.graphics.Matrix
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
+import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeTaskThumbnailViewModel : TaskThumbnailViewModel {
+ override val cornerRadiusProgress = MutableStateFlow(0f)
+ override val inheritedScale = MutableStateFlow(1f)
+ override val dimProgress = MutableStateFlow(0f)
+ override val splashAlpha = MutableStateFlow(0f)
+ override val uiState = MutableStateFlow<TaskThumbnailUiState>(Uninitialized)
+
+ override fun bind(taskId: Int) {
+ // no-op
+ }
+
+ override fun getThumbnailPositionState(width: Int, height: Int, isRtl: Boolean) =
+ Matrix.IDENTITY_MATRIX
+}
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
new file mode 100644
index 0000000..75769e9
--- /dev/null
+++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.quickstep.task.thumbnail
+
+import android.content.Context
+import android.graphics.Color
+import android.view.LayoutInflater
+import com.android.launcher3.R
+import com.android.quickstep.recents.di.RecentsDependencies
+import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
+import com.google.android.apps.nexuslauncher.imagecomparison.goldenpathmanager.ViewScreenshotGoldenPathManager
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.Displays
+import platform.test.screenshot.ViewScreenshotTestRule
+import platform.test.screenshot.getEmulatedDevicePathConfig
+
+/** Screenshot tests for [TaskThumbnailView]. */
+@RunWith(ParameterizedAndroidJunit4::class)
+class TaskThumbnailViewScreenshotTest(emulationSpec: DeviceEmulationSpec) {
+
+ @get:Rule
+ val screenshotRule =
+ ViewScreenshotTestRule(
+ emulationSpec,
+ ViewScreenshotGoldenPathManager(getEmulatedDevicePathConfig(emulationSpec)),
+ )
+
+ private val taskThumbnailViewModel = FakeTaskThumbnailViewModel()
+
+ @Test
+ fun taskThumbnailView_uninitialized() {
+ screenshotRule.screenshotTest("taskThumbnailView_uninitialized") { activity ->
+ activity.actionBar?.hide()
+ createTaskThumbnailView(activity)
+ }
+ }
+
+ @Test
+ fun taskThumbnailView_backgroundOnly() {
+ screenshotRule.screenshotTest("taskThumbnailView_backgroundOnly") { activity ->
+ activity.actionBar?.hide()
+ taskThumbnailViewModel.uiState.value = TaskThumbnailUiState.BackgroundOnly(Color.YELLOW)
+ createTaskThumbnailView(activity)
+ }
+ }
+
+ private fun createTaskThumbnailView(context: Context): TaskThumbnailView {
+ val di = RecentsDependencies.initialize(context)
+ val taskThumbnailView =
+ LayoutInflater.from(context).inflate(R.layout.task_thumbnail, null, false)
+ val ttvDiScopeId = di.getScope(taskThumbnailView).scopeId
+ di.provide(TaskThumbnailViewData::class.java, ttvDiScopeId) { TaskThumbnailViewData() }
+ di.provide(TaskThumbnailViewModel::class.java, ttvDiScopeId) { taskThumbnailViewModel }
+
+ return taskThumbnailView as TaskThumbnailView
+ }
+
+ companion object {
+ @Parameters(name = "{0}")
+ @JvmStatic
+ fun getTestSpecs() =
+ DeviceEmulationSpec.forDisplays(
+ Displays.Phone,
+ isDarkTheme = false,
+ isLandscape = false,
+ )
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendControllerTest.kt
index f3fff9f..59900b1 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendControllerTest.kt
@@ -41,15 +41,14 @@
@EmulatedDevices(["pixelTablet2023"])
class TaskbarAutohideSuspendControllerTest {
- private val context = TaskbarWindowSandboxContext.create(getInstrumentation().targetContext)
-
- @get:Rule(order = 0) val animatorTestRule = AnimatorTestRule(this)
- @get:Rule(order = 1)
+ @get:Rule(order = 0) val context = TaskbarWindowSandboxContext.create()
+ @get:Rule(order = 1) val animatorTestRule = AnimatorTestRule(this)
+ @get:Rule(order = 2)
val systemUiProxyRule = TestRule { base, _ ->
object : Statement() {
override fun evaluate() {
getInstrumentation().runOnMainSync {
- context.applicationContext.putObject(
+ context.putObject(
SystemUiProxy.INSTANCE,
object : SystemUiProxy(context) {
override fun notifyTaskbarAutohideSuspend(suspend: Boolean) {
@@ -62,8 +61,8 @@
}
}
}
- @get:Rule(order = 2) val taskbarModeRule = TaskbarModeRule(context)
- @get:Rule(order = 3) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
+ @get:Rule(order = 3) val taskbarModeRule = TaskbarModeRule(context)
+ @get:Rule(order = 4) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
@InjectController lateinit var autohideSuspendController: TaskbarAutohideSuspendController
@InjectController lateinit var stashController: TaskbarStashController
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarDesktopModeControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarDesktopModeControllerTest.kt
index 72bbfc9..455b6c5 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarDesktopModeControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarDesktopModeControllerTest.kt
@@ -16,7 +16,6 @@
package com.android.launcher3.taskbar
-import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.taskbar.TaskbarBackgroundRenderer.Companion.MAX_ROUNDNESS
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule
import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext
@@ -30,12 +29,8 @@
@LauncherMultivalentJUnit.EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"])
class TaskbarDesktopModeControllerTest {
- private val context =
- TaskbarWindowSandboxContext.create(
- InstrumentationRegistry.getInstrumentation().targetContext
- )
-
- @get:Rule(order = 0) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
+ @get:Rule(order = 0) val context = TaskbarWindowSandboxContext.create()
+ @get:Rule(order = 1) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
@TaskbarUnitTestRule.InjectController
lateinit var taskbarDesktopModeController: TaskbarDesktopModeController
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarEduTooltipControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarEduTooltipControllerTest.kt
index 961d4dc..e575efd 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarEduTooltipControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarEduTooltipControllerTest.kt
@@ -17,7 +17,6 @@
package com.android.launcher3.taskbar.test
import android.util.Log
-import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.Utilities
import com.android.launcher3.taskbar.TOOLTIP_STEP_FEATURES
import com.android.launcher3.taskbar.TOOLTIP_STEP_NONE
@@ -52,30 +51,21 @@
@Ignore
class TaskbarEduTooltipControllerTest {
- private val context =
- TaskbarWindowSandboxContext.create(
- InstrumentationRegistry.getInstrumentation().targetContext
- )
-
- @get:Rule(order = 0)
- val tooltipStepPreferenceRule =
- TaskbarPreferenceRule(
- context,
- OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP.prefItem,
- )
+ @get:Rule(order = 0) val context = TaskbarWindowSandboxContext.create()
@get:Rule(order = 1)
+ val tooltipStepPreferenceRule =
+ TaskbarPreferenceRule(context, OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP.prefItem)
+
+ @get:Rule(order = 2)
val searchEduPreferenceRule =
- TaskbarPreferenceRule(
- context,
- OnboardingPrefs.TASKBAR_SEARCH_EDU_SEEN,
- )
+ TaskbarPreferenceRule(context, OnboardingPrefs.TASKBAR_SEARCH_EDU_SEEN)
- @get:Rule(order = 2) val taskbarPinningPreferenceRule = TaskbarPinningPreferenceRule(context)
+ @get:Rule(order = 3) val taskbarPinningPreferenceRule = TaskbarPinningPreferenceRule(context)
- @get:Rule(order = 3) val taskbarModeRule = TaskbarModeRule(context)
+ @get:Rule(order = 4) val taskbarModeRule = TaskbarModeRule(context)
- @get:Rule(order = 4) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
+ @get:Rule(order = 5) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
@InjectController lateinit var taskbarEduTooltipController: TaskbarEduTooltipController
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarScrimViewControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarScrimViewControllerTest.kt
index 3524961..12e84b8 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarScrimViewControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarScrimViewControllerTest.kt
@@ -42,11 +42,10 @@
@RunWith(LauncherMultivalentJUnit::class)
@EmulatedDevices(["pixelTablet2023"])
class TaskbarScrimViewControllerTest {
- private val context = TaskbarWindowSandboxContext.create(getInstrumentation().targetContext)
-
- @get:Rule(order = 0) val taskbarModeRule = TaskbarModeRule(context)
- @get:Rule(order = 1) val animatorTestRule = AnimatorTestRule(this)
- @get:Rule(order = 2) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
+ @get:Rule(order = 0) val context = TaskbarWindowSandboxContext.create()
+ @get:Rule(order = 1) val taskbarModeRule = TaskbarModeRule(context)
+ @get:Rule(order = 2) val animatorTestRule = AnimatorTestRule(this)
+ @get:Rule(order = 3) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
@InjectController lateinit var scrimViewController: TaskbarScrimViewController
@@ -132,7 +131,7 @@
@TaskbarMode(PINNED)
fun testOnClick_scrimShown_performsSystemBack() {
var backPressed = false
- context.applicationContext.putObject(
+ context.putObject(
SystemUiProxy.INSTANCE,
object : SystemUiProxy(context) {
override fun onBackPressed() {
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt
index d7ce4ed..de73ce7 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt
@@ -63,12 +63,11 @@
@EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
@EmulatedDevices(["pixelTablet2023"])
class TaskbarStashControllerTest {
- private val context = TaskbarWindowSandboxContext.create(getInstrumentation().targetContext)
-
- @get:Rule(order = 0) val taskbarModeRule = TaskbarModeRule(context)
- @get:Rule(order = 1) val taskbarPinningPreferenceRule = TaskbarPinningPreferenceRule(context)
- @get:Rule(order = 2) val animatorTestRule = AnimatorTestRule(this)
- @get:Rule(order = 3) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
+ @get:Rule(order = 0) val context = TaskbarWindowSandboxContext.create()
+ @get:Rule(order = 1) val taskbarModeRule = TaskbarModeRule(context)
+ @get:Rule(order = 2) val taskbarPinningPreferenceRule = TaskbarPinningPreferenceRule(context)
+ @get:Rule(order = 3) val animatorTestRule = AnimatorTestRule(this)
+ @get:Rule(order = 4) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
@InjectController lateinit var stashController: TaskbarStashController
@InjectController lateinit var viewController: TaskbarViewController
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewControllerTest.kt
index 4aac1dc..b13eafe 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewControllerTest.kt
@@ -17,7 +17,6 @@
package com.android.launcher3.taskbar
import android.view.View
-import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.launcher3.taskbar.TaskbarViewController.DIVIDER_VIEW_POSITION_OFFSET
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController
@@ -33,19 +32,23 @@
@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"])
/**
* Legend for the comments below:
+ * ```
* A: All Apps Button
* H: Hotseat item
* |: Divider
* R: Recent item
+ * ```
*
* The comments are formatted in two lines:
+ * ```
* // Items in taskbar, e.g. A | HHHHHH
* // Index of items relative to Hotseat: -1 -.5 012345
+ * ```
*/
class TaskbarViewControllerTest {
- private val context = TaskbarWindowSandboxContext.create(getInstrumentation().targetContext)
- @get:Rule val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
+ @get:Rule(order = 0) val context = TaskbarWindowSandboxContext.create()
+ @get:Rule(order = 1) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
@InjectController lateinit var taskbarViewController: TaskbarViewController
@@ -59,7 +62,7 @@
/* isAllAppsButton = */ true,
/* isTaskbarDividerView = */ false,
/* isDividerForRecents = */ false,
- /* recentTaskIndex = */ -1
+ /* recentTaskIndex = */ -1,
)
// [>A<] | [HHHHHH]
// -1 -.5 012345
@@ -77,7 +80,7 @@
/* isAllAppsButton = */ true,
/* isTaskbarDividerView = */ false,
/* isDividerForRecents = */ false,
- /* recentTaskIndex = */ -1
+ /* recentTaskIndex = */ -1,
)
// [HHHHHH] | [>A<]
// 012345 5.5 6
@@ -94,7 +97,7 @@
/* isAllAppsButton = */ false,
/* isTaskbarDividerView = */ true,
/* isDividerForRecents = */ false,
- /* recentTaskIndex = */ -1
+ /* recentTaskIndex = */ -1,
)
// [A] >|< [HHHHHH]
// -1 -.5 012345
@@ -112,7 +115,7 @@
/* isAllAppsButton = */ false,
/* isTaskbarDividerView = */ true,
/* isDividerForRecents = */ true,
- /* recentTaskIndex = */ -1
+ /* recentTaskIndex = */ -1,
)
// [A] [HHHHHH] >|< [RR]
// -1 012345 5.5 67
@@ -130,7 +133,7 @@
/* isAllAppsButton = */ false,
/* isTaskbarDividerView = */ true,
/* isDividerForRecents = */ false,
- /* recentTaskIndex = */ -1
+ /* recentTaskIndex = */ -1,
)
// [HHHHHH] >|< [A]
// 012345 5.5 6
@@ -148,7 +151,7 @@
/* isAllAppsButton = */ false,
/* isTaskbarDividerView = */ true,
/* isDividerForRecents = */ true,
- /* recentTaskIndex = */ -1
+ /* recentTaskIndex = */ -1,
)
// [HHHHHH][A] >|< [RR]
// 012345 6 6.5 78
@@ -167,7 +170,7 @@
/* isAllAppsButton = */ false,
/* isTaskbarDividerView = */ false,
/* isDividerForRecents = */ false,
- /* recentTaskIndex = */ recentTaskIndex
+ /* recentTaskIndex = */ recentTaskIndex,
)
// [A][HHHHHH] | [>R<R]
// -1 012345 5.5 6 7
@@ -186,7 +189,7 @@
/* isAllAppsButton = */ false,
/* isTaskbarDividerView = */ false,
/* isDividerForRecents = */ false,
- /* recentTaskIndex = */ recentTaskIndex
+ /* recentTaskIndex = */ recentTaskIndex,
)
// [A][HHHHHH] | [R>R<]
// -1 012345 5.5 6 7
@@ -205,7 +208,7 @@
/* isAllAppsButton = */ false,
/* isTaskbarDividerView = */ false,
/* isDividerForRecents = */ false,
- /* recentTaskIndex = */ recentTaskIndex
+ /* recentTaskIndex = */ recentTaskIndex,
)
// [HHHHHH][A] | [>R<R]
// 012345 6 6.5 7 8
@@ -224,7 +227,7 @@
/* isAllAppsButton = */ false,
/* isTaskbarDividerView = */ false,
/* isDividerForRecents = */ false,
- /* recentTaskIndex = */ recentTaskIndex
+ /* recentTaskIndex = */ recentTaskIndex,
)
// [HHHHHH][A] | [R>R<]
// 012345 6 6.5 7 8
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt
index f783e40..60c94a8 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt
@@ -44,13 +44,9 @@
@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"])
class TaskbarAllAppsControllerTest {
- @get:Rule
- val taskbarUnitTestRule =
- TaskbarUnitTestRule(
- this,
- TaskbarWindowSandboxContext.create(getInstrumentation().targetContext),
- )
- @get:Rule val animatorTestRule = AnimatorTestRule(this)
+ @get:Rule(order = 0) val context = TaskbarWindowSandboxContext.create()
+ @get:Rule(order = 1) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
+ @get:Rule(order = 2) val animatorTestRule = AnimatorTestRule(this)
@InjectController lateinit var allAppsController: TaskbarAllAppsController
@InjectController lateinit var overlayController: TaskbarOverlayController
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewControllerTest.kt
index 04f02e9..516220a 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewControllerTest.kt
@@ -47,13 +47,12 @@
@EmulatedDevices(["pixelFoldable2023"])
class TaskbarAllAppsViewControllerTest {
- private val context = TaskbarWindowSandboxContext.create(getInstrumentation().targetContext)
-
- @get:Rule(order = 0) val taskbarModeRule = TaskbarModeRule(context)
- @get:Rule(order = 1)
+ @get:Rule(order = 0) val context = TaskbarWindowSandboxContext.create()
+ @get:Rule(order = 1) val taskbarModeRule = TaskbarModeRule(context)
+ @get:Rule(order = 2)
val allAppsVisitedPreferenceRule =
TaskbarPreferenceRule(context, ALL_APPS_VISITED_COUNT.prefItem)
- @get:Rule(order = 2) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
+ @get:Rule(order = 3) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
@InjectController lateinit var overlayController: TaskbarOverlayController
@InjectController lateinit var stashController: TaskbarStashController
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleBarSwipeControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleBarSwipeControllerTest.kt
index 3b6952d..2e471b8 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleBarSwipeControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleBarSwipeControllerTest.kt
@@ -37,6 +37,7 @@
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.never
+import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -45,15 +46,11 @@
companion object {
const val UNSTASH_THRESHOLD = 100
- const val EXPAND_THRESHOLD = 200
const val MAX_OVERSCROLL = 300
- const val STASH_THRESHOLD = 50
const val UP_BELOW_UNSTASH = -UNSTASH_THRESHOLD + 10f
const val UP_ABOVE_UNSTASH = -UNSTASH_THRESHOLD - 10f
- const val UP_ABOVE_EXPAND = -EXPAND_THRESHOLD - 10f
- const val DOWN_UNDER_STASH = STASH_THRESHOLD - 10f
- const val DOWN_OVER_STASH = STASH_THRESHOLD + 10f
+ const val DOWN = UNSTASH_THRESHOLD + 10f
}
private val context = ApplicationProvider.getApplicationContext<Context>()
@@ -80,14 +77,8 @@
override val unstashThreshold: Int
get() = UNSTASH_THRESHOLD
- override val expandThreshold: Int
- get() = EXPAND_THRESHOLD
-
override val maxOverscroll: Int
get() = MAX_OVERSCROLL
-
- override val stashThreshold: Int
- get() = STASH_THRESHOLD
}
bubbleBarSwipeController = BubbleBarSwipeController(context, dimensionProvider)
@@ -135,35 +126,11 @@
}
@Test
- fun swipeUp_stashedBar_aboveExpandThreshold_viewsHaveDampedTranslation() {
- setUpStashedBar()
- testViewsHaveDampedTranslationOnSwipe(UP_ABOVE_EXPAND)
- }
-
- @Test
fun swipeUp_collapsedBar_aboveUnstashThreshold_viewsHaveDampedTranslation() {
setUpCollapsedBar()
testViewsHaveDampedTranslationOnSwipe(UP_ABOVE_UNSTASH)
}
- @Test
- fun swipeUp_collapsedBar_aboveExpandThreshold_viewsHaveDampedTranslation() {
- setUpCollapsedBar()
- testViewsHaveDampedTranslationOnSwipe(UP_ABOVE_EXPAND)
- }
-
- @Test
- fun swipeDown_collapsedBar_belowStashThreshold_viewsHaveDampedTranslation() {
- setUpCollapsedBar()
- testViewsHaveDampedTranslationOnSwipe(DOWN_UNDER_STASH)
- }
-
- @Test
- fun swipeDown_collapsedBar_overStashThreshold_viewsHaveDampedTranslation() {
- setUpCollapsedBar()
- testViewsHaveDampedTranslationOnSwipe(DOWN_OVER_STASH)
- }
-
// endregion
// region Test that translation on views is reset on finish
@@ -203,29 +170,11 @@
}
@Test
- fun swipeUp_stashedBar_aboveExpandThreshold_animateTranslationToZeroOnFinish() {
- setUpStashedBar()
- testViewsTranslationResetOnFinish(UP_ABOVE_EXPAND)
- }
-
- @Test
fun swipeUp_collapsedBar_aboveUnstashThreshold_animateTranslationToZeroOnFinish() {
setUpCollapsedBar()
testViewsTranslationResetOnFinish(UP_ABOVE_UNSTASH)
}
- @Test
- fun swipeUp_collapsedBar_aboveExpandThreshold_animateTranslationToZeroOnFinish() {
- setUpCollapsedBar()
- testViewsTranslationResetOnFinish(UP_ABOVE_EXPAND)
- }
-
- @Test
- fun swipeDown_collapsedBar_aboveStashThreshold_animateTranslationToZeroOnFinish() {
- setUpCollapsedBar()
- testViewsTranslationResetOnFinish(DOWN_OVER_STASH)
- }
-
// endregion
// region Test swipe interactions on stashed bar
@@ -251,7 +200,7 @@
}
@Test
- fun swipeUp_stashedBar_aboveUnstashThreshold_unstashBubbleBar() {
+ fun swipeUp_stashedBar_overUnstashThreshold_unstashBubbleBar() {
setUpStashedBar()
getInstrumentation().runOnMainSync {
bubbleBarSwipeController.start()
@@ -271,50 +220,45 @@
}
@Test
- fun swipeUp_stashedBar_overUnstashThresholdMultipleTimes_unstashBubbleBarOnce() {
+ fun swipeUp_stashedBar_overUnstashThresholdMultipleTimes_unstashesMultipleTimes() {
setUpStashedBar()
getInstrumentation().runOnMainSync {
bubbleBarSwipeController.start()
bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH)
bubbleBarSwipeController.swipeTo(UP_BELOW_UNSTASH)
- bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH)
}
verify(bubbleStashController).showBubbleBar(expandBubbles = false)
+ verify(bubbleStashController).stashBubbleBar()
+
+ getInstrumentation().runOnMainSync { bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH) }
+ verify(bubbleStashController, times(2)).showBubbleBar(expandBubbles = false)
}
@Test
- fun swipeUp_stashedBar_overExpandThreshold_doesNotExpandBeforeFinish() {
+ fun swipeUp_stashedBar_releaseOverUnstashThreshold_expandsBar() {
setUpStashedBar()
getInstrumentation().runOnMainSync {
bubbleBarSwipeController.start()
- bubbleBarSwipeController.swipeTo(UP_ABOVE_EXPAND)
+ bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH)
}
- verify(bubbleStashController).showBubbleBar(expandBubbles = false)
+ verify(bubbleStashController, never()).showBubbleBar(expandBubbles = true)
getInstrumentation().runOnMainSync { bubbleBarSwipeController.finish() }
verify(bubbleStashController).showBubbleBar(expandBubbles = true)
}
@Test
- fun swipeUp_stashedBar_overExpandThreshold_isSwipeGestureTrue() {
+ fun swipeUp_stashedBar_overUnstashReleaseBelowUnstash_doesNotExpandBar() {
setUpStashedBar()
getInstrumentation().runOnMainSync {
bubbleBarSwipeController.start()
- bubbleBarSwipeController.swipeTo(UP_ABOVE_EXPAND)
- }
- assertThat(bubbleBarSwipeController.isSwipeGesture()).isTrue()
- }
-
- @Test
- fun swipeUp_stashedBar_overExpandThresholdAndBackDown_doesNotExpandAfterFinish() {
- setUpStashedBar()
- getInstrumentation().runOnMainSync {
- bubbleBarSwipeController.start()
- bubbleBarSwipeController.swipeTo(UP_ABOVE_EXPAND)
bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH)
}
verify(bubbleStashController).showBubbleBar(expandBubbles = false)
- getInstrumentation().runOnMainSync { bubbleBarSwipeController.finish() }
- verify(bubbleStashController).showBubbleBar(expandBubbles = false)
+ getInstrumentation().runOnMainSync {
+ bubbleBarSwipeController.swipeTo(UP_BELOW_UNSTASH)
+ bubbleBarSwipeController.finish()
+ }
+ verify(bubbleStashController, never()).showBubbleBar(expandBubbles = true)
}
@Test
@@ -322,7 +266,7 @@
setUpStashedBar()
getInstrumentation().runOnMainSync {
bubbleBarSwipeController.start()
- bubbleBarSwipeController.swipeTo(DOWN_OVER_STASH)
+ bubbleBarSwipeController.swipeTo(DOWN)
}
verify(bubbleStashedHandleViewController, never()).setTranslationYForSwipe(any())
verify(bubbleBarViewController, never()).setTranslationYForSwipe(any())
@@ -338,8 +282,8 @@
setUpExpandedBar()
getInstrumentation().runOnMainSync {
bubbleBarSwipeController.start()
- bubbleBarSwipeController.swipeTo(UP_ABOVE_EXPAND)
- bubbleBarSwipeController.swipeTo(DOWN_OVER_STASH)
+ bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH)
+ bubbleBarSwipeController.swipeTo(DOWN)
bubbleBarSwipeController.finish()
}
verify(bubbleStashedHandleViewController, never()).setTranslationYForSwipe(any())
@@ -352,48 +296,71 @@
// region Test swipe interactions on collapsed bar
@Test
- fun swipeDown_collapsedBar_underStashThreshold_doesNotHideBar() {
+ fun swipeUp_collapsedBar_doesNotShowBarDuringDrag() {
setUpCollapsedBar()
getInstrumentation().runOnMainSync {
bubbleBarSwipeController.start()
- bubbleBarSwipeController.swipeTo(DOWN_UNDER_STASH)
- bubbleBarSwipeController.finish()
+ bubbleBarSwipeController.swipeTo(UP_BELOW_UNSTASH)
+ bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH)
}
- verify(bubbleStashController, never()).stashBubbleBar()
+ verify(bubbleStashController, never()).showBubbleBar(any())
}
@Test
- fun swipeDown_collapsedBar_overStashThreshold_doesNotHideBarBeforeFinish() {
+ fun swipeUp_collapsedBar_belowUnstashThreshold_isSwipeGestureFalse() {
setUpCollapsedBar()
getInstrumentation().runOnMainSync {
bubbleBarSwipeController.start()
- bubbleBarSwipeController.swipeTo(DOWN_OVER_STASH)
- }
- verify(bubbleStashController, never()).stashBubbleBar()
- getInstrumentation().runOnMainSync { bubbleBarSwipeController.finish() }
- verify(bubbleStashController).stashBubbleBar()
- }
-
- @Test
- fun swipeDown_collapsedBar_underStashThreshold_isSwipeGestureFalse() {
- setUpCollapsedBar()
- getInstrumentation().runOnMainSync {
- bubbleBarSwipeController.start()
- bubbleBarSwipeController.swipeTo(DOWN_UNDER_STASH)
+ bubbleBarSwipeController.swipeTo(UP_BELOW_UNSTASH)
}
assertThat(bubbleBarSwipeController.isSwipeGesture()).isFalse()
}
@Test
- fun swipeDown_collapsedBar_overStashThreshold_isSwipeGestureTrue() {
+ fun swipeUp_collapsedBar_overUnstashThreshold_isSwipeGestureTrue() {
setUpCollapsedBar()
getInstrumentation().runOnMainSync {
bubbleBarSwipeController.start()
- bubbleBarSwipeController.swipeTo(DOWN_OVER_STASH)
+ bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH)
}
assertThat(bubbleBarSwipeController.isSwipeGesture()).isTrue()
}
+ @Test
+ fun swipeUp_collapsedBar_finishOverUnstashThreshold_expandsBar() {
+ setUpCollapsedBar()
+ getInstrumentation().runOnMainSync {
+ bubbleBarSwipeController.start()
+ bubbleBarSwipeController.swipeTo(UP_ABOVE_UNSTASH)
+ bubbleBarSwipeController.finish()
+ }
+ verify(bubbleStashController).showBubbleBar(expandBubbles = true)
+ }
+
+ @Test
+ fun swipeUp_collapsedBar_finishBelowUnstashThreshold_doesNotExpandBar() {
+ setUpCollapsedBar()
+ getInstrumentation().runOnMainSync {
+ bubbleBarSwipeController.start()
+ bubbleBarSwipeController.swipeTo(UP_BELOW_UNSTASH)
+ bubbleBarSwipeController.finish()
+ }
+ verify(bubbleStashController, never()).showBubbleBar(any())
+ }
+
+ @Test
+ fun swipeDown_collapsedBar_swipeIgnored() {
+ setUpCollapsedBar()
+ getInstrumentation().runOnMainSync {
+ bubbleBarSwipeController.start()
+ bubbleBarSwipeController.swipeTo(DOWN)
+ }
+ verify(bubbleStashedHandleViewController, never()).setTranslationYForSwipe(any())
+ verify(bubbleBarViewController, never()).setTranslationYForSwipe(any())
+ verify(bubbleStashController, never()).showBubbleBar(any())
+ verify(bubbleStashController, never()).stashBubbleBar()
+ }
+
// endregion
private fun setUpStashedBar() {
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt
index 4fa821d..1113129 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt
@@ -18,7 +18,6 @@
import android.app.ActivityManager.RunningTaskInfo
import android.view.MotionEvent
-import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.launcher3.AbstractFloatingView
import com.android.launcher3.AbstractFloatingView.TYPE_OPTIONS_POPUP
import com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_ALL_APPS
@@ -42,12 +41,8 @@
@EmulatedDevices(["pixelFoldable2023"])
class TaskbarOverlayControllerTest {
- @get:Rule
- val taskbarUnitTestRule =
- TaskbarUnitTestRule(
- this,
- TaskbarWindowSandboxContext.create(getInstrumentation().targetContext),
- )
+ @get:Rule(order = 0) val context = TaskbarWindowSandboxContext.create()
+ @get:Rule(order = 1) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
@InjectController lateinit var overlayController: TaskbarOverlayController
private val taskbarContext: TaskbarActivityContext
@@ -223,9 +218,8 @@
}
private class TestOverlayView
- private constructor(
- private val overlayContext: TaskbarOverlayContext,
- ) : AbstractFloatingView(overlayContext, null) {
+ private constructor(private val overlayContext: TaskbarOverlayContext) :
+ AbstractFloatingView(overlayContext, null) {
var type = TYPE_OPTIONS_POPUP
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt
index c48947e..74b154a 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt
@@ -61,7 +61,7 @@
val mode = taskbarMode.mode
getInstrumentation().runOnMainSync {
- context.applicationContext.putObject(
+ context.putObject(
DisplayController.INSTANCE,
object : DisplayController(context) {
override fun getInfo(): Info {
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRuleTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRuleTest.kt
index f7e4576..0dd1324 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRuleTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRuleTest.kt
@@ -16,7 +16,6 @@
package com.android.launcher3.taskbar.rules
-import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.PINNED
import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.THREE_BUTTONS
@@ -35,9 +34,8 @@
@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"])
class TaskbarModeRuleTest {
- private val context = TaskbarWindowSandboxContext.create(getInstrumentation().targetContext)
-
- @get:Rule val taskbarModeRule = TaskbarModeRule(context)
+ @get:Rule(order = 0) val context = TaskbarWindowSandboxContext.create()
+ @get:Rule(order = 1) val taskbarModeRule = TaskbarModeRule(context)
@Test
@TaskbarMode(TRANSIENT)
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPinningPreferenceRuleTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPinningPreferenceRuleTest.kt
index a515405..977e7a5 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPinningPreferenceRuleTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPinningPreferenceRuleTest.kt
@@ -16,13 +16,13 @@
package com.android.launcher3.taskbar.rules
-import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.launcher3.util.DisplayController
import com.android.launcher3.util.LauncherMultivalentJUnit
import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
import com.android.launcher3.util.window.WindowManagerProxy
import com.google.android.apps.nexuslauncher.deviceemulator.TestWindowManagerProxy
import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.Description
import org.junit.runner.RunWith
@@ -31,7 +31,7 @@
@RunWith(LauncherMultivalentJUnit::class)
@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"])
class TaskbarPinningPreferenceRuleTest {
- private val context = TaskbarWindowSandboxContext.create(getInstrumentation().targetContext)
+ @get:Rule val context = TaskbarWindowSandboxContext.create()
private val preferenceRule = TaskbarPinningPreferenceRule(context)
@@ -55,7 +55,7 @@
@Test
fun testEnableDesktopPinning_verifyDisplayController() {
- context.applicationContext.putObject(
+ context.putObject(
WindowManagerProxy.INSTANCE,
TestWindowManagerProxy(context).apply { isInDesktopMode = true },
)
@@ -69,7 +69,7 @@
@Test
fun testDisableDesktopPinning_verifyDisplayController() {
- context.applicationContext.putObject(
+ context.putObject(
WindowManagerProxy.INSTANCE,
TestWindowManagerProxy(context).apply { isInDesktopMode = true },
)
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPreferenceRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPreferenceRule.kt
index a76a77d..e42ca9e 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPreferenceRule.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPreferenceRule.kt
@@ -29,11 +29,12 @@
* The original preference value is restored on teardown.
*/
class TaskbarPreferenceRule<T : Any>(
- context: TaskbarWindowSandboxContext,
- private val constantItem: ConstantItem<T>
+ private val context: TaskbarWindowSandboxContext,
+ private val constantItem: ConstantItem<T>,
) : TestRule {
- private val prefs = LauncherPrefs.get(context)
+ private val prefs: LauncherPrefs
+ get() = LauncherPrefs.get(context)
var value: T
get() = prefs.get(constantItem)
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPreferenceRuleTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPreferenceRuleTest.kt
index 46817d2..b7e6fa3 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPreferenceRuleTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPreferenceRuleTest.kt
@@ -16,11 +16,11 @@
package com.android.launcher3.taskbar.rules
-import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING
import com.android.launcher3.util.LauncherMultivalentJUnit
import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.Description
import org.junit.runner.RunWith
@@ -30,7 +30,7 @@
@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"])
class TaskbarPreferenceRuleTest {
- private val context = TaskbarWindowSandboxContext.create(getInstrumentation().targetContext)
+ @get:Rule val context = TaskbarWindowSandboxContext.create()
private val preferenceRule = TaskbarPreferenceRule(context, TASKBAR_PINNING)
@Test
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRuleTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRuleTest.kt
index 52ca78d..7daa142 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRuleTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRuleTest.kt
@@ -19,7 +19,6 @@
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
-import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.launcher3.taskbar.TaskbarKeyguardController
import com.android.launcher3.taskbar.TaskbarManager
@@ -43,9 +42,8 @@
@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"])
class TaskbarUnitTestRuleTest {
- private val context = TaskbarWindowSandboxContext.create(getInstrumentation().targetContext)
-
- @get:Rule(order = 0) val setFlagsRule = SetFlagsRule()
+ @get:Rule(order = 0) val context = TaskbarWindowSandboxContext.create()
+ @get:Rule(order = 1) val setFlagsRule = SetFlagsRule()
@Test
fun testSetup_taskbarInitialized() {
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
index ee21df8..741be50 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
@@ -16,46 +16,19 @@
package com.android.launcher3.taskbar.rules
-import android.content.Context
import android.content.ContextWrapper
-import android.os.Bundle
-import android.view.Display
-import com.android.launcher3.util.MainThreadInitializedObject
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
+import com.android.launcher3.util.MainThreadInitializedObject.ObjectSandbox
+import com.android.launcher3.util.SandboxApplication
+import org.junit.rules.TestRule
-/**
- * Sandbox wrapper where [createWindowContext] provides contexts that are still sandboxed within
- * [application].
- *
- * Taskbar can create window contexts, which need to operate under the same sandbox application, but
- * [Context.getApplicationContext] by default returns the actual application. For this reason,
- * [SandboxContext] overrides [getApplicationContext] to return itself, which prevents leaving the
- * sandbox. [SandboxContext] and the real application have different sets of
- * [MainThreadInitializedObject] instances, so overriding the application prevents the latter set
- * from leaking into the sandbox. Similarly, this implementation overrides [getApplicationContext]
- * to return the original sandboxed [application], and it wraps created windowed contexts to
- * propagate this [application].
- */
-class TaskbarWindowSandboxContext
-private constructor(private val application: SandboxContext, base: Context) : ContextWrapper(base) {
-
- override fun createWindowContext(type: Int, options: Bundle?): Context {
- return TaskbarWindowSandboxContext(application, super.createWindowContext(type, options))
- }
-
- override fun createWindowContext(display: Display, type: Int, options: Bundle?): Context {
- return TaskbarWindowSandboxContext(
- application,
- super.createWindowContext(display, type, options),
- )
- }
-
- override fun getApplicationContext(): SandboxContext = application
+/** Sandbox Context for running Taskbar tests. */
+class TaskbarWindowSandboxContext private constructor(base: SandboxApplication) :
+ ContextWrapper(base), ObjectSandbox by base, TestRule by base {
companion object {
- /** Creates a [TaskbarWindowSandboxContext] to sandbox [base] for Taskbar tests. */
- fun create(base: Context): TaskbarWindowSandboxContext {
- return SandboxContext(base).let { TaskbarWindowSandboxContext(it, it) }
+ /** Creates a [SandboxApplication] for Taskbar tests. */
+ fun create(): TaskbarWindowSandboxContext {
+ return TaskbarWindowSandboxContext(SandboxApplication())
}
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContextTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContextTest.kt
deleted file mode 100644
index 4834d48..0000000
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContextTest.kt
+++ /dev/null
@@ -1,45 +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.launcher3.taskbar.rules
-
-import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
-import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
-import com.android.launcher3.util.LauncherMultivalentJUnit
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(LauncherMultivalentJUnit::class)
-@LauncherMultivalentJUnit.EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"])
-class TaskbarWindowSandboxContextTest {
-
- private val context = TaskbarWindowSandboxContext.create(getInstrumentation().targetContext)
-
- @Test
- fun testCreateWindowContext_applicationContextSandboxed() {
- val windowContext = context.createWindowContext(TYPE_APPLICATION_OVERLAY, null)
- assertThat(windowContext.applicationContext).isInstanceOf(SandboxContext::class.java)
- }
-
- @Test
- fun testCreateWindowContext_nested_applicationContextSandboxed() {
- val windowContext = context.createWindowContext(TYPE_APPLICATION_OVERLAY, null)
- val nestedContext = windowContext.createWindowContext(TYPE_APPLICATION_OVERLAY, null)
- assertThat(nestedContext.applicationContext).isInstanceOf(SandboxContext::class.java)
- }
-}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
index a87465f..d55f2e3 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
@@ -22,7 +22,6 @@
import android.graphics.drawable.Drawable
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.launcher3.util.TestDispatcherProvider
-import com.android.quickstep.task.thumbnail.TaskThumbnailViewModelTest
import com.android.quickstep.util.DesktopTask
import com.android.quickstep.util.GroupTask
import com.android.systemui.shared.recents.model.Task
@@ -286,9 +285,14 @@
private fun createThumbnailData(): ThumbnailData {
val bitmap = mock<Bitmap>()
- whenever(bitmap.width).thenReturn(TaskThumbnailViewModelTest.THUMBNAIL_WIDTH)
- whenever(bitmap.height).thenReturn(TaskThumbnailViewModelTest.THUMBNAIL_HEIGHT)
+ whenever(bitmap.width).thenReturn(THUMBNAIL_WIDTH)
+ whenever(bitmap.height).thenReturn(THUMBNAIL_HEIGHT)
return ThumbnailData(thumbnail = bitmap)
}
+
+ companion object {
+ const val THUMBNAIL_WIDTH = 100
+ const val THUMBNAIL_HEIGHT = 200
+ }
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/viewmodel/RecentsViewModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/viewmodel/RecentsViewModelTest.kt
index 33d96a8..829987c 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/viewmodel/RecentsViewModelTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/viewmodel/RecentsViewModelTest.kt
@@ -22,7 +22,6 @@
import android.graphics.Color
import android.view.Surface
import com.android.quickstep.recents.data.FakeTasksRepository
-import com.android.quickstep.task.thumbnail.TaskThumbnailViewModelTest
import com.android.systemui.shared.recents.model.Task
import com.android.systemui.shared.recents.model.ThumbnailData
import com.google.common.truth.Truth.assertThat
@@ -126,9 +125,14 @@
private fun createThumbnailData(rotation: Int = Surface.ROTATION_0): ThumbnailData {
val bitmap = mock<Bitmap>()
- whenever(bitmap.width).thenReturn(TaskThumbnailViewModelTest.THUMBNAIL_WIDTH)
- whenever(bitmap.height).thenReturn(TaskThumbnailViewModelTest.THUMBNAIL_HEIGHT)
+ whenever(bitmap.width).thenReturn(THUMBNAIL_WIDTH)
+ whenever(bitmap.height).thenReturn(THUMBNAIL_HEIGHT)
return ThumbnailData(thumbnail = bitmap, rotation = rotation)
}
+
+ companion object {
+ const val THUMBNAIL_WIDTH = 100
+ const val THUMBNAIL_HEIGHT = 200
+ }
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt
similarity index 97%
rename from quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt
index fcf4e56..c88a3fc 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt
@@ -36,7 +36,7 @@
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.SnapshotSplash
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
import com.android.quickstep.task.viewmodel.TaskContainerData
-import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
+import com.android.quickstep.task.viewmodel.TaskThumbnailViewModelImpl
import com.android.quickstep.task.viewmodel.TaskViewData
import com.android.quickstep.views.TaskViewType
import com.android.systemui.shared.recents.model.Task
@@ -51,7 +51,7 @@
/** Test for [TaskThumbnailView] */
@RunWith(AndroidJUnit4::class)
-class TaskThumbnailViewModelTest {
+class TaskThumbnailViewModelImplTest {
private var taskViewType = TaskViewType.SINGLE
private val recentsViewData = RecentsViewData()
private val taskViewData by lazy { TaskViewData(taskViewType) }
@@ -60,7 +60,7 @@
private val mGetThumbnailPositionUseCase = mock<GetThumbnailPositionUseCase>()
private val splashAlphaUseCase: SplashAlphaUseCase = mock()
private val systemUnderTest by lazy {
- TaskThumbnailViewModel(
+ TaskThumbnailViewModelImpl(
recentsViewData,
taskViewData,
taskContainerData,
@@ -109,7 +109,7 @@
bitmap = expectedThumbnailData.thumbnail!!,
thumbnailRotation = Surface.ROTATION_0,
),
- expectedIconData.icon
+ expectedIconData.icon,
)
)
}
@@ -204,7 +204,7 @@
bitmap = expectedThumbnailData.thumbnail!!,
thumbnailRotation = Surface.ROTATION_270,
),
- expectedIconData.icon
+ expectedIconData.icon,
)
)
}
@@ -230,7 +230,7 @@
bitmap = expectedThumbnailData.thumbnail!!,
thumbnailRotation = Surface.ROTATION_0,
),
- expectedIconData.icon
+ expectedIconData.icon,
)
)
}
diff --git a/res/layout/user_folder_icon_normalized.xml b/res/layout/user_folder_icon_normalized.xml
index 43a8aac..002e7b7 100644
--- a/res/layout/user_folder_icon_normalized.xml
+++ b/res/layout/user_folder_icon_normalized.xml
@@ -40,12 +40,11 @@
<com.android.launcher3.folder.FolderNameEditText
android:id="@+id/folder_name"
android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
+ android:layout_height="match_parent"
style="@style/TextHeadline"
android:layout_weight="1"
android:background="@android:color/transparent"
- android:gravity="center_horizontal"
+ android:gravity="center"
android:hint="@string/folder_hint_text"
android:imeOptions="flagNoExtractUi"
android:importantForAutofill="no"
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index b0ec9b0..200d5a7 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1872,13 +1872,18 @@
}
}
- // Exit spring loaded mode if necessary after adding the widget
- Runnable onComplete = MULTI_SELECT_EDIT_MODE.get() ? null
- : () -> mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
+ // Exit spring loaded mode if necessary after adding the widget; unless config activity was
+ // started.
+ Runnable onComplete = MULTI_SELECT_EDIT_MODE.get() ? null : () -> mStateManager.goToState(
+ NORMAL, SPRING_LOADED_EXIT_DELAY);
completeAddAppWidget(appWidgetId, info, boundWidget,
addFlowHandler.getProviderInfo(this), addFlowHandler.needsConfigure(),
false, widgetPreviewBitmap);
- mWorkspace.removeExtraEmptyScreenDelayed(delay, false, onComplete);
+ // Remove extra screen if widget drop concluded. If a config activity was started, extra
+ // screen will be removed when we get back its result.
+ if (!isActivityStarted) {
+ mWorkspace.removeExtraEmptyScreenDelayed(delay, false, onComplete);
+ }
}
public void addPendingItem(PendingAddItemInfo info, int container, int screenId,
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 15641ab..05a3a52 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -266,7 +266,7 @@
}
private class IconObserver
- implements IconProvider.IconChangeListener, OnSharedPreferenceChangeListener {
+ implements IconProvider.IconChangeListener, LauncherPrefChangeListener {
@Override
public void onAppIconChanged(String packageName, UserHandle user) {
@@ -288,7 +288,7 @@
}
@Override
- public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+ public void onPrefChanged(String key) {
if (Themes.KEY_THEMED_ICONS.equals(key)) {
mIconProvider.setIconThemeSupported(Themes.isThemedIconEnabled(mContext));
verifyIconChanged();
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
deleted file mode 100644
index 7ad17d9..0000000
--- a/src/com/android/launcher3/LauncherModel.java
+++ /dev/null
@@ -1,692 +0,0 @@
-/*
- * Copyright (C) 2008 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.launcher3;
-
-import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
-
-import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD;
-import static com.android.launcher3.LauncherPrefs.WORK_EDU_STEP;
-import static com.android.launcher3.config.FeatureFlags.IS_STUDIO_BUILD;
-import static com.android.launcher3.icons.cache.BaseIconCache.EMPTY_CLASS_NAME;
-import static com.android.launcher3.model.PackageUpdatedTask.OP_UPDATE;
-import static com.android.launcher3.pm.UserCache.ACTION_PROFILE_AVAILABLE;
-import static com.android.launcher3.pm.UserCache.ACTION_PROFILE_UNAVAILABLE;
-import static com.android.launcher3.testing.shared.TestProtocol.sDebugTracing;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageInstaller;
-import android.content.pm.ShortcutInfo;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Pair;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.WorkerThread;
-
-import com.android.launcher3.celllayout.CellPosMapper;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.model.AddWorkspaceItemsTask;
-import com.android.launcher3.model.AllAppsList;
-import com.android.launcher3.model.BaseLauncherBinder;
-import com.android.launcher3.model.BgDataModel;
-import com.android.launcher3.model.BgDataModel.Callbacks;
-import com.android.launcher3.model.CacheDataUpdatedTask;
-import com.android.launcher3.model.ItemInstallQueue;
-import com.android.launcher3.model.LoaderTask;
-import com.android.launcher3.model.ModelDbController;
-import com.android.launcher3.model.ModelDelegate;
-import com.android.launcher3.model.ModelLauncherCallbacks;
-import com.android.launcher3.model.ModelTaskController;
-import com.android.launcher3.model.ModelWriter;
-import com.android.launcher3.model.PackageInstallStateChangedTask;
-import com.android.launcher3.model.PackageUpdatedTask;
-import com.android.launcher3.model.ReloadStringCacheTask;
-import com.android.launcher3.model.ShortcutsChangedTask;
-import com.android.launcher3.model.UserLockStateChangedTask;
-import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.pm.InstallSessionTracker;
-import com.android.launcher3.pm.PackageInstallInfo;
-import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.shortcuts.ShortcutRequest;
-import com.android.launcher3.util.ApplicationInfoWrapper;
-import com.android.launcher3.util.IntSet;
-import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.PackageManagerHelper;
-import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.util.Preconditions;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.concurrent.CancellationException;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-
-/**
- * Maintains in-memory state of the Launcher. It is expected that there should be only one
- * LauncherModel object held in a static. Also provide APIs for updating the database state
- * for the Launcher.
- */
-public class LauncherModel implements InstallSessionTracker.Callback {
- private static final boolean DEBUG_RECEIVER = false;
-
- static final String TAG = "Launcher.Model";
-
- @NonNull
- private final LauncherAppState mApp;
- @NonNull
- private final PackageManagerHelper mPmHelper;
- @NonNull
- private final ModelDbController mModelDbController;
- @NonNull
- private final Object mLock = new Object();
- @Nullable
- private LoaderTask mLoaderTask;
- private boolean mIsLoaderTaskRunning;
-
- // only allow this once per reboot to reload work apps
- private boolean mShouldReloadWorkProfile = true;
-
- // Indicates whether the current model data is valid or not.
- // We start off with everything not loaded. After that, we assume that
- // our monitoring of the package manager provides all updates and we never
- // need to do a requery. This is only ever touched from the loader thread.
- private boolean mModelLoaded;
- private boolean mModelDestroyed = false;
- public boolean isModelLoaded() {
- synchronized (mLock) {
- return mModelLoaded && mLoaderTask == null && !mModelDestroyed;
- }
- }
-
- @NonNull
- private final ArrayList<Callbacks> mCallbacksList = new ArrayList<>(1);
-
- // < only access in worker thread >
- @NonNull
- private final AllAppsList mBgAllAppsList;
-
- /**
- * All the static data should be accessed on the background thread, A lock should be acquired
- * on this object when accessing any data from this model.
- */
- @NonNull
- private final BgDataModel mBgDataModel = new BgDataModel();
-
- @NonNull
- private final ModelDelegate mModelDelegate;
-
- private int mLastLoadId = -1;
-
- // Runnable to check if the shortcuts permission has changed.
- @NonNull
- private final Runnable mDataValidationCheck = new Runnable() {
- @Override
- public void run() {
- if (mModelLoaded) {
- mModelDelegate.validateData();
- }
- }
- };
-
- LauncherModel(@NonNull final Context context, @NonNull final LauncherAppState app,
- @NonNull final IconCache iconCache, @NonNull final AppFilter appFilter,
- @NonNull final PackageManagerHelper pmHelper, final boolean isPrimaryInstance) {
- mApp = app;
- mPmHelper = pmHelper;
- mModelDbController = new ModelDbController(context);
- mBgAllAppsList = new AllAppsList(iconCache, appFilter);
- mModelDelegate = ModelDelegate.newInstance(context, app, mPmHelper, mBgAllAppsList,
- mBgDataModel, isPrimaryInstance);
- }
-
- @NonNull
- public ModelDelegate getModelDelegate() {
- return mModelDelegate;
- }
-
- public ModelDbController getModelDbController() {
- return mModelDbController;
- }
-
- public ModelLauncherCallbacks newModelCallbacks() {
- return new ModelLauncherCallbacks(this::enqueueModelUpdateTask);
- }
-
- /**
- * Adds the provided items to the workspace.
- */
- public void addAndBindAddedWorkspaceItems(
- @NonNull final List<Pair<ItemInfo, Object>> itemList) {
- for (Callbacks cb : getCallbacks()) {
- cb.preAddApps();
- }
- enqueueModelUpdateTask(new AddWorkspaceItemsTask(itemList));
- }
-
- @NonNull
- public ModelWriter getWriter(final boolean verifyChanges, CellPosMapper cellPosMapper,
- @Nullable final Callbacks owner) {
- return new ModelWriter(mApp.getContext(), this, mBgDataModel, verifyChanges, cellPosMapper,
- owner);
- }
-
- /**
- * Called when the icon for an app changes, outside of package event
- */
- @WorkerThread
- public void onAppIconChanged(@NonNull final String packageName,
- @NonNull final UserHandle user) {
- // Update the icon for the calendar package
- Context context = mApp.getContext();
- enqueueModelUpdateTask(new PackageUpdatedTask(OP_UPDATE, user, packageName));
-
- List<ShortcutInfo> pinnedShortcuts = new ShortcutRequest(context, user)
- .forPackage(packageName).query(ShortcutRequest.PINNED);
- if (!pinnedShortcuts.isEmpty()) {
- enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, pinnedShortcuts, user,
- false));
- }
- }
-
- /**
- * Called when the workspace items have drastically changed
- */
- public void onWorkspaceUiChanged() {
- MODEL_EXECUTOR.execute(mModelDelegate::workspaceLoadComplete);
- }
-
- /**
- * Called when the model is destroyed
- */
- public void destroy() {
- mModelDestroyed = true;
- MODEL_EXECUTOR.execute(mModelDelegate::destroy);
- }
-
- public void onBroadcastIntent(@NonNull final Intent intent) {
- if (DEBUG_RECEIVER || sDebugTracing) Log.d(TAG, "onReceive intent=" + intent);
- final String action = intent.getAction();
- if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
- // If we have changed locale we need to clear out the labels in all apps/workspace.
- forceReload();
- } else if (ACTION_DEVICE_POLICY_RESOURCE_UPDATED.equals(action)) {
- enqueueModelUpdateTask(new ReloadStringCacheTask(mModelDelegate));
- } else if (IS_STUDIO_BUILD && ACTION_FORCE_ROLOAD.equals(action)) {
- for (Callbacks cb : getCallbacks()) {
- if (cb instanceof Launcher) {
- ((Launcher) cb).recreate();
- }
- }
- }
- }
-
- /**
- * Called then there use a user event
- * @see UserCache#addUserEventListener
- */
- public void onUserEvent(UserHandle user, String action) {
- if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action)
- && mShouldReloadWorkProfile) {
- mShouldReloadWorkProfile = false;
- forceReload();
- } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action)
- || Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
- mShouldReloadWorkProfile = false;
- enqueueModelUpdateTask(new PackageUpdatedTask(
- PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user));
- } else if (UserCache.ACTION_PROFILE_LOCKED.equals(action)
- || UserCache.ACTION_PROFILE_UNLOCKED.equals(action)) {
- enqueueModelUpdateTask(new UserLockStateChangedTask(
- user, UserCache.ACTION_PROFILE_UNLOCKED.equals(action)));
- } else if (UserCache.ACTION_PROFILE_ADDED.equals(action)
- || UserCache.ACTION_PROFILE_REMOVED.equals(action)) {
- forceReload();
- } else if (ACTION_PROFILE_AVAILABLE.equals(action)
- || ACTION_PROFILE_UNAVAILABLE.equals(action)) {
- /*
- * This broadcast is only available when android.os.Flags.allowPrivateProfile() is set.
- * For Work-profile this broadcast will be sent in addition to
- * ACTION_MANAGED_PROFILE_AVAILABLE/UNAVAILABLE.
- * So effectively, this if block only handles the non-work profile case.
- */
- enqueueModelUpdateTask(new PackageUpdatedTask(
- PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user));
- }
- if (Intent.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
- LauncherPrefs.get(mApp.getContext()).put(WORK_EDU_STEP, 0);
- }
- }
-
- /**
- * Reloads the workspace items from the DB and re-binds the workspace. This should generally
- * not be called as DB updates are automatically followed by UI update
- */
- public void forceReload() {
- synchronized (mLock) {
- // Stop any existing loaders first, so they don't set mModelLoaded to true later
- stopLoader();
- mModelLoaded = false;
- }
-
- // Start the loader if launcher is already running, otherwise the loader will run,
- // the next time launcher starts
- if (hasCallbacks()) {
- startLoader();
- }
- }
-
- /**
- * Rebinds all existing callbacks with already loaded model
- */
- public void rebindCallbacks() {
- if (hasCallbacks()) {
- startLoader();
- }
- }
-
- /**
- * Removes an existing callback
- */
- public void removeCallbacks(@NonNull final Callbacks callbacks) {
- synchronized (mCallbacksList) {
- Preconditions.assertUIThread();
- if (mCallbacksList.remove(callbacks)) {
- if (stopLoader()) {
- // Rebind existing callbacks
- startLoader();
- }
- }
- }
- }
-
- /**
- * Adds a callbacks to receive model updates
- * @return true if workspace load was performed synchronously
- */
- public boolean addCallbacksAndLoad(@NonNull final Callbacks callbacks) {
- synchronized (mLock) {
- addCallbacks(callbacks);
- return startLoader(new Callbacks[] { callbacks });
-
- }
- }
-
- /**
- * Adds a callbacks to receive model updates
- */
- public void addCallbacks(@NonNull final Callbacks callbacks) {
- Preconditions.assertUIThread();
- synchronized (mCallbacksList) {
- mCallbacksList.add(callbacks);
- }
- }
-
- /**
- * Starts the loader. Tries to bind {@params synchronousBindPage} synchronously if possible.
- * @return true if the page could be bound synchronously.
- */
- public boolean startLoader() {
- return startLoader(new Callbacks[0]);
- }
-
- private boolean startLoader(@NonNull final Callbacks[] newCallbacks) {
- // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
- ItemInstallQueue.INSTANCE.get(mApp.getContext())
- .pauseModelPush(ItemInstallQueue.FLAG_LOADER_RUNNING);
- synchronized (mLock) {
- // If there is already one running, tell it to stop.
- boolean wasRunning = stopLoader();
- boolean bindDirectly = mModelLoaded && !mIsLoaderTaskRunning;
- boolean bindAllCallbacks = wasRunning || !bindDirectly || newCallbacks.length == 0;
- final Callbacks[] callbacksList = bindAllCallbacks ? getCallbacks() : newCallbacks;
-
- if (callbacksList.length > 0) {
- // Clear any pending bind-runnables from the synchronized load process.
- for (Callbacks cb : callbacksList) {
- MAIN_EXECUTOR.execute(cb::clearPendingBinds);
- }
-
- BaseLauncherBinder launcherBinder = new BaseLauncherBinder(
- mApp, mBgDataModel, mBgAllAppsList, callbacksList);
- if (bindDirectly) {
- // Divide the set of loaded items into those that we are binding synchronously,
- // and everything else that is to be bound normally (asynchronously).
- launcherBinder.bindWorkspace(bindAllCallbacks, /* isBindSync= */ true);
- // For now, continue posting the binding of AllApps as there are other
- // issues that arise from that.
- launcherBinder.bindAllApps();
- launcherBinder.bindDeepShortcuts();
- launcherBinder.bindWidgets();
- if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
- mModelDelegate.bindAllModelExtras(callbacksList);
- }
- return true;
- } else {
- stopLoader();
- mLoaderTask = new LoaderTask(
- mApp, mBgAllAppsList, mBgDataModel, mModelDelegate, launcherBinder);
-
- // Always post the loader task, instead of running directly
- // (even on same thread) so that we exit any nested synchronized blocks
- MODEL_EXECUTOR.post(mLoaderTask);
- }
- }
- }
- return false;
- }
-
- /**
- * If there is already a loader task running, tell it to stop.
- * @return true if an existing loader was stopped.
- */
- private boolean stopLoader() {
- synchronized (mLock) {
- LoaderTask oldTask = mLoaderTask;
- mLoaderTask = null;
- if (oldTask != null) {
- oldTask.stopLocked();
- return true;
- }
- return false;
- }
- }
-
- /**
- * Loads the model if not loaded
- * @param callback called with the data model upon successful load or null on model thread.
- */
- public void loadAsync(@NonNull final Consumer<BgDataModel> callback) {
- synchronized (mLock) {
- if (!mModelLoaded && !mIsLoaderTaskRunning) {
- startLoader();
- }
- }
- MODEL_EXECUTOR.post(() -> callback.accept(isModelLoaded() ? mBgDataModel : null));
- }
-
- @Override
- public void onInstallSessionCreated(@NonNull final PackageInstallInfo sessionInfo) {
- if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
- enqueueModelUpdateTask((taskController, dataModel, apps) -> {
- apps.addPromiseApp(mApp.getContext(), sessionInfo);
- taskController.bindApplicationsIfNeeded();
- });
- }
- }
-
- @Override
- public void onSessionFailure(@NonNull final String packageName,
- @NonNull final UserHandle user) {
- enqueueModelUpdateTask((taskController, dataModel, apps) -> {
- IconCache iconCache = mApp.getIconCache();
- final IntSet removedIds = new IntSet();
- HashSet<WorkspaceItemInfo> archivedWorkspaceItemsToCacheRefresh = new HashSet<>();
- boolean isAppArchived =
- new ApplicationInfoWrapper(mApp.getContext(), packageName, user).isArchived();
- synchronized (dataModel) {
- if (isAppArchived) {
- // Remove package icon cache entry for archived app in case of a session
- // failure.
- mApp.getIconCache().remove(
- new ComponentName(packageName, packageName + EMPTY_CLASS_NAME),
- user);
- }
-
- for (ItemInfo info : dataModel.itemsIdMap) {
- if (info instanceof WorkspaceItemInfo
- && ((WorkspaceItemInfo) info).hasPromiseIconUi()
- && user.equals(info.user)
- && info.getIntent() != null) {
- if (TextUtils.equals(packageName, info.getIntent().getPackage())) {
- removedIds.add(info.id);
- }
- if (((WorkspaceItemInfo) info).isArchived()) {
- WorkspaceItemInfo workspaceItem = (WorkspaceItemInfo) info;
- // Refresh icons on the workspace for archived apps.
- iconCache.getTitleAndIcon(workspaceItem,
- workspaceItem.usingLowResIcon());
- archivedWorkspaceItemsToCacheRefresh.add(workspaceItem);
- }
- }
- }
-
- if (isAppArchived) {
- apps.updateIconsAndLabels(new HashSet<>(List.of(packageName)), user);
- }
- }
-
- if (!removedIds.isEmpty() && !isAppArchived) {
- taskController.deleteAndBindComponentsRemoved(
- ItemInfoMatcher.ofItemIds(removedIds),
- "removed because install session failed");
- }
- if (!archivedWorkspaceItemsToCacheRefresh.isEmpty()) {
- taskController.bindUpdatedWorkspaceItems(
- archivedWorkspaceItemsToCacheRefresh.stream().toList());
- }
- if (isAppArchived) {
- taskController.bindApplicationsIfNeeded();
- }
- });
- }
-
- @Override
- public void onPackageStateChanged(@NonNull final PackageInstallInfo installInfo) {
- enqueueModelUpdateTask(new PackageInstallStateChangedTask(installInfo));
- }
-
- /**
- * Updates the icons and label of all pending icons for the provided package name.
- */
- @Override
- public void onUpdateSessionDisplay(@NonNull final PackageUserKey key,
- @NonNull final PackageInstaller.SessionInfo info) {
- mApp.getIconCache().updateSessionCache(key, info);
-
- HashSet<String> packages = new HashSet<>();
- packages.add(key.mPackageName);
- enqueueModelUpdateTask(new CacheDataUpdatedTask(
- CacheDataUpdatedTask.OP_SESSION_UPDATE, key.mUser, packages));
- }
-
- public class LoaderTransaction implements AutoCloseable {
-
- @NonNull
- private final LoaderTask mTask;
-
- private LoaderTransaction(@NonNull final LoaderTask task) throws CancellationException {
- synchronized (mLock) {
- if (mLoaderTask != task) {
- throw new CancellationException("Loader already stopped");
- }
- mLastLoadId++;
- mTask = task;
- mIsLoaderTaskRunning = true;
- mModelLoaded = false;
- }
- }
-
- public void commit() {
- synchronized (mLock) {
- // Everything loaded bind the data.
- mModelLoaded = true;
- }
- }
-
- @Override
- public void close() {
- synchronized (mLock) {
- // If we are still the last one to be scheduled, remove ourselves.
- if (mLoaderTask == mTask) {
- mLoaderTask = null;
- }
- mIsLoaderTaskRunning = false;
- }
- }
- }
-
- public LoaderTransaction beginLoader(@NonNull final LoaderTask task)
- throws CancellationException {
- return new LoaderTransaction(task);
- }
-
- /**
- * Refreshes the cached shortcuts if the shortcut permission has changed.
- * Current implementation simply reloads the workspace, but it can be optimized to
- * use partial updates similar to {@link UserCache}
- */
- public void validateModelDataOnResume() {
- MODEL_EXECUTOR.getHandler().removeCallbacks(mDataValidationCheck);
- MODEL_EXECUTOR.post(mDataValidationCheck);
- }
-
- /**
- * Called when the icons for packages have been updated in the icon cache.
- */
- public void onPackageIconsUpdated(@NonNull final HashSet<String> updatedPackages,
- @NonNull final UserHandle user) {
- // If any package icon has changed (app was updated while launcher was dead),
- // update the corresponding shortcuts.
- enqueueModelUpdateTask(new CacheDataUpdatedTask(
- CacheDataUpdatedTask.OP_CACHE_UPDATE, user, updatedPackages));
- }
-
- /**
- * Called when the labels for the widgets has updated in the icon cache.
- */
- public void onWidgetLabelsUpdated(@NonNull final HashSet<String> updatedPackages,
- @NonNull final UserHandle user) {
- enqueueModelUpdateTask((taskController, dataModel, apps) -> {
- dataModel.widgetsModel.onPackageIconsUpdated(updatedPackages, user, mApp);
- taskController.bindUpdatedWidgets(dataModel);
- });
- }
-
- public void enqueueModelUpdateTask(@NonNull final ModelUpdateTask task) {
- if (mModelDestroyed) {
- return;
- }
- MODEL_EXECUTOR.execute(() -> {
- if (!isModelLoaded()) {
- // Loader has not yet run.
- return;
- }
- ModelTaskController controller = new ModelTaskController(
- mApp, mBgDataModel, mBgAllAppsList, this, MAIN_EXECUTOR);
- task.execute(controller, mBgDataModel, mBgAllAppsList);
- });
- }
-
- /**
- * A task to be executed on the current callbacks on the UI thread.
- * If there is no current callbacks, the task is ignored.
- */
- public interface CallbackTask {
-
- void execute(@NonNull Callbacks callbacks);
- }
-
- public interface ModelUpdateTask {
-
- void execute(@NonNull ModelTaskController taskController,
- @NonNull BgDataModel dataModel, @NonNull AllAppsList apps);
- }
-
- public void updateAndBindWorkspaceItem(@NonNull final WorkspaceItemInfo si,
- @NonNull final ShortcutInfo info) {
- updateAndBindWorkspaceItem(() -> {
- si.updateFromDeepShortcutInfo(info, mApp.getContext());
- mApp.getIconCache().getShortcutIcon(si, info);
- return si;
- });
- }
-
- /**
- * Utility method to update a shortcut on the background thread.
- */
- public void updateAndBindWorkspaceItem(
- @NonNull final Supplier<WorkspaceItemInfo> itemProvider) {
- enqueueModelUpdateTask((taskController, dataModel, apps) -> {
- WorkspaceItemInfo info = itemProvider.get();
- taskController.getModelWriter().updateItemInDatabase(info);
- ArrayList<WorkspaceItemInfo> update = new ArrayList<>();
- update.add(info);
- taskController.bindUpdatedWorkspaceItems(update);
- });
- }
-
- public void refreshAndBindWidgetsAndShortcuts(@Nullable final PackageUserKey packageUser) {
- enqueueModelUpdateTask((taskController, dataModel, apps) -> {
- dataModel.widgetsModel.update(taskController.getApp(), packageUser);
- taskController.bindUpdatedWidgets(dataModel);
- });
- }
-
- public void dumpState(@Nullable final String prefix, @Nullable final FileDescriptor fd,
- @NonNull final PrintWriter writer, @NonNull final String[] args) {
- if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
- writer.println(prefix + "All apps list: size=" + mBgAllAppsList.data.size());
- for (AppInfo info : mBgAllAppsList.data) {
- writer.println(prefix + " title=\"" + info.title
- + "\" bitmapIcon=" + info.bitmap.icon
- + " componentName=" + info.componentName.getPackageName());
- }
- writer.println();
- }
- mModelDelegate.dump(prefix, fd, writer, args);
- mBgDataModel.dump(prefix, fd, writer, args);
- }
-
- /**
- * Returns true if there are any callbacks attached to the model
- */
- public boolean hasCallbacks() {
- synchronized (mCallbacksList) {
- return !mCallbacksList.isEmpty();
- }
- }
-
- /**
- * Returns an array of currently attached callbacks
- */
- @NonNull
- public Callbacks[] getCallbacks() {
- synchronized (mCallbacksList) {
- return mCallbacksList.toArray(new Callbacks[mCallbacksList.size()]);
- }
- }
-
- /**
- * Returns the ID for the last model load. If the load ID doesn't match for a transaction, the
- * transaction should be ignored.
- */
- public int getLastLoadId() {
- return mLastLoadId;
- }
-}
diff --git a/src/com/android/launcher3/LauncherModel.kt b/src/com/android/launcher3/LauncherModel.kt
new file mode 100644
index 0000000..e7b9d89
--- /dev/null
+++ b/src/com/android/launcher3/LauncherModel.kt
@@ -0,0 +1,601 @@
+/*
+ * Copyright (C) 2008 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.launcher3
+
+import android.app.admin.DevicePolicyManager
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageInstaller
+import android.content.pm.ShortcutInfo
+import android.os.UserHandle
+import android.text.TextUtils
+import android.util.Log
+import android.util.Pair
+import androidx.annotation.WorkerThread
+import com.android.launcher3.celllayout.CellPosMapper
+import com.android.launcher3.config.FeatureFlags
+import com.android.launcher3.icons.IconCache
+import com.android.launcher3.icons.cache.BaseIconCache
+import com.android.launcher3.model.AddWorkspaceItemsTask
+import com.android.launcher3.model.AllAppsList
+import com.android.launcher3.model.BaseLauncherBinder
+import com.android.launcher3.model.BgDataModel
+import com.android.launcher3.model.CacheDataUpdatedTask
+import com.android.launcher3.model.ItemInstallQueue
+import com.android.launcher3.model.LoaderTask
+import com.android.launcher3.model.ModelDbController
+import com.android.launcher3.model.ModelDelegate
+import com.android.launcher3.model.ModelLauncherCallbacks
+import com.android.launcher3.model.ModelTaskController
+import com.android.launcher3.model.ModelWriter
+import com.android.launcher3.model.PackageInstallStateChangedTask
+import com.android.launcher3.model.PackageUpdatedTask
+import com.android.launcher3.model.ReloadStringCacheTask
+import com.android.launcher3.model.ShortcutsChangedTask
+import com.android.launcher3.model.UserLockStateChangedTask
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.pm.InstallSessionTracker
+import com.android.launcher3.pm.PackageInstallInfo
+import com.android.launcher3.pm.UserCache
+import com.android.launcher3.shortcuts.ShortcutRequest
+import com.android.launcher3.testing.shared.TestProtocol.sDebugTracing
+import com.android.launcher3.util.ApplicationInfoWrapper
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.Executors.MODEL_EXECUTOR
+import com.android.launcher3.util.IntSet
+import com.android.launcher3.util.ItemInfoMatcher
+import com.android.launcher3.util.PackageManagerHelper
+import com.android.launcher3.util.PackageUserKey
+import com.android.launcher3.util.Preconditions
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import java.util.concurrent.CancellationException
+import java.util.function.Consumer
+import java.util.function.Supplier
+
+/**
+ * Maintains in-memory state of the Launcher. It is expected that there should be only one
+ * LauncherModel object held in a static. Also provide APIs for updating the database state for the
+ * Launcher.
+ */
+class LauncherModel(
+ private val context: Context,
+ private val mApp: LauncherAppState,
+ private val iconCache: IconCache,
+ private val appFilter: AppFilter,
+ private val mPmHelper: PackageManagerHelper,
+ isPrimaryInstance: Boolean,
+) : InstallSessionTracker.Callback {
+
+ private val mCallbacksList = ArrayList<BgDataModel.Callbacks>(1)
+
+ // < only access in worker thread >
+ private val mBgAllAppsList = AllAppsList(iconCache, appFilter)
+
+ /**
+ * All the static data should be accessed on the background thread, A lock should be acquired on
+ * this object when accessing any data from this model.
+ */
+ private val mBgDataModel = BgDataModel()
+
+ val modelDelegate: ModelDelegate =
+ ModelDelegate.newInstance(
+ context,
+ mApp,
+ mPmHelper,
+ mBgAllAppsList,
+ mBgDataModel,
+ isPrimaryInstance,
+ )
+
+ val modelDbController = ModelDbController(context)
+
+ private val mLock = Any()
+
+ private var mLoaderTask: LoaderTask? = null
+ private var mIsLoaderTaskRunning = false
+
+ // only allow this once per reboot to reload work apps
+ private var mShouldReloadWorkProfile = true
+
+ // Indicates whether the current model data is valid or not.
+ // We start off with everything not loaded. After that, we assume that
+ // our monitoring of the package manager provides all updates and we never
+ // need to do a requery. This is only ever touched from the loader thread.
+ private var mModelLoaded = false
+ private var mModelDestroyed = false
+
+ fun isModelLoaded() =
+ synchronized(mLock) { mModelLoaded && mLoaderTask == null && !mModelDestroyed }
+
+ /**
+ * Returns the ID for the last model load. If the load ID doesn't match for a transaction, the
+ * transaction should be ignored.
+ */
+ var lastLoadId: Int = -1
+ private set
+
+ // Runnable to check if the shortcuts permission has changed.
+ private val mDataValidationCheck = Runnable {
+ if (mModelLoaded) {
+ modelDelegate.validateData()
+ }
+ }
+
+ fun newModelCallbacks() = ModelLauncherCallbacks(this::enqueueModelUpdateTask)
+
+ /** Adds the provided items to the workspace. */
+ fun addAndBindAddedWorkspaceItems(itemList: List<Pair<ItemInfo?, Any?>?>) {
+ callbacks.forEach { it.preAddApps() }
+ enqueueModelUpdateTask(AddWorkspaceItemsTask(itemList))
+ }
+
+ fun getWriter(
+ verifyChanges: Boolean,
+ cellPosMapper: CellPosMapper?,
+ owner: BgDataModel.Callbacks?,
+ ) = ModelWriter(mApp.context, this, mBgDataModel, verifyChanges, cellPosMapper, owner)
+
+ /** Called when the icon for an app changes, outside of package event */
+ @WorkerThread
+ fun onAppIconChanged(packageName: String, user: UserHandle) {
+ // Update the icon for the calendar package
+ enqueueModelUpdateTask(PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, user, packageName))
+ val pinnedShortcuts: List<ShortcutInfo> =
+ ShortcutRequest(context, user).forPackage(packageName).query(ShortcutRequest.PINNED)
+ if (pinnedShortcuts.isNotEmpty()) {
+ enqueueModelUpdateTask(ShortcutsChangedTask(packageName, pinnedShortcuts, user, false))
+ }
+ }
+
+ /** Called when the workspace items have drastically changed */
+ fun onWorkspaceUiChanged() {
+ MODEL_EXECUTOR.execute(modelDelegate::workspaceLoadComplete)
+ }
+
+ /** Called when the model is destroyed */
+ fun destroy() {
+ mModelDestroyed = true
+ MODEL_EXECUTOR.execute(modelDelegate::destroy)
+ }
+
+ fun onBroadcastIntent(intent: Intent) {
+ if (DEBUG_RECEIVER || sDebugTracing) Log.d(TAG, "onReceive intent=$intent")
+ val action = intent.action
+ if (Intent.ACTION_LOCALE_CHANGED == action) {
+ // If we have changed locale we need to clear out the labels in all apps/workspace.
+ forceReload()
+ } else if (DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED == action) {
+ enqueueModelUpdateTask(ReloadStringCacheTask(this.modelDelegate))
+ } else if (BuildConfig.IS_STUDIO_BUILD && LauncherAppState.ACTION_FORCE_ROLOAD == action) {
+ forceReload()
+ }
+ }
+
+ /**
+ * Called then there use a user event
+ *
+ * @see UserCache.addUserEventListener
+ */
+ fun onUserEvent(user: UserHandle, action: String) {
+ if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE == action && mShouldReloadWorkProfile) {
+ mShouldReloadWorkProfile = false
+ forceReload()
+ } else if (
+ Intent.ACTION_MANAGED_PROFILE_AVAILABLE == action ||
+ Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE == action
+ ) {
+ mShouldReloadWorkProfile = false
+ enqueueModelUpdateTask(
+ PackageUpdatedTask(PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user)
+ )
+ } else if (
+ UserCache.ACTION_PROFILE_LOCKED == action || UserCache.ACTION_PROFILE_UNLOCKED == action
+ ) {
+ enqueueModelUpdateTask(
+ UserLockStateChangedTask(user, UserCache.ACTION_PROFILE_UNLOCKED == action)
+ )
+ } else if (
+ UserCache.ACTION_PROFILE_ADDED == action || UserCache.ACTION_PROFILE_REMOVED == action
+ ) {
+ forceReload()
+ } else if (
+ UserCache.ACTION_PROFILE_AVAILABLE == action ||
+ UserCache.ACTION_PROFILE_UNAVAILABLE == action
+ ) {
+ /*
+ * This broadcast is only available when android.os.Flags.allowPrivateProfile() is set.
+ * For Work-profile this broadcast will be sent in addition to
+ * ACTION_MANAGED_PROFILE_AVAILABLE/UNAVAILABLE.
+ * So effectively, this if block only handles the non-work profile case.
+ */
+ enqueueModelUpdateTask(
+ PackageUpdatedTask(PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user)
+ )
+ }
+ if (Intent.ACTION_MANAGED_PROFILE_REMOVED == action) {
+ LauncherPrefs.get(mApp.context).put(LauncherPrefs.WORK_EDU_STEP, 0)
+ }
+ }
+
+ /**
+ * Reloads the workspace items from the DB and re-binds the workspace. This should generally not
+ * be called as DB updates are automatically followed by UI update
+ */
+ fun forceReload() {
+ synchronized(mLock) {
+ // Stop any existing loaders first, so they don't set mModelLoaded to true later
+ stopLoader()
+ mModelLoaded = false
+ }
+
+ // Start the loader if launcher is already running, otherwise the loader will run,
+ // the next time launcher starts
+ if (hasCallbacks()) {
+ startLoader()
+ }
+ }
+
+ /** Rebinds all existing callbacks with already loaded model */
+ fun rebindCallbacks() {
+ if (hasCallbacks()) {
+ startLoader()
+ }
+ }
+
+ /** Removes an existing callback */
+ fun removeCallbacks(callbacks: BgDataModel.Callbacks) {
+ synchronized(mCallbacksList) {
+ Preconditions.assertUIThread()
+ if (mCallbacksList.remove(callbacks)) {
+ if (stopLoader()) {
+ // Rebind existing callbacks
+ startLoader()
+ }
+ }
+ }
+ }
+
+ /**
+ * Adds a callbacks to receive model updates
+ *
+ * @return true if workspace load was performed synchronously
+ */
+ fun addCallbacksAndLoad(callbacks: BgDataModel.Callbacks): Boolean {
+ synchronized(mLock) {
+ addCallbacks(callbacks)
+ return startLoader(arrayOf(callbacks))
+ }
+ }
+
+ /** Adds a callbacks to receive model updates */
+ fun addCallbacks(callbacks: BgDataModel.Callbacks) {
+ Preconditions.assertUIThread()
+ synchronized(mCallbacksList) { mCallbacksList.add(callbacks) }
+ }
+
+ /**
+ * Starts the loader. Tries to bind {@params synchronousBindPage} synchronously if possible.
+ *
+ * @return true if the page could be bound synchronously.
+ */
+ fun startLoader() = startLoader(arrayOf())
+
+ private fun startLoader(newCallbacks: Array<BgDataModel.Callbacks>): Boolean {
+ // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
+ ItemInstallQueue.INSTANCE.get(context).pauseModelPush(ItemInstallQueue.FLAG_LOADER_RUNNING)
+ synchronized(mLock) {
+ // If there is already one running, tell it to stop.
+ val wasRunning = stopLoader()
+ val bindDirectly = mModelLoaded && !mIsLoaderTaskRunning
+ val bindAllCallbacks = wasRunning || !bindDirectly || newCallbacks.isEmpty()
+ val callbacksList = if (bindAllCallbacks) callbacks else newCallbacks
+ if (callbacksList.isNotEmpty()) {
+ // Clear any pending bind-runnables from the synchronized load process.
+ callbacksList.forEach { MAIN_EXECUTOR.execute(it::clearPendingBinds) }
+
+ val launcherBinder =
+ BaseLauncherBinder(mApp, mBgDataModel, mBgAllAppsList, callbacksList)
+ if (bindDirectly) {
+ // Divide the set of loaded items into those that we are binding synchronously,
+ // and everything else that is to be bound normally (asynchronously).
+ launcherBinder.bindWorkspace(bindAllCallbacks, /* isBindSync= */ true)
+ // For now, continue posting the binding of AllApps as there are other
+ // issues that arise from that.
+ launcherBinder.bindAllApps()
+ launcherBinder.bindDeepShortcuts()
+ launcherBinder.bindWidgets()
+ if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
+ this.modelDelegate.bindAllModelExtras(callbacksList)
+ }
+ return true
+ } else {
+ stopLoader()
+ mLoaderTask =
+ LoaderTask(
+ mApp,
+ mBgAllAppsList,
+ mBgDataModel,
+ this.modelDelegate,
+ launcherBinder,
+ )
+
+ // Always post the loader task, instead of running directly
+ // (even on same thread) so that we exit any nested synchronized blocks
+ MODEL_EXECUTOR.post(mLoaderTask)
+ }
+ }
+ }
+ return false
+ }
+
+ /**
+ * If there is already a loader task running, tell it to stop.
+ *
+ * @return true if an existing loader was stopped.
+ */
+ private fun stopLoader(): Boolean {
+ synchronized(mLock) {
+ val oldTask: LoaderTask? = mLoaderTask
+ mLoaderTask = null
+ if (oldTask != null) {
+ oldTask.stopLocked()
+ return true
+ }
+ return false
+ }
+ }
+
+ /**
+ * Loads the model if not loaded
+ *
+ * @param callback called with the data model upon successful load or null on model thread.
+ */
+ fun loadAsync(callback: Consumer<BgDataModel?>) {
+ synchronized(mLock) {
+ if (!mModelLoaded && !mIsLoaderTaskRunning) {
+ startLoader()
+ }
+ }
+ MODEL_EXECUTOR.post { callback.accept(if (isModelLoaded()) mBgDataModel else null) }
+ }
+
+ override fun onInstallSessionCreated(sessionInfo: PackageInstallInfo) {
+ if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
+ enqueueModelUpdateTask { taskController, _, apps ->
+ apps.addPromiseApp(mApp.context, sessionInfo)
+ taskController.bindApplicationsIfNeeded()
+ }
+ }
+ }
+
+ override fun onSessionFailure(packageName: String, user: UserHandle) {
+ enqueueModelUpdateTask { taskController, dataModel, apps ->
+ val iconCache = mApp.iconCache
+ val removedIds = IntSet()
+ val archivedWorkspaceItemsToCacheRefresh = HashSet<WorkspaceItemInfo>()
+ val isAppArchived = ApplicationInfoWrapper(mApp.context, packageName, user).isArchived()
+ synchronized(dataModel) {
+ if (isAppArchived) {
+ // Remove package icon cache entry for archived app in case of a session
+ // failure.
+ mApp.iconCache.remove(
+ ComponentName(packageName, packageName + BaseIconCache.EMPTY_CLASS_NAME),
+ user,
+ )
+ }
+ for (info in dataModel.itemsIdMap) {
+ if (
+ (info is WorkspaceItemInfo && info.hasPromiseIconUi()) &&
+ user == info.user &&
+ info.intent != null
+ ) {
+ if (TextUtils.equals(packageName, info.intent!!.getPackage())) {
+ removedIds.add(info.id)
+ }
+ if (info.isArchived()) {
+ // Refresh icons on the workspace for archived apps.
+ iconCache.getTitleAndIcon(info, info.usingLowResIcon())
+ archivedWorkspaceItemsToCacheRefresh.add(info)
+ }
+ }
+ }
+ if (isAppArchived) {
+ apps.updateIconsAndLabels(hashSetOf(packageName), user)
+ }
+ }
+
+ if (!removedIds.isEmpty && !isAppArchived) {
+ taskController.deleteAndBindComponentsRemoved(
+ ItemInfoMatcher.ofItemIds(removedIds),
+ "removed because install session failed",
+ )
+ }
+ if (archivedWorkspaceItemsToCacheRefresh.isNotEmpty()) {
+ taskController.bindUpdatedWorkspaceItems(
+ archivedWorkspaceItemsToCacheRefresh.stream().toList()
+ )
+ }
+ if (isAppArchived) {
+ taskController.bindApplicationsIfNeeded()
+ }
+ }
+ }
+
+ override fun onPackageStateChanged(installInfo: PackageInstallInfo) {
+ enqueueModelUpdateTask(PackageInstallStateChangedTask(installInfo))
+ }
+
+ /** Updates the icons and label of all pending icons for the provided package name. */
+ override fun onUpdateSessionDisplay(key: PackageUserKey, info: PackageInstaller.SessionInfo) {
+ mApp.iconCache.updateSessionCache(key, info)
+
+ val packages = HashSet<String>()
+ packages.add(key.mPackageName)
+ enqueueModelUpdateTask(
+ CacheDataUpdatedTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, key.mUser, packages)
+ )
+ }
+
+ inner class LoaderTransaction(task: LoaderTask) : AutoCloseable {
+ private var mTask: LoaderTask? = null
+
+ init {
+ synchronized(mLock) {
+ if (mLoaderTask !== task) {
+ throw CancellationException("Loader already stopped")
+ }
+ this@LauncherModel.lastLoadId++
+ mTask = task
+ mIsLoaderTaskRunning = true
+ mModelLoaded = false
+ }
+ }
+
+ fun commit() {
+ synchronized(mLock) {
+ // Everything loaded bind the data.
+ mModelLoaded = true
+ }
+ }
+
+ override fun close() {
+ synchronized(mLock) {
+ // If we are still the last one to be scheduled, remove ourselves.
+ if (mLoaderTask === mTask) {
+ mLoaderTask = null
+ }
+ mIsLoaderTaskRunning = false
+ }
+ }
+ }
+
+ @Throws(CancellationException::class)
+ fun beginLoader(task: LoaderTask) = LoaderTransaction(task)
+
+ /**
+ * Refreshes the cached shortcuts if the shortcut permission has changed. Current implementation
+ * simply reloads the workspace, but it can be optimized to use partial updates similar to
+ * [UserCache]
+ */
+ fun validateModelDataOnResume() {
+ MODEL_EXECUTOR.handler.removeCallbacks(mDataValidationCheck)
+ MODEL_EXECUTOR.post(mDataValidationCheck)
+ }
+
+ /** Called when the icons for packages have been updated in the icon cache. */
+ fun onPackageIconsUpdated(updatedPackages: HashSet<String?>, user: UserHandle) {
+ // If any package icon has changed (app was updated while launcher was dead),
+ // update the corresponding shortcuts.
+ enqueueModelUpdateTask(
+ CacheDataUpdatedTask(CacheDataUpdatedTask.OP_CACHE_UPDATE, user, updatedPackages)
+ )
+ }
+
+ /** Called when the labels for the widgets has updated in the icon cache. */
+ fun onWidgetLabelsUpdated(updatedPackages: HashSet<String?>, user: UserHandle) {
+ enqueueModelUpdateTask { taskController, dataModel, _ ->
+ dataModel.widgetsModel.onPackageIconsUpdated(updatedPackages, user, mApp)
+ taskController.bindUpdatedWidgets(dataModel)
+ }
+ }
+
+ fun enqueueModelUpdateTask(task: ModelUpdateTask) {
+ if (mModelDestroyed) {
+ return
+ }
+ MODEL_EXECUTOR.execute {
+ if (!isModelLoaded()) {
+ // Loader has not yet run.
+ return@execute
+ }
+ task.execute(
+ ModelTaskController(mApp, mBgDataModel, mBgAllAppsList, this, MAIN_EXECUTOR),
+ mBgDataModel,
+ mBgAllAppsList,
+ )
+ }
+ }
+
+ /**
+ * A task to be executed on the current callbacks on the UI thread. If there is no current
+ * callbacks, the task is ignored.
+ */
+ fun interface CallbackTask {
+ fun execute(callbacks: BgDataModel.Callbacks)
+ }
+
+ fun interface ModelUpdateTask {
+ fun execute(taskController: ModelTaskController, dataModel: BgDataModel, apps: AllAppsList)
+ }
+
+ fun updateAndBindWorkspaceItem(si: WorkspaceItemInfo, info: ShortcutInfo) {
+ updateAndBindWorkspaceItem {
+ si.updateFromDeepShortcutInfo(info, mApp.context)
+ mApp.iconCache.getShortcutIcon(si, info)
+ si
+ }
+ }
+
+ /** Utility method to update a shortcut on the background thread. */
+ private fun updateAndBindWorkspaceItem(itemProvider: Supplier<WorkspaceItemInfo>) {
+ enqueueModelUpdateTask { taskController, _, _ ->
+ val info = itemProvider.get()
+ taskController.getModelWriter().updateItemInDatabase(info)
+ taskController.bindUpdatedWorkspaceItems(listOf(info))
+ }
+ }
+
+ fun refreshAndBindWidgetsAndShortcuts(packageUser: PackageUserKey?) {
+ enqueueModelUpdateTask { taskController, dataModel, _ ->
+ dataModel.widgetsModel.update(taskController.app, packageUser)
+ taskController.bindUpdatedWidgets(dataModel)
+ }
+ }
+
+ fun dumpState(prefix: String?, fd: FileDescriptor?, writer: PrintWriter, args: Array<String?>) {
+ if (args.isNotEmpty() && TextUtils.equals(args[0], "--all")) {
+ writer.println(prefix + "All apps list: size=" + mBgAllAppsList.data.size)
+ for (info in mBgAllAppsList.data) {
+ writer.println(
+ "$prefix title=\"${info.title}\" bitmapIcon=${info.bitmap.icon} componentName=${info.targetPackage}"
+ )
+ }
+ writer.println()
+ }
+ modelDelegate.dump(prefix, fd, writer, args)
+ mBgDataModel.dump(prefix, fd, writer, args)
+ }
+
+ /** Returns true if there are any callbacks attached to the model */
+ fun hasCallbacks() = synchronized(mCallbacksList) { mCallbacksList.isNotEmpty() }
+
+ /** Returns an array of currently attached callbacks */
+ val callbacks: Array<BgDataModel.Callbacks>
+ get() {
+ synchronized(mCallbacksList) {
+ return mCallbacksList.toTypedArray<BgDataModel.Callbacks>()
+ }
+ }
+
+ companion object {
+ private const val DEBUG_RECEIVER = false
+
+ const val TAG = "Launcher.Model"
+ }
+}
diff --git a/src/com/android/launcher3/LauncherPrefChangeListener.java b/src/com/android/launcher3/LauncherPrefChangeListener.java
new file mode 100644
index 0000000..3e9a846
--- /dev/null
+++ b/src/com/android/launcher3/LauncherPrefChangeListener.java
@@ -0,0 +1,38 @@
+/*
+ * 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.launcher3;
+
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+
+/**
+ * Listener for changes in [LauncherPrefs].
+ * <p>
+ * The listener also serves as an [OnSharedPreferenceChangeListener] where
+ * [onSharedPreferenceChanged] delegates to [onPrefChanged]. Overriding [onSharedPreferenceChanged]
+ * breaks compatibility with [SharedPreferences].
+ */
+public interface LauncherPrefChangeListener extends OnSharedPreferenceChangeListener {
+
+ /** Callback invoked when the preference for [key] has changed. */
+ void onPrefChanged(String key);
+
+ @Override
+ default void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ onPrefChanged(key);
+ }
+}
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index 13181e8..7ebfc18 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -18,7 +18,6 @@
import android.content.Context
import android.content.Context.MODE_PRIVATE
import android.content.SharedPreferences
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import androidx.annotation.VisibleForTesting
import com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN
import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY
@@ -34,11 +33,177 @@
import com.android.launcher3.util.Themes
/**
- * Use same context for shared preferences, so that we use a single cached instance
+ * Manages Launcher [SharedPreferences] through [Item] instances.
*
* TODO(b/262721340): Replace all direct SharedPreference refs with LauncherPrefs / Item methods.
*/
-class LauncherPrefs(private val encryptedContext: Context) : SafeCloseable {
+abstract class LauncherPrefs : SafeCloseable {
+
+ /** Returns the value with type [T] for [item]. */
+ abstract fun <T> get(item: ContextualItem<T>): T
+
+ /** Returns the value with type [T] for [item]. */
+ abstract fun <T> get(item: ConstantItem<T>): T
+
+ /** Stores the values for each item in preferences. */
+ abstract fun put(vararg itemsToValues: Pair<Item, Any>)
+
+ /** Stores the [value] with type [T] for [item] in preferences. */
+ abstract fun <T : Any> put(item: Item, value: T)
+
+ /** Synchronous version of [put]. */
+ abstract fun putSync(vararg itemsToValues: Pair<Item, Any>)
+
+ /** Registers [listener] for [items]. */
+ abstract fun addListener(listener: LauncherPrefChangeListener, vararg items: Item)
+
+ /** Unregisters [listener] for [items]. */
+ abstract fun removeListener(listener: LauncherPrefChangeListener, vararg items: Item)
+
+ /** Returns `true` iff all [items] have a value. */
+ abstract fun has(vararg items: Item): Boolean
+
+ /** Removes the value for each item in [items]. */
+ abstract fun remove(vararg items: Item)
+
+ /** Synchronous version of [remove]. */
+ abstract fun removeSync(vararg items: Item)
+
+ companion object {
+ @VisibleForTesting const val BOOT_AWARE_PREFS_KEY = "boot_aware_prefs"
+
+ @JvmField
+ var INSTANCE = MainThreadInitializedObject<LauncherPrefs> { LauncherPrefsImpl(it) }
+
+ @JvmStatic fun get(context: Context): LauncherPrefs = INSTANCE.get(context)
+
+ const val TASKBAR_PINNING_KEY = "TASKBAR_PINNING_KEY"
+ const val TASKBAR_PINNING_DESKTOP_MODE_KEY = "TASKBAR_PINNING_DESKTOP_MODE_KEY"
+ const val SHOULD_SHOW_SMARTSPACE_KEY = "SHOULD_SHOW_SMARTSPACE_KEY"
+ @JvmField
+ val ICON_STATE = nonRestorableItem("pref_icon_shape_path", "", EncryptionType.ENCRYPTED)
+
+ @JvmField
+ val ENABLE_TWOLINE_ALLAPPS_TOGGLE = backedUpItem("pref_enable_two_line_toggle", false)
+ @JvmField
+ val THEMED_ICONS = backedUpItem(Themes.KEY_THEMED_ICONS, false, EncryptionType.ENCRYPTED)
+ @JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "")
+ @JvmField val WORK_EDU_STEP = backedUpItem("showed_work_profile_edu", 0)
+ @JvmField
+ val WORKSPACE_SIZE =
+ backedUpItem(DeviceGridState.KEY_WORKSPACE_SIZE, "", EncryptionType.ENCRYPTED)
+ @JvmField
+ val HOTSEAT_COUNT =
+ backedUpItem(DeviceGridState.KEY_HOTSEAT_COUNT, -1, EncryptionType.ENCRYPTED)
+ @JvmField
+ val TASKBAR_PINNING =
+ backedUpItem(TASKBAR_PINNING_KEY, false, EncryptionType.DEVICE_PROTECTED)
+ @JvmField
+ val TASKBAR_PINNING_IN_DESKTOP_MODE =
+ backedUpItem(TASKBAR_PINNING_DESKTOP_MODE_KEY, true, EncryptionType.DEVICE_PROTECTED)
+
+ @JvmField
+ val DEVICE_TYPE =
+ backedUpItem(
+ DeviceGridState.KEY_DEVICE_TYPE,
+ InvariantDeviceProfile.TYPE_PHONE,
+ EncryptionType.ENCRYPTED,
+ )
+ @JvmField
+ val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "", EncryptionType.ENCRYPTED)
+ @JvmField
+ val SHOULD_SHOW_SMARTSPACE =
+ backedUpItem(
+ SHOULD_SHOW_SMARTSPACE_KEY,
+ WIDGET_ON_FIRST_SCREEN,
+ EncryptionType.DEVICE_PROTECTED,
+ )
+ @JvmField
+ val RESTORE_DEVICE =
+ backedUpItem(
+ RestoreDbTask.RESTORED_DEVICE_TYPE,
+ InvariantDeviceProfile.TYPE_PHONE,
+ EncryptionType.ENCRYPTED,
+ )
+ @JvmField
+ val IS_FIRST_LOAD_AFTER_RESTORE =
+ nonRestorableItem(FIRST_LOAD_AFTER_RESTORE_KEY, false, EncryptionType.ENCRYPTED)
+ @JvmField val APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_IDS, "")
+ @JvmField val OLD_APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_OLD_IDS, "")
+ @JvmField
+ val GRID_NAME =
+ ConstantItem(
+ "idp_grid_name",
+ isBackedUp = true,
+ defaultValue = null,
+ encryptionType = EncryptionType.ENCRYPTED,
+ type = String::class.java,
+ )
+ @JvmField
+ val ALLOW_ROTATION =
+ backedUpItem(RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY, Boolean::class.java) {
+ RotationHelper.getAllowRotationDefaultValue(DisplayController.INSTANCE.get(it).info)
+ }
+
+ // Preferences for widget configurations
+ @JvmField
+ val RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN =
+ backedUpItem("launcher.reconfigurable_widget_education_tip_seen", false)
+
+ @JvmStatic
+ fun <T> backedUpItem(
+ sharedPrefKey: String,
+ defaultValue: T,
+ encryptionType: EncryptionType = EncryptionType.ENCRYPTED,
+ ): ConstantItem<T> =
+ ConstantItem(sharedPrefKey, isBackedUp = true, defaultValue, encryptionType)
+
+ @JvmStatic
+ fun <T> backedUpItem(
+ sharedPrefKey: String,
+ type: Class<out T>,
+ encryptionType: EncryptionType = EncryptionType.ENCRYPTED,
+ defaultValueFromContext: (c: Context) -> T,
+ ): ContextualItem<T> =
+ ContextualItem(
+ sharedPrefKey,
+ isBackedUp = true,
+ defaultValueFromContext,
+ encryptionType,
+ type,
+ )
+
+ @JvmStatic
+ fun <T> nonRestorableItem(
+ sharedPrefKey: String,
+ defaultValue: T,
+ encryptionType: EncryptionType = EncryptionType.ENCRYPTED,
+ ): ConstantItem<T> =
+ ConstantItem(sharedPrefKey, isBackedUp = false, defaultValue, encryptionType)
+
+ @Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
+ @JvmStatic
+ fun getPrefs(context: Context): SharedPreferences {
+ // Use application context for shared preferences, so we use single cached instance
+ return context.applicationContext.getSharedPreferences(
+ SHARED_PREFERENCES_KEY,
+ MODE_PRIVATE,
+ )
+ }
+
+ @Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
+ @JvmStatic
+ fun getDevicePrefs(context: Context): SharedPreferences {
+ // Use application context for shared preferences, so we use a single cached instance
+ return context.applicationContext.getSharedPreferences(
+ DEVICE_PREFERENCES_KEY,
+ MODE_PRIVATE,
+ )
+ }
+ }
+}
+
+private class LauncherPrefsImpl(private val encryptedContext: Context) : LauncherPrefs() {
private val deviceProtectedStorageContext =
encryptedContext.createDeviceProtectedStorageContext()
@@ -54,11 +219,11 @@
else item.encryptedPrefs
/** Wrapper around `getInner` for a `ContextualItem` */
- fun <T> get(item: ContextualItem<T>): T =
+ override fun <T> get(item: ContextualItem<T>): T =
getInner(item, item.defaultValueFromContext(encryptedContext))
/** Wrapper around `getInner` for an `Item` */
- fun <T> get(item: ConstantItem<T>): T = getInner(item, item.defaultValue)
+ override fun <T> get(item: ConstantItem<T>): T = getInner(item, item.defaultValue)
/**
* Retrieves the value for an [Item] from [SharedPreferences]. It handles method typing via the
@@ -97,17 +262,17 @@
* prepareToPutValue(itemsToValues) for every distinct `SharedPreferences` file present in the
* provided item configurations.
*/
- fun put(vararg itemsToValues: Pair<Item, Any>): Unit =
+ override fun put(vararg itemsToValues: Pair<Item, Any>): Unit =
prepareToPutValues(itemsToValues).forEach { it.apply() }
/** See referenced `put` method above. */
- fun <T : Any> put(item: Item, value: T): Unit = put(item.to(value))
+ override fun <T : Any> put(item: Item, value: T): Unit = put(item.to(value))
/**
* Synchronously stores all the values provided according to their associated Item
* configuration.
*/
- fun putSync(vararg itemsToValues: Pair<Item, Any>): Unit =
+ override fun putSync(vararg itemsToValues: Pair<Item, Any>): Unit =
prepareToPutValues(itemsToValues).forEach { it.commit() }
/**
@@ -152,7 +317,7 @@
@Suppress("UNCHECKED_CAST")
private fun SharedPreferences.Editor.putValue(
item: Item,
- value: Any?
+ value: Any?,
): SharedPreferences.Editor =
when (item.type) {
String::class.java -> putString(item.sharedPrefKey, value as? String)
@@ -176,7 +341,7 @@
* `SharedPreferences` files associated with the provided list of items. The listener will need
* to filter update notifications so they don't activate for non-relevant updates.
*/
- fun addListener(listener: OnSharedPreferenceChangeListener, vararg items: Item) {
+ override fun addListener(listener: LauncherPrefChangeListener, vararg items: Item) {
items
.map { chooseSharedPreferences(it) }
.distinct()
@@ -187,7 +352,7 @@
* Stops the listener from getting notified of any more updates to any of the
* `SharedPreferences` files associated with any of the provided list of [Item].
*/
- fun removeListener(listener: OnSharedPreferenceChangeListener, vararg items: Item) {
+ override fun removeListener(listener: LauncherPrefChangeListener, vararg items: Item) {
// If a listener is not registered to a SharedPreference, unregistering it does nothing
items
.map { chooseSharedPreferences(it) }
@@ -199,7 +364,7 @@
* Checks if all the provided [Item] have values stored in their corresponding
* `SharedPreferences` files.
*/
- fun has(vararg items: Item): Boolean {
+ override fun has(vararg items: Item): Boolean {
items
.groupBy { chooseSharedPreferences(it) }
.forEach { (prefs, itemsSublist) ->
@@ -211,10 +376,10 @@
/**
* Asynchronously removes the [Item]'s value from its corresponding `SharedPreferences` file.
*/
- fun remove(vararg items: Item) = prepareToRemove(items).forEach { it.apply() }
+ override fun remove(vararg items: Item) = prepareToRemove(items).forEach { it.apply() }
/** Synchronously removes the [Item]'s value from its corresponding `SharedPreferences` file. */
- fun removeSync(vararg items: Item) = prepareToRemove(items).forEach { it.commit() }
+ override fun removeSync(vararg items: Item) = prepareToRemove(items).forEach { it.commit() }
/**
* Removes the key value pairs stored in `SharedPreferences` for each corresponding Item. If the
@@ -244,138 +409,6 @@
}
override fun close() {}
-
- companion object {
- @VisibleForTesting const val BOOT_AWARE_PREFS_KEY = "boot_aware_prefs"
-
- @JvmField var INSTANCE = MainThreadInitializedObject { LauncherPrefs(it) }
-
- @JvmStatic fun get(context: Context): LauncherPrefs = INSTANCE.get(context)
-
- const val TASKBAR_PINNING_KEY = "TASKBAR_PINNING_KEY"
- const val TASKBAR_PINNING_DESKTOP_MODE_KEY = "TASKBAR_PINNING_DESKTOP_MODE_KEY"
- const val SHOULD_SHOW_SMARTSPACE_KEY = "SHOULD_SHOW_SMARTSPACE_KEY"
- @JvmField
- val ICON_STATE = nonRestorableItem("pref_icon_shape_path", "", EncryptionType.ENCRYPTED)
-
- @JvmField
- val ENABLE_TWOLINE_ALLAPPS_TOGGLE = backedUpItem("pref_enable_two_line_toggle", false)
- @JvmField
- val THEMED_ICONS = backedUpItem(Themes.KEY_THEMED_ICONS, false, EncryptionType.ENCRYPTED)
- @JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "")
- @JvmField val WORK_EDU_STEP = backedUpItem("showed_work_profile_edu", 0)
- @JvmField
- val WORKSPACE_SIZE =
- backedUpItem(DeviceGridState.KEY_WORKSPACE_SIZE, "", EncryptionType.ENCRYPTED)
- @JvmField
- val HOTSEAT_COUNT =
- backedUpItem(DeviceGridState.KEY_HOTSEAT_COUNT, -1, EncryptionType.ENCRYPTED)
- @JvmField
- val TASKBAR_PINNING =
- backedUpItem(TASKBAR_PINNING_KEY, false, EncryptionType.DEVICE_PROTECTED)
- @JvmField
- val TASKBAR_PINNING_IN_DESKTOP_MODE =
- backedUpItem(TASKBAR_PINNING_DESKTOP_MODE_KEY, true, EncryptionType.DEVICE_PROTECTED)
-
- @JvmField
- val DEVICE_TYPE =
- backedUpItem(
- DeviceGridState.KEY_DEVICE_TYPE,
- InvariantDeviceProfile.TYPE_PHONE,
- EncryptionType.ENCRYPTED
- )
- @JvmField
- val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "", EncryptionType.ENCRYPTED)
- @JvmField
- val SHOULD_SHOW_SMARTSPACE =
- backedUpItem(
- SHOULD_SHOW_SMARTSPACE_KEY,
- WIDGET_ON_FIRST_SCREEN,
- EncryptionType.DEVICE_PROTECTED
- )
- @JvmField
- val RESTORE_DEVICE =
- backedUpItem(
- RestoreDbTask.RESTORED_DEVICE_TYPE,
- InvariantDeviceProfile.TYPE_PHONE,
- EncryptionType.ENCRYPTED
- )
- @JvmField
- val IS_FIRST_LOAD_AFTER_RESTORE =
- nonRestorableItem(FIRST_LOAD_AFTER_RESTORE_KEY, false, EncryptionType.ENCRYPTED)
- @JvmField val APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_IDS, "")
- @JvmField val OLD_APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_OLD_IDS, "")
- @JvmField
- val GRID_NAME =
- ConstantItem(
- "idp_grid_name",
- isBackedUp = true,
- defaultValue = null,
- encryptionType = EncryptionType.ENCRYPTED,
- type = String::class.java
- )
- @JvmField
- val ALLOW_ROTATION =
- backedUpItem(RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY, Boolean::class.java) {
- RotationHelper.getAllowRotationDefaultValue(DisplayController.INSTANCE.get(it).info)
- }
-
- // Preferences for widget configurations
- @JvmField
- val RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN =
- backedUpItem("launcher.reconfigurable_widget_education_tip_seen", false)
-
- @JvmStatic
- fun <T> backedUpItem(
- sharedPrefKey: String,
- defaultValue: T,
- encryptionType: EncryptionType = EncryptionType.ENCRYPTED
- ): ConstantItem<T> =
- ConstantItem(sharedPrefKey, isBackedUp = true, defaultValue, encryptionType)
-
- @JvmStatic
- fun <T> backedUpItem(
- sharedPrefKey: String,
- type: Class<out T>,
- encryptionType: EncryptionType = EncryptionType.ENCRYPTED,
- defaultValueFromContext: (c: Context) -> T
- ): ContextualItem<T> =
- ContextualItem(
- sharedPrefKey,
- isBackedUp = true,
- defaultValueFromContext,
- encryptionType,
- type
- )
-
- @JvmStatic
- fun <T> nonRestorableItem(
- sharedPrefKey: String,
- defaultValue: T,
- encryptionType: EncryptionType = EncryptionType.ENCRYPTED
- ): ConstantItem<T> =
- ConstantItem(sharedPrefKey, isBackedUp = false, defaultValue, encryptionType)
-
- @Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
- @JvmStatic
- fun getPrefs(context: Context): SharedPreferences {
- // Use application context for shared preferences, so we use single cached instance
- return context.applicationContext.getSharedPreferences(
- SHARED_PREFERENCES_KEY,
- MODE_PRIVATE
- )
- }
-
- @Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
- @JvmStatic
- fun getDevicePrefs(context: Context): SharedPreferences {
- // Use application context for shared preferences, so we use a single cached instance
- return context.applicationContext.getSharedPreferences(
- DEVICE_PREFERENCES_KEY,
- MODE_PRIVATE
- )
- }
- }
}
abstract class Item {
@@ -395,7 +428,7 @@
val defaultValue: T,
override val encryptionType: EncryptionType,
// The default value can be null. If so, the type needs to be explicitly stated, or else NPE
- override val type: Class<out T> = defaultValue!!::class.java
+ override val type: Class<out T> = defaultValue!!::class.java,
) : Item() {
fun get(c: Context): T = LauncherPrefs.get(c).get(this)
@@ -406,7 +439,7 @@
override val isBackedUp: Boolean,
private val defaultSupplier: (c: Context) -> T,
override val encryptionType: EncryptionType,
- override val type: Class<out T>
+ override val type: Class<out T>,
) : Item() {
private var default: T? = null
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 7bec768..5defef3 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -317,10 +317,7 @@
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
| InputType.TYPE_TEXT_FLAG_CAP_WORDS);
mFolderName.forceDisableSuggestions(true);
- mFolderName.setPadding(mFolderName.getPaddingLeft(),
- (getFooterHeight() - mFolderName.getLineHeight()) / 2,
- mFolderName.getPaddingRight(),
- (getFooterHeight() - mFolderName.getLineHeight()) / 2);
+
mKeyboardInsetAnimationCallback = new KeyboardInsetAnimationCallback(this);
setWindowInsetsAnimationCallback(mKeyboardInsetAnimationCallback);
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 9dc2d24..fe26194 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -373,8 +373,9 @@
// Update footer
mPageIndicator.setVisibility(getPageCount() > 1 ? View.VISIBLE : View.GONE);
// Set the gravity as LEFT or RIGHT instead of START, as START depends on the actual text.
- mFolder.getFolderName().setGravity(getPageCount() > 1
- ? (mIsRtl ? Gravity.RIGHT : Gravity.LEFT) : Gravity.CENTER_HORIZONTAL);
+ int horizontalGravity = getPageCount() > 1
+ ? (mIsRtl ? Gravity.RIGHT : Gravity.LEFT) : Gravity.CENTER_HORIZONTAL;
+ mFolder.getFolderName().setGravity(horizontalGravity | Gravity.CENTER_VERTICAL);
}
public int getDesiredWidth() {
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index 303290d..763f3ba 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -18,6 +18,7 @@
import static android.animation.ValueAnimator.areAnimatorsEnabled;
+import static com.android.launcher3.Flags.enableStateManagerProtoLog;
import static com.android.launcher3.anim.AnimatorPlaybackController.callListenerCommandRecursively;
import static com.android.launcher3.states.StateAnimationConfig.HANDLE_STATE_APPLY;
import static com.android.launcher3.states.StateAnimationConfig.SKIP_ALL_ANIMATIONS;
@@ -39,6 +40,7 @@
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
import com.android.launcher3.states.StateAnimationConfig.AnimationPropertyFlags;
+import com.android.launcher3.util.StateManagerProtoLogProxy;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -243,7 +245,10 @@
private void goToState(
STATE_TYPE state, boolean animated, long delay, AnimatorListener listener) {
- if (DEBUG) {
+ if (enableStateManagerProtoLog()) {
+ StateManagerProtoLogProxy.logGoToState(
+ mState, state, getTrimmedStackTrace("StateManager.goToState"));
+ } else if (DEBUG) {
Log.d(TAG, "goToState - fromState: " + mState + ", toState: " + state
+ ", partial trace:\n" + getTrimmedStackTrace("StateManager.goToState"));
}
@@ -331,7 +336,10 @@
*/
public AnimatorSet createAtomicAnimation(
STATE_TYPE fromState, STATE_TYPE toState, StateAnimationConfig config) {
- if (DEBUG) {
+ if (enableStateManagerProtoLog()) {
+ StateManagerProtoLogProxy.logCreateAtomicAnimation(
+ mState, toState, getTrimmedStackTrace("StateManager.createAtomicAnimation"));
+ } else if (DEBUG) {
Log.d(TAG, "createAtomicAnimation - fromState: " + fromState + ", toState: " + toState
+ ", partial trace:\n" + getTrimmedStackTrace(
"StateManager.createAtomicAnimation"));
@@ -408,7 +416,9 @@
mState = state;
mStatefulContainer.onStateSetStart(mState);
- if (DEBUG) {
+ if (enableStateManagerProtoLog()) {
+ StateManagerProtoLogProxy.logOnStateTransitionStart(state);
+ } else if (DEBUG) {
Log.d(TAG, "onStateTransitionStart - state: " + state);
}
for (int i = mListeners.size() - 1; i >= 0; i--) {
@@ -428,7 +438,9 @@
setRestState(null);
}
- if (DEBUG) {
+ if (enableStateManagerProtoLog()) {
+ StateManagerProtoLogProxy.logOnStateTransitionEnd(state);
+ } else if (DEBUG) {
Log.d(TAG, "onStateTransitionEnd - state: " + state);
}
for (int i = mListeners.size() - 1; i >= 0; i--) {
@@ -468,7 +480,11 @@
* Cancels the current animation.
*/
public void cancelAnimation() {
- if (DEBUG && mConfig.currentAnimation != null) {
+ if (enableStateManagerProtoLog()) {
+ StateManagerProtoLogProxy.logCancelAnimation(
+ mConfig.currentAnimation != null,
+ getTrimmedStackTrace("StateManager.cancelAnimation"));
+ } else if (DEBUG && mConfig.currentAnimation != null) {
Log.d(TAG, "cancelAnimation - with ongoing animation"
+ ", partial trace:\n" + getTrimmedStackTrace("StateManager.cancelAnimation"));
}
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index fdb37f0..b3bcada 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -26,8 +26,6 @@
import static com.android.launcher3.util.window.WindowManagerProxy.MIN_TABLET_WIDTH;
import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Handler;
import android.os.Message;
@@ -36,13 +34,14 @@
import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherPrefChangeListener;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.util.DisplayController;
/**
* Utility class to manage launcher rotation
*/
-public class RotationHelper implements OnSharedPreferenceChangeListener,
+public class RotationHelper implements LauncherPrefChangeListener,
DeviceProfile.OnDeviceProfileChangeListener,
DisplayController.DisplayInfoChangeListener {
@@ -112,7 +111,7 @@
}
@Override
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
+ public void onPrefChanged(String s) {
if (mDestroyed || mIgnoreAutoRotateSettings) return;
boolean wasRotationEnabled = mHomeRotationEnabled;
mHomeRotationEnabled = LauncherPrefs.get(mActivity).get(ALLOW_ROTATION);
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index c59cc81..0b45118 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -35,7 +35,6 @@
import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.Intent;
-import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
@@ -51,6 +50,7 @@
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.InvariantDeviceProfile.DeviceType;
+import com.android.launcher3.LauncherPrefChangeListener;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.Utilities;
import com.android.launcher3.logging.FileLog;
@@ -116,8 +116,7 @@
private Info mInfo;
private boolean mDestroyed = false;
- private SharedPreferences.OnSharedPreferenceChangeListener
- mTaskbarPinningPreferenceChangeListener;
+ private LauncherPrefChangeListener mTaskbarPinningPreferenceChangeListener;
@VisibleForTesting
protected DisplayController(Context context) {
@@ -142,19 +141,18 @@
}
private void attachTaskbarPinningSharedPreferenceChangeListener(Context context) {
- mTaskbarPinningPreferenceChangeListener =
- (sharedPreferences, key) -> {
- LauncherPrefs prefs = LauncherPrefs.get(mContext);
- boolean isTaskbarPinningChanged = TASKBAR_PINNING_KEY.equals(key)
- && mInfo.mIsTaskbarPinned != prefs.get(TASKBAR_PINNING);
- boolean isTaskbarPinningDesktopModeChanged =
- TASKBAR_PINNING_DESKTOP_MODE_KEY.equals(key)
- && mInfo.mIsTaskbarPinnedInDesktopMode != prefs.get(
- TASKBAR_PINNING_IN_DESKTOP_MODE);
- if (isTaskbarPinningChanged || isTaskbarPinningDesktopModeChanged) {
- notifyConfigChange();
- }
- };
+ mTaskbarPinningPreferenceChangeListener = key -> {
+ LauncherPrefs prefs = LauncherPrefs.get(mContext);
+ boolean isTaskbarPinningChanged = TASKBAR_PINNING_KEY.equals(key)
+ && mInfo.mIsTaskbarPinned != prefs.get(TASKBAR_PINNING);
+ boolean isTaskbarPinningDesktopModeChanged =
+ TASKBAR_PINNING_DESKTOP_MODE_KEY.equals(key)
+ && mInfo.mIsTaskbarPinnedInDesktopMode != prefs.get(
+ TASKBAR_PINNING_IN_DESKTOP_MODE);
+ if (isTaskbarPinningChanged || isTaskbarPinningDesktopModeChanged) {
+ notifyConfigChange();
+ }
+ };
LauncherPrefs.get(context).addListener(
mTaskbarPinningPreferenceChangeListener, TASKBAR_PINNING);
diff --git a/src/com/android/launcher3/util/SettingsCache.java b/src/com/android/launcher3/util/SettingsCache.java
index a52f75b..a1ed499 100644
--- a/src/com/android/launcher3/util/SettingsCache.java
+++ b/src/com/android/launcher3/util/SettingsCache.java
@@ -91,7 +91,7 @@
new DaggerSingletonObject<>(LauncherBaseAppComponent::getSettingsCache);
@Inject
- SettingsCache(@ApplicationContext final Context context, DaggerSingletonTracker tracker) {
+ SettingsCache(@ApplicationContext Context context, DaggerSingletonTracker tracker) {
super(new Handler(Looper.getMainLooper()));
mResolver = context.getContentResolver();
ExecutorUtil.executeSyncOnMainOrFail(() -> tracker.addCloseable(this));
diff --git a/src_no_quickstep/com/android/launcher3/util/StateManagerProtoLogProxy.java b/src_no_quickstep/com/android/launcher3/util/StateManagerProtoLogProxy.java
new file mode 100644
index 0000000..34e15f7
--- /dev/null
+++ b/src_no_quickstep/com/android/launcher3/util/StateManagerProtoLogProxy.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util;
+
+/**
+ * Proxy class used for StateManager ProtoLog support.
+ */
+public class StateManagerProtoLogProxy {
+
+ public static void logGoToState(Object fromState, Object toState, String trace) { }
+
+ public static void logCreateAtomicAnimation(Object fromState, Object toState, String trace) { }
+
+ public static void logOnStateTransitionStart(Object state) { }
+
+ public static void logOnStateTransitionEnd(Object state) { }
+
+ public static void logCancelAnimation(boolean animationOngoing, String trace) { }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/AppWidgetsRestoredReceiverTest.kt b/tests/multivalentTests/src/com/android/launcher3/AppWidgetsRestoredReceiverTest.kt
index 21abab4..0e06051 100644
--- a/tests/multivalentTests/src/com/android/launcher3/AppWidgetsRestoredReceiverTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/AppWidgetsRestoredReceiverTest.kt
@@ -29,7 +29,7 @@
@Before
fun setup() {
- launcherPrefs = LauncherPrefs(DeviceHelpers.context)
+ launcherPrefs = LauncherPrefs.get(DeviceHelpers.context)
receiverUnderTest = AppWidgetsRestoredReceiver()
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefs.kt b/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefs.kt
new file mode 100644
index 0000000..058ac05
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefs.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.launcher3
+
+import android.content.Context
+
+/** Emulates Launcher preferences for a test environment. */
+class FakeLauncherPrefs(private val context: Context) : LauncherPrefs() {
+ private val prefsMap = mutableMapOf<String, Any>()
+ private val listeners = mutableSetOf<LauncherPrefChangeListener>()
+
+ @Suppress("UNCHECKED_CAST")
+ override fun <T> get(item: ContextualItem<T>): T {
+ return prefsMap.getOrDefault(item.sharedPrefKey, item.defaultValueFromContext(context)) as T
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ override fun <T> get(item: ConstantItem<T>): T {
+ return prefsMap.getOrDefault(item.sharedPrefKey, item.defaultValue) as T
+ }
+
+ override fun put(vararg itemsToValues: Pair<Item, Any>) = putSync(*itemsToValues)
+
+ override fun <T : Any> put(item: Item, value: T) = putSync(item to value)
+
+ override fun putSync(vararg itemsToValues: Pair<Item, Any>) {
+ itemsToValues
+ .map { (i, v) -> i.sharedPrefKey to v }
+ .forEach { (k, v) ->
+ prefsMap[k] = v
+ notifyChange(k)
+ }
+ }
+
+ override fun addListener(listener: LauncherPrefChangeListener, vararg items: Item) {
+ listeners.add(listener)
+ }
+
+ override fun removeListener(listener: LauncherPrefChangeListener, vararg items: Item) {
+ listeners.remove(listener)
+ }
+
+ override fun has(vararg items: Item) = items.all { it.sharedPrefKey in prefsMap }
+
+ override fun remove(vararg items: Item) = removeSync(*items)
+
+ override fun removeSync(vararg items: Item) {
+ items
+ .filter { it.sharedPrefKey in prefsMap }
+ .forEach {
+ prefsMap.remove(it.sharedPrefKey)
+ notifyChange(it.sharedPrefKey)
+ }
+ }
+
+ override fun close() = Unit
+
+ private fun notifyChange(key: String) = listeners.forEach { it.onPrefChanged(key) }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefsTest.kt b/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefsTest.kt
new file mode 100644
index 0000000..b8e347c
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefsTest.kt
@@ -0,0 +1,134 @@
+/*
+ * 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.launcher3
+
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private val TEST_CONSTANT_ITEM = LauncherPrefs.nonRestorableItem("TEST_BOOLEAN_ITEM", false)
+
+private val TEST_CONTEXTUAL_ITEM =
+ ContextualItem(
+ "TEST_CONTEXTUAL_ITEM",
+ true,
+ { false },
+ EncryptionType.ENCRYPTED,
+ Boolean::class.java,
+ )
+
+@RunWith(LauncherMultivalentJUnit::class)
+class FakeLauncherPrefsTest {
+ private val launcherPrefs = FakeLauncherPrefs(getApplicationContext())
+
+ @Test
+ fun testGet_constantItemNotInPrefs_returnsDefaultValue() {
+ assertThat(launcherPrefs.get(TEST_CONSTANT_ITEM)).isFalse()
+ }
+
+ @Test
+ fun testGet_constantItemInPrefs_returnsStoredValue() {
+ launcherPrefs.put(TEST_CONSTANT_ITEM, true)
+ assertThat(launcherPrefs.get(TEST_CONSTANT_ITEM)).isTrue()
+ }
+
+ @Test
+ fun testGet_contextualItemNotInPrefs_returnsDefaultValue() {
+ assertThat(launcherPrefs.get(TEST_CONTEXTUAL_ITEM)).isFalse()
+ }
+
+ @Test
+ fun testGet_contextualItemInPrefs_returnsStoredValue() {
+ launcherPrefs.put(TEST_CONTEXTUAL_ITEM, true)
+ assertThat(launcherPrefs.get(TEST_CONTEXTUAL_ITEM)).isTrue()
+ }
+
+ @Test
+ fun testPut_multipleItems_storesAll() {
+ launcherPrefs.put(TEST_CONSTANT_ITEM to true, TEST_CONTEXTUAL_ITEM to true)
+ assertThat(launcherPrefs.get(TEST_CONSTANT_ITEM)).isTrue()
+ assertThat(launcherPrefs.get(TEST_CONTEXTUAL_ITEM)).isTrue()
+ }
+
+ @Test
+ fun testHas_itemNotInPrefs_returnsFalse() {
+ assertThat(launcherPrefs.has(TEST_CONSTANT_ITEM)).isFalse()
+ }
+
+ @Test
+ fun testHas_itemInPrefs_returnsTrue() {
+ launcherPrefs.put(TEST_CONSTANT_ITEM, true)
+ assertThat(launcherPrefs.has(TEST_CONSTANT_ITEM)).isTrue()
+ }
+
+ @Test
+ fun testHas_twoItemsWithOneInPrefs_returnsFalse() {
+ launcherPrefs.put(TEST_CONSTANT_ITEM, true)
+ assertThat(launcherPrefs.has(TEST_CONSTANT_ITEM, TEST_CONTEXTUAL_ITEM)).isFalse()
+ }
+
+ @Test
+ fun testHas_twoItemsInPrefs_returnsTrue() {
+ launcherPrefs.put(TEST_CONSTANT_ITEM to true, TEST_CONTEXTUAL_ITEM to true)
+ assertThat(launcherPrefs.has(TEST_CONSTANT_ITEM, TEST_CONTEXTUAL_ITEM)).isTrue()
+ }
+
+ @Test
+ fun testRemove_itemInPrefs_removesItem() {
+ launcherPrefs.put(TEST_CONSTANT_ITEM, true)
+ launcherPrefs.remove(TEST_CONSTANT_ITEM)
+ assertThat(launcherPrefs.has(TEST_CONSTANT_ITEM)).isFalse()
+ }
+
+ @Test
+ fun testRemove_itemsInPrefs_removesItems() {
+ launcherPrefs.put(TEST_CONSTANT_ITEM to true, TEST_CONTEXTUAL_ITEM to true)
+ launcherPrefs.remove(TEST_CONSTANT_ITEM, TEST_CONTEXTUAL_ITEM)
+ assertThat(launcherPrefs.has(TEST_CONSTANT_ITEM, TEST_CONTEXTUAL_ITEM)).isFalse()
+ }
+
+ @Test
+ fun testAddListener_changeItemInPrefs_callsListener() {
+ var changedKey: String? = null
+ launcherPrefs.addListener({ changedKey = it }, TEST_CONSTANT_ITEM)
+ launcherPrefs.put(TEST_CONSTANT_ITEM, true)
+ assertThat(changedKey).isEqualTo(TEST_CONSTANT_ITEM.sharedPrefKey)
+ }
+
+ @Test
+ fun testAddListener_removeItemFromPrefs_callsListener() {
+ var changedKey: String? = null
+ launcherPrefs.put(TEST_CONSTANT_ITEM, true)
+ launcherPrefs.addListener({ changedKey = it }, TEST_CONSTANT_ITEM)
+
+ launcherPrefs.remove(TEST_CONSTANT_ITEM)
+ assertThat(changedKey).isEqualTo(TEST_CONSTANT_ITEM.sharedPrefKey)
+ }
+
+ @Test
+ fun testRemoveListener_changeItemInPrefs_doesNotCallListener() {
+ var changedKey: String? = null
+ val listener = LauncherPrefChangeListener { changedKey = it }
+ launcherPrefs.addListener(listener, TEST_CONSTANT_ITEM)
+
+ launcherPrefs.removeListener(listener)
+ launcherPrefs.put(TEST_CONSTANT_ITEM, true)
+ assertThat(changedKey).isNull()
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/LauncherPrefsTest.kt b/tests/multivalentTests/src/com/android/launcher3/LauncherPrefsTest.kt
index b813095..4aeef2e 100644
--- a/tests/multivalentTests/src/com/android/launcher3/LauncherPrefsTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/LauncherPrefsTest.kt
@@ -17,7 +17,6 @@
import android.content.Context
import android.content.SharedPreferences
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
@@ -63,7 +62,7 @@
@Test
fun addListener_listeningForStringItemUpdates_isCorrectlyNotifiedOfUpdates() {
val latch = CountDownLatch(1)
- val listener = OnSharedPreferenceChangeListener { _, _ -> latch.countDown() }
+ val listener = LauncherPrefChangeListener { latch.countDown() }
with(launcherPrefs) {
putSync(TEST_STRING_ITEM.to(TEST_STRING_ITEM.defaultValue))
@@ -78,7 +77,7 @@
@Test
fun removeListener_previouslyListeningForStringItemUpdates_isNoLongerNotifiedOfUpdates() {
val latch = CountDownLatch(1)
- val listener = OnSharedPreferenceChangeListener { _, _ -> latch.countDown() }
+ val listener = LauncherPrefChangeListener { latch.countDown() }
with(launcherPrefs) {
addListener(listener, TEST_STRING_ITEM)
@@ -94,14 +93,14 @@
@Test
fun addListenerAndRemoveListener_forMultipleItems_bothWorkProperly() {
var latch = CountDownLatch(3)
- val listener = OnSharedPreferenceChangeListener { _, _ -> latch.countDown() }
+ val listener = LauncherPrefChangeListener { latch.countDown() }
with(launcherPrefs) {
addListener(listener, TEST_INT_ITEM, TEST_STRING_ITEM, TEST_BOOLEAN_ITEM)
putSync(
TEST_INT_ITEM.to(TEST_INT_ITEM.defaultValue + 123),
TEST_STRING_ITEM.to(TEST_STRING_ITEM.defaultValue + "abc"),
- TEST_BOOLEAN_ITEM.to(!TEST_BOOLEAN_ITEM.defaultValue)
+ TEST_BOOLEAN_ITEM.to(!TEST_BOOLEAN_ITEM.defaultValue),
)
assertThat(latch.await(WAIT_TIME_IN_SECONDS, TimeUnit.SECONDS)).isTrue()
@@ -110,7 +109,7 @@
putSync(
TEST_INT_ITEM.to(TEST_INT_ITEM.defaultValue),
TEST_STRING_ITEM.to(TEST_STRING_ITEM.defaultValue),
- TEST_BOOLEAN_ITEM.to(TEST_BOOLEAN_ITEM.defaultValue)
+ TEST_BOOLEAN_ITEM.to(TEST_BOOLEAN_ITEM.defaultValue),
)
remove(TEST_INT_ITEM, TEST_STRING_ITEM, TEST_BOOLEAN_ITEM)
@@ -150,7 +149,7 @@
putSync(
TEST_STRING_ITEM.to(TEST_STRING_ITEM.defaultValue),
TEST_INT_ITEM.to(TEST_INT_ITEM.defaultValue),
- TEST_BOOLEAN_ITEM.to(TEST_BOOLEAN_ITEM.defaultValue)
+ TEST_BOOLEAN_ITEM.to(TEST_BOOLEAN_ITEM.defaultValue),
)
assertThat(has(TEST_BOOLEAN_ITEM, TEST_INT_ITEM, TEST_STRING_ITEM)).isTrue()
remove(TEST_STRING_ITEM, TEST_INT_ITEM, TEST_BOOLEAN_ITEM)
@@ -191,7 +190,7 @@
LauncherPrefs.backedUpItem(
TEST_PREF_KEY,
TEST_DEFAULT_VALUE,
- EncryptionType.DEVICE_PROTECTED
+ EncryptionType.DEVICE_PROTECTED,
)
val bootAwarePrefs: SharedPreferences =
@@ -212,7 +211,7 @@
LauncherPrefs.backedUpItem(
TEST_PREF_KEY,
TEST_DEFAULT_VALUE,
- EncryptionType.DEVICE_PROTECTED
+ EncryptionType.DEVICE_PROTECTED,
)
val bootAwarePrefs: SharedPreferences =
diff --git a/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt b/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
index d236551..111ffaa 100644
--- a/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
@@ -29,7 +29,6 @@
import com.android.launcher3.icons.BaseIconFactory
import com.android.launcher3.icons.FastBitmapDrawable
import com.android.launcher3.icons.UserBadgeDrawable
-import com.android.launcher3.model.ModelTestRule
import com.android.launcher3.model.data.FolderInfo
import com.android.launcher3.model.data.ItemInfo
import com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED
@@ -45,7 +44,6 @@
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -54,8 +52,6 @@
@RunWith(AndroidJUnit4::class)
class PreviewItemManagerTest {
- @get:Rule val modelTestRule = ModelTestRule()
-
private lateinit var previewItemManager: PreviewItemManager
private lateinit var context: Context
private lateinit var folderItems: ArrayList<ItemInfo>
@@ -99,8 +95,8 @@
BaseIconFactory(
context,
context.resources.configuration.densityDpi,
- previewItemManager.mIconSize
- )
+ previewItemManager.mIconSize,
+ ),
)
// Set second icon to be non-themed.
@@ -111,8 +107,8 @@
BaseIconFactory(
context,
context.resources.configuration.densityDpi,
- previewItemManager.mIconSize
- )
+ previewItemManager.mIconSize,
+ ),
)
// Set third icon to be themed with badge.
@@ -123,8 +119,8 @@
BaseIconFactory(
context,
context.resources.configuration.densityDpi,
- previewItemManager.mIconSize
- )
+ previewItemManager.mIconSize,
+ ),
)
folderApps[2].bitmap = folderApps[2].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
@@ -137,8 +133,8 @@
BaseIconFactory(
context,
context.resources.configuration.densityDpi,
- previewItemManager.mIconSize
- )
+ previewItemManager.mIconSize,
+ ),
)
defaultThemedIcons = get(context).get(THEMED_ICONS)
diff --git a/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheTest.java b/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheTest.java
index 519108d..ce00b28 100644
--- a/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheTest.java
@@ -68,7 +68,6 @@
import com.android.launcher3.util.ApplicationInfoWrapper;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.util.RoboApiWrapper;
import com.google.common.truth.Truth;
@@ -148,7 +147,6 @@
@Test
public void launcherActivityInfo_cached_in_memory() {
- RoboApiWrapper.INSTANCE.initialize();
ComponentName cn = new ComponentName(TEST_PACKAGE, TEST_ACTIVITY);
UserHandle user = myUserHandle();
ComponentKey cacheKey = new ComponentKey(cn, user);
@@ -213,7 +211,6 @@
@Test
public void item_kept_in_db_if_nothing_changes() {
- RoboApiWrapper.INSTANCE.initialize();
ComponentName cn = new ComponentName(TEST_PACKAGE, TEST_ACTIVITY);
UserHandle user = myUserHandle();
@@ -232,7 +229,6 @@
@Test
public void item_updated_in_db_if_appInfo_changes() {
- RoboApiWrapper.INSTANCE.initialize();
ComponentName cn = new ComponentName(TEST_PACKAGE, TEST_ACTIVITY);
UserHandle user = myUserHandle();
@@ -253,7 +249,6 @@
@Test
public void item_removed_in_db_if_item_removed() {
- RoboApiWrapper.INSTANCE.initialize();
ComponentName cn = new ComponentName(TEST_PACKAGE, TEST_ACTIVITY);
UserHandle user = myUserHandle();
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
index 43dc36b..ce04682 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
@@ -27,7 +27,6 @@
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.times
@@ -44,8 +43,6 @@
@RunWith(AndroidJUnit4::class)
class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() {
- @get:Rule val modelTestRule = ModelTestRule()
-
private lateinit var mDataModelCallbacks: MyCallbacks
private val mWorkspaceItemSpaceFinder: WorkspaceItemSpaceFinder = mock()
@@ -121,18 +118,8 @@
@Test
fun givenMultipleItems_whenExecuteTask_thenAddThem() {
val itemsToAdd =
- arrayOf(
- getNewItem(),
- getExistingItem(),
- getNewItem(),
- getNewItem(),
- getExistingItem(),
- )
- givenNewItemSpaces(
- NewItemSpace(1, 3, 3),
- NewItemSpace(2, 0, 0),
- NewItemSpace(2, 0, 1),
- )
+ arrayOf(getNewItem(), getExistingItem(), getNewItem(), getNewItem(), getExistingItem())
+ givenNewItemSpaces(NewItemSpace(1, 3, 3), NewItemSpace(2, 0, 0), NewItemSpace(2, 0, 1))
val nonEmptyScreenIds = listOf(0, 1)
val addedItems = testAddItems(nonEmptyScreenIds, *itemsToAdd)
@@ -173,7 +160,7 @@
eq(IntArray.wrap(*nonEmptyScreenIds.toIntArray())),
eq(IntArray()),
eq(1),
- eq(1)
+ eq(1),
)
}
@@ -183,7 +170,7 @@
*/
private fun testAddItems(
nonEmptyScreenIds: List<Int>,
- vararg itemsToAdd: WorkspaceItemInfo
+ vararg itemsToAdd: WorkspaceItemInfo,
): List<AddedItem> {
setupWorkspaces(nonEmptyScreenIds)
val task = newTask(*itemsToAdd)
@@ -220,7 +207,7 @@
override fun bindAppsAdded(
newScreens: IntArray?,
addNotAnimated: ArrayList<ItemInfo>,
- addAnimated: ArrayList<ItemInfo>
+ addAnimated: ArrayList<ItemInfo>,
) {
addedItems.addAll(addAnimated.map { AddedItem(it, true) })
addedItems.addAll(addNotAnimated.map { AddedItem(it, false) })
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/AsyncBindingTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/AsyncBindingTest.kt
index dce75b9..ba59253 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/AsyncBindingTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/AsyncBindingTest.kt
@@ -64,8 +64,6 @@
@get:Rule val setFlagsRule = SetFlagsRule()
- @get:Rule val modelTestRule = ModelTestRule()
-
@Spy private var callbacks = MyCallbacks()
@Mock private lateinit var itemInflater: ItemInflater<*>
@@ -138,7 +136,7 @@
@Test
fun test_bind_sync_partially_inflates_on_background() {
modelHelper.loadModelSync()
- assertTrue(modelHelper.model.isModelLoaded)
+ assertTrue(modelHelper.model.isModelLoaded())
callbacks.inflater = itemInflater
val firstPageBindIds = IntSet()
@@ -203,7 +201,7 @@
pendingTasks: RunnableList,
onCompleteSignal: RunnableList,
workspaceItemCount: Int,
- isBindSync: Boolean
+ isBindSync: Boolean,
) {
this.pendingTasks = pendingTasks
this.onCompleteSignal = onCompleteSignal
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java b/tests/multivalentTests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
index 535080a..600af42 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
@@ -64,11 +64,7 @@
@RunWith(AndroidJUnit4.class)
public class CacheDataUpdatedTaskTest {
- @Rule(order = 0)
- public TestRule testStabilityRule = new TestStabilityRule();
-
- @Rule(order = 1)
- public ModelTestRule mModelTestRule = new ModelTestRule();
+ @Rule public TestRule testStabilityRule = new TestStabilityRule();
private static final String PENDING_APP_1 = TEST_PACKAGE + ".pending1";
private static final String PENDING_APP_2 = TEST_PACKAGE + ".pending2";
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java b/tests/multivalentTests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
index e14e145..1e2431f 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
@@ -39,7 +39,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -50,8 +49,6 @@
@RunWith(AndroidJUnit4.class)
public class DefaultLayoutProviderTest {
- @Rule public ModelTestRule rule = new ModelTestRule();
-
private LauncherModelHelper mModelHelper;
private LauncherModelHelper.SandboxModelContext mTargetContext;
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt
index d2d9512..9cc380e 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt
@@ -34,7 +34,6 @@
import com.android.launcher3.util.PackageManagerHelper
import com.android.launcher3.util.PackageUserKey
import junit.framework.Assert.assertEquals
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
@@ -46,8 +45,6 @@
@RunWith(AndroidJUnit4::class)
class FirstScreenBroadcastHelperTest {
- @get:Rule val modelTestRule = ModelTestRule()
-
private val context = spy(InstrumentationRegistry.getInstrumentation().targetContext)
private val mockPmHelper = mock<PackageManagerHelper>()
private val expectedAppPackage = "appPackageExpected"
@@ -70,7 +67,7 @@
container = CONTAINER_HOTSEAT
intent = expectedIntent
},
- LauncherAppWidgetInfo().apply { providerName = expectedComponentName }
+ LauncherAppWidgetInfo().apply { providerName = expectedComponentName },
)
@Test
@@ -89,7 +86,7 @@
val sessionInfoMap: HashMap<PackageUserKey, SessionInfo> =
hashMapOf(
PackageUserKey(unexpectedAppPackage, UserHandle(0)) to sessionInfoExpected,
- PackageUserKey(expectedAppPackage, UserHandle(0)) to sessionInfoUnexpected
+ PackageUserKey(expectedAppPackage, UserHandle(0)) to sessionInfoUnexpected,
)
// When
@@ -98,7 +95,7 @@
packageManagerHelper = mockPmHelper,
firstScreenItems = firstScreenItems,
userKeyToSessionMap = sessionInfoMap,
- allWidgets = listOf()
+ allWidgets = listOf(),
)
// Then
@@ -108,7 +105,7 @@
installerPackage = expectedInstallerPackage,
pendingWorkspaceItems = mutableSetOf(expectedAppPackage),
pendingHotseatItems = mutableSetOf(expectedAppPackage),
- pendingWidgetItems = mutableSetOf(expectedAppPackage)
+ pendingWidgetItems = mutableSetOf(expectedAppPackage),
)
)
@@ -133,7 +130,7 @@
providerName = expectedComponentName
screenId = 0
}
- )
+ ),
)
// Then
@@ -143,7 +140,7 @@
installerPackage = expectedInstallerPackage,
installedHotseatItems = mutableSetOf(expectedAppPackage),
installedWorkspaceItems = mutableSetOf(expectedAppPackage),
- firstScreenInstalledWidgets = mutableSetOf(expectedAppPackage)
+ firstScreenInstalledWidgets = mutableSetOf(expectedAppPackage),
)
)
assertEquals(expectedResult, actualResult)
@@ -178,8 +175,8 @@
LauncherAppWidgetInfo().apply {
providerName = unexpectedComponentName
screenId = 0
- }
- )
+ },
+ ),
)
// Then
@@ -190,7 +187,7 @@
installedHotseatItems = mutableSetOf(),
installedWorkspaceItems = mutableSetOf(),
firstScreenInstalledWidgets = mutableSetOf(expectedAppPackage),
- secondaryScreenInstalledWidgets = mutableSetOf(expectedAppPackage2)
+ secondaryScreenInstalledWidgets = mutableSetOf(expectedAppPackage2),
)
)
assertEquals(expectedResult, actualResult)
@@ -224,7 +221,7 @@
packageManagerHelper = mockPmHelper,
firstScreenItems = firstScreenItems,
userKeyToSessionMap = sessionInfoMap,
- allWidgets = listOf()
+ allWidgets = listOf(),
)
// Then
@@ -232,7 +229,7 @@
listOf(
FirstScreenBroadcastModel(
installerPackage = expectedInstallerPackage,
- pendingCollectionItems = mutableSetOf(expectedAppPackage)
+ pendingCollectionItems = mutableSetOf(expectedAppPackage),
)
)
assertEquals(expectedResult, actualResult)
@@ -259,7 +256,7 @@
firstScreenInstalledWidgets =
mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } },
secondaryScreenInstalledWidgets =
- mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } }
+ mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } },
)
// When
@@ -334,7 +331,7 @@
installedWorkspaceItems = mutableSetOf("installedWorkspaceItems"),
installedHotseatItems = mutableSetOf("installedHotseatItems"),
firstScreenInstalledWidgets = mutableSetOf("firstScreenInstalledWidgetItems"),
- secondaryScreenInstalledWidgets = mutableSetOf("secondaryInstalledWidgetItems")
+ secondaryScreenInstalledWidgets = mutableSetOf("secondaryInstalledWidgetItems"),
)
)
val expectedPendingIntent =
@@ -342,7 +339,7 @@
context,
0 /* requestCode */,
Intent(),
- PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
+ PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE,
)
// When
@@ -354,40 +351,40 @@
assertEquals(
"com.android.launcher3.action.FIRST_SCREEN_ACTIVE_INSTALLS",
- argumentCaptor.value.action
+ argumentCaptor.value.action,
)
assertEquals(expectedInstallerPackage, argumentCaptor.value.`package`)
assertEquals(
expectedPendingIntent,
- argumentCaptor.value.getParcelableExtra("verificationToken")
+ argumentCaptor.value.getParcelableExtra("verificationToken"),
)
assertEquals(
arrayListOf("pendingCollectionItem"),
- argumentCaptor.value.getStringArrayListExtra("folderItem")
+ argumentCaptor.value.getStringArrayListExtra("folderItem"),
)
assertEquals(
arrayListOf("pendingWorkspaceItem"),
- argumentCaptor.value.getStringArrayListExtra("workspaceItem")
+ argumentCaptor.value.getStringArrayListExtra("workspaceItem"),
)
assertEquals(
arrayListOf("pendingHotseatItems"),
- argumentCaptor.value.getStringArrayListExtra("hotseatItem")
+ argumentCaptor.value.getStringArrayListExtra("hotseatItem"),
)
assertEquals(
arrayListOf("pendingWidgetItems"),
- argumentCaptor.value.getStringArrayListExtra("widgetItem")
+ argumentCaptor.value.getStringArrayListExtra("widgetItem"),
)
assertEquals(
arrayListOf("installedWorkspaceItems"),
- argumentCaptor.value.getStringArrayListExtra("workspaceInstalledItems")
+ argumentCaptor.value.getStringArrayListExtra("workspaceInstalledItems"),
)
assertEquals(
arrayListOf("installedHotseatItems"),
- argumentCaptor.value.getStringArrayListExtra("hotseatInstalledItems")
+ argumentCaptor.value.getStringArrayListExtra("hotseatInstalledItems"),
)
assertEquals(
arrayListOf("firstScreenInstalledWidgetItems", "secondaryInstalledWidgetItems"),
- argumentCaptor.value.getStringArrayListExtra("widgetInstalledItems")
+ argumentCaptor.value.getStringArrayListExtra("widgetInstalledItems"),
)
}
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt
index 4ca47e3..e8f778f 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt
@@ -30,7 +30,6 @@
import com.google.common.truth.Truth.assertWithMessage
import org.junit.After
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -39,8 +38,6 @@
@RunWith(AndroidJUnit4::class)
class FolderIconLoadTest {
- @get:Rule(order = 0) val modelTestRule = ModelTestRule()
-
private lateinit var modelHelper: LauncherModelHelper
private val uniqueActivities =
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/multivalentTests/src/com/android/launcher3/model/LoaderCursorTest.java
index ac911b3..b4945d7 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/LoaderCursorTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -67,7 +67,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -78,8 +77,6 @@
@RunWith(AndroidJUnit4.class)
public class LoaderCursorTest {
- @Rule public ModelTestRule rule = new ModelTestRule();
-
private LauncherModelHelper mModelHelper;
private LauncherAppState mApp;
private PackageManagerHelper mPmHelper;
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/ModelTestRule.kt b/tests/multivalentTests/src/com/android/launcher3/model/ModelTestRule.kt
deleted file mode 100644
index ad2c2a4..0000000
--- a/tests/multivalentTests/src/com/android/launcher3/model/ModelTestRule.kt
+++ /dev/null
@@ -1,27 +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.launcher3.model
-
-import com.android.launcher3.util.RoboApiWrapper
-import org.junit.rules.TestWatcher
-import org.junit.runner.Description
-
-class ModelTestRule : TestWatcher() {
- override fun starting(description: Description?) {
- RoboApiWrapper.initialize()
- }
-}
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java b/tests/multivalentTests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
index a0d9da9..0f1fc00 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
@@ -37,7 +37,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -48,8 +47,6 @@
@RunWith(AndroidJUnit4.class)
public class PackageInstallStateChangedTaskTest {
- @Rule public ModelTestRule mModelTestRule = new ModelTestRule();
-
private static final String PENDING_APP_1 = TEST_PACKAGE + ".pending1";
private static final String PENDING_APP_2 = TEST_PACKAGE + ".pending2";
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
index c7abce6..ed8b397 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
@@ -58,7 +58,6 @@
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
@@ -77,8 +76,6 @@
@RunWith(AndroidJUnit4::class)
class WorkspaceItemProcessorTest {
- @get:Rule val modelTestRule = ModelTestRule()
-
@Mock private lateinit var mockIconRequestInfo: IconRequestInfo<WorkspaceItemInfo>
@Mock private lateinit var mockWorkspaceInfo: WorkspaceItemInfo
@Mock private lateinit var mockBgDataModel: BgDataModel
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemSpaceFinderTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemSpaceFinderTest.kt
index ae8e966..dd03eee 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemSpaceFinderTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemSpaceFinderTest.kt
@@ -21,7 +21,6 @@
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -30,8 +29,6 @@
@RunWith(AndroidJUnit4::class)
class WorkspaceItemSpaceFinderTest : AbstractWorkspaceModelTest() {
- @get:Rule val modelTestRule = ModelTestRule()
-
private val mItemSpaceFinder = WorkspaceItemSpaceFinder()
@Before
@@ -52,7 +49,7 @@
mExistingScreens,
mNewScreens,
spanX,
- spanY
+ spanY,
)
.let { NewItemSpace.fromIntArray(it) }
@@ -62,7 +59,7 @@
newItemSpace.cellX,
newItemSpace.cellY,
spanX,
- spanY
+ spanY,
)
)
.isTrue()
@@ -171,7 +168,7 @@
screen0 = listOf(Rect(2, 0, 5, 2)),
screen1 = fullScreenSpaces, // full screens are skipped
screen2 = fullScreenSpaces, // full screens are skipped
- screen3 = emptyScreenSpaces
+ screen3 = emptyScreenSpaces,
)
val spaceFound = findSpace(3, 1)
diff --git a/tests/multivalentTests/src/com/android/launcher3/pm/InstallSessionTrackerTest.kt b/tests/multivalentTests/src/com/android/launcher3/pm/InstallSessionTrackerTest.kt
index d860710..15a9964 100644
--- a/tests/multivalentTests/src/com/android/launcher3/pm/InstallSessionTrackerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/pm/InstallSessionTrackerTest.kt
@@ -26,7 +26,6 @@
import androidx.test.filters.SdkSuppress
import androidx.test.filters.SmallTest
import com.android.launcher3.Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING
-import com.android.launcher3.model.ModelTestRule
import com.android.launcher3.util.Executors.MODEL_EXECUTOR
import com.android.launcher3.util.LauncherModelHelper
import com.android.launcher3.util.PackageUserKey
@@ -45,9 +44,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class InstallSessionTrackerTest {
- @get:Rule(order = 0) val setFlagsRule = SetFlagsRule()
-
- @get:Rule(order = 1) val modelTestRule = ModelTestRule()
+ @get:Rule val setFlagsRule = SetFlagsRule()
private val mockInstallSessionHelper: InstallSessionHelper = mock()
private val mockCallback: InstallSessionTracker.Callback = mock()
@@ -67,7 +64,7 @@
mockInstallSessionHelper,
mockCallback,
mockPackageInstaller,
- launcherApps
+ launcherApps,
)
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/pm/UserCacheTest.kt b/tests/multivalentTests/src/com/android/launcher3/pm/UserCacheTest.kt
index 482dced..5f08c31 100644
--- a/tests/multivalentTests/src/com/android/launcher3/pm/UserCacheTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/pm/UserCacheTest.kt
@@ -20,7 +20,6 @@
import android.os.UserHandle
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
-import com.android.launcher3.model.ModelTestRule
import com.android.launcher3.util.Executors.MODEL_EXECUTOR
import com.android.launcher3.util.LauncherModelHelper
import com.android.launcher3.util.TestUtil
@@ -28,15 +27,12 @@
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class UserCacheTest {
- @get:Rule val modelTestRule = ModelTestRule()
-
private val launcherModelHelper = LauncherModelHelper()
private val sandboxContext = launcherModelHelper.sandboxContext
private lateinit var userCache: UserCache
diff --git a/tests/multivalentTests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/tests/multivalentTests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
index b3675a6..d0c168a 100644
--- a/tests/multivalentTests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
@@ -101,7 +101,7 @@
mMockController = Mockito.mock(ModelDbController.class);
mMockDb = mock(SQLiteDatabase.class);
mMockCursor = mock(Cursor.class);
- mPrefs = new LauncherPrefs(mContext);
+ mPrefs = LauncherPrefs.get(mContext);
mMockRestoreEventLogger = mock(LauncherRestoreEventLogger.class);
}
diff --git a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
index 0dd13a9..b17cd4d 100644
--- a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
+++ b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
@@ -33,17 +33,15 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.ArgumentMatchers.anyList
-import org.mockito.ArgumentMatchers.anyMap
import org.mockito.Mock
import org.mockito.Mockito
+import org.mockito.Mockito.mock
import org.mockito.Mockito.times
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
import org.mockito.MockitoSession
import org.mockito.Spy
+import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.spy
@@ -67,7 +65,7 @@
installedHotseatItems = mutableSetOf("installedHotseatItem"),
installedWorkspaceItems = mutableSetOf("installedWorkspaceItem"),
firstScreenInstalledWidgets = mutableSetOf("installedFirstScreenWidget"),
- secondaryScreenInstalledWidgets = mutableSetOf("installedSecondaryScreenWidget")
+ secondaryScreenInstalledWidgets = mutableSetOf("installedSecondaryScreenWidget"),
)
private lateinit var mockitoSession: MockitoSession
@@ -75,7 +73,7 @@
@Mock private lateinit var bgAllAppsList: AllAppsList
@Mock private lateinit var modelDelegate: ModelDelegate
@Mock private lateinit var launcherBinder: BaseLauncherBinder
- @Mock private lateinit var launcherModel: LauncherModel
+ private lateinit var launcherModel: LauncherModel
@Mock private lateinit var transaction: LoaderTransaction
@Mock private lateinit var iconCache: IconCache
@Mock private lateinit var idleLock: LooperIdleLock
@@ -89,6 +87,7 @@
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
+ launcherModel = mock(LauncherModel::class.java)
mockitoSession =
ExtendedMockito.mockitoSession()
.strictness(Strictness.LENIENT)
@@ -105,15 +104,16 @@
doReturn(TestViewHelpers.findWidgetProvider(false))
.`when`(context.spyService(AppWidgetManager::class.java))
- .getAppWidgetInfo(anyInt())
+ .getAppWidgetInfo(any())
`when`(app.context).thenReturn(context)
`when`(app.model).thenReturn(launcherModel)
- `when`(launcherModel.beginLoader(any(LoaderTask::class.java))).thenReturn(transaction)
+
+ `when`(launcherModel.beginLoader(any())).thenReturn(transaction)
`when`(app.iconCache).thenReturn(iconCache)
`when`(launcherModel.modelDbController)
.thenReturn(FactitiousDbController(context, INSERTION_STATEMENT_FILE))
`when`(app.invariantDeviceProfile).thenReturn(idp)
- `when`(launcherBinder.newIdleLock(any(LoaderTask::class.java))).thenReturn(idleLock)
+ `when`(launcherBinder.newIdleLock(any())).thenReturn(idleLock)
`when`(idleLock.awaitLocked(1000)).thenReturn(false)
`when`(iconCache.updateHandler).thenReturn(iconCacheUpdateHandler)
context.putObject(UserCache.INSTANCE, userCache)
@@ -149,12 +149,12 @@
verify(launcherBinder).bindWorkspace(true, false)
verify(modelDelegate).workspaceLoadComplete()
- verify(modelDelegate).loadAndBindAllAppsItems(any(), any(), any())
+ verify(modelDelegate).loadAndBindAllAppsItems(any(), anyOrNull(), any())
verify(launcherBinder).bindAllApps()
verify(iconCacheUpdateHandler, times(4)).updateIcons(any(), any<CachingLogic<Any>>(), any())
verify(launcherBinder).bindDeepShortcuts()
verify(launcherBinder).bindWidgets()
- verify(modelDelegate).loadAndBindOtherItems(any())
+ verify(modelDelegate).loadAndBindOtherItems(anyOrNull())
verify(iconCacheUpdateHandler).finish()
verify(modelDelegate).modelLoadComplete()
verify(transaction).commit()
@@ -209,10 +209,10 @@
`when`(app.context).thenReturn(spyContext)
whenever(
FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
- anyOrNull(),
- anyList(),
- anyMap(),
- anyList()
+ any(),
+ any(),
+ any(),
+ any(),
)
)
.thenReturn(listOf(expectedBroadcastModel))
@@ -220,7 +220,7 @@
whenever(
FirstScreenBroadcastHelper.sendBroadcastsForModels(
spyContext,
- listOf(expectedBroadcastModel)
+ listOf(expectedBroadcastModel),
)
)
.thenCallRealMethod()
@@ -239,34 +239,34 @@
assertEquals(expectedBroadcastModel.installerPackage, actualBroadcastIntent.`package`)
assertEquals(
ArrayList(expectedBroadcastModel.installedWorkspaceItems),
- actualBroadcastIntent.getStringArrayListExtra("workspaceInstalledItems")
+ actualBroadcastIntent.getStringArrayListExtra("workspaceInstalledItems"),
)
assertEquals(
ArrayList(expectedBroadcastModel.installedHotseatItems),
- actualBroadcastIntent.getStringArrayListExtra("hotseatInstalledItems")
+ actualBroadcastIntent.getStringArrayListExtra("hotseatInstalledItems"),
)
assertEquals(
ArrayList(
expectedBroadcastModel.firstScreenInstalledWidgets +
expectedBroadcastModel.secondaryScreenInstalledWidgets
),
- actualBroadcastIntent.getStringArrayListExtra("widgetInstalledItems")
+ actualBroadcastIntent.getStringArrayListExtra("widgetInstalledItems"),
)
assertEquals(
ArrayList(expectedBroadcastModel.pendingCollectionItems),
- actualBroadcastIntent.getStringArrayListExtra("folderItem")
+ actualBroadcastIntent.getStringArrayListExtra("folderItem"),
)
assertEquals(
ArrayList(expectedBroadcastModel.pendingWorkspaceItems),
- actualBroadcastIntent.getStringArrayListExtra("workspaceItem")
+ actualBroadcastIntent.getStringArrayListExtra("workspaceItem"),
)
assertEquals(
ArrayList(expectedBroadcastModel.pendingHotseatItems),
- actualBroadcastIntent.getStringArrayListExtra("hotseatItem")
+ actualBroadcastIntent.getStringArrayListExtra("hotseatItem"),
)
assertEquals(
ArrayList(expectedBroadcastModel.pendingWidgetItems),
- actualBroadcastIntent.getStringArrayListExtra("widgetItem")
+ actualBroadcastIntent.getStringArrayListExtra("widgetItem"),
)
}
@@ -277,10 +277,10 @@
`when`(app.context).thenReturn(spyContext)
whenever(
FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
- anyOrNull(),
- anyList(),
- anyMap(),
- anyList()
+ any(),
+ any(),
+ any(),
+ any(),
)
)
.thenReturn(listOf(expectedBroadcastModel))
@@ -288,7 +288,7 @@
whenever(
FirstScreenBroadcastHelper.sendBroadcastsForModels(
spyContext,
- listOf(expectedBroadcastModel)
+ listOf(expectedBroadcastModel),
)
)
.thenCallRealMethod()
@@ -300,7 +300,7 @@
.runSyncOnBackgroundThread()
// Then
- verify(spyContext, times(0)).sendBroadcast(any(Intent::class.java))
+ verify(spyContext, times(0)).sendBroadcast(any())
}
@Test
@@ -310,10 +310,10 @@
`when`(app.context).thenReturn(spyContext)
whenever(
FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
- anyOrNull(),
- anyList(),
- anyMap(),
- anyList()
+ any(),
+ any(),
+ any(),
+ any(),
)
)
.thenReturn(listOf(expectedBroadcastModel))
@@ -321,7 +321,7 @@
whenever(
FirstScreenBroadcastHelper.sendBroadcastsForModels(
spyContext,
- listOf(expectedBroadcastModel)
+ listOf(expectedBroadcastModel),
)
)
.thenCallRealMethod()
@@ -334,7 +334,7 @@
.runSyncOnBackgroundThread()
// Then
- verify(spyContext, times(0)).sendBroadcast(any(Intent::class.java))
+ verify(spyContext, times(0)).sendBroadcast(any())
}
}
diff --git a/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt b/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt
index 05f626d..d9af07a 100644
--- a/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt
+++ b/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt
@@ -58,8 +58,7 @@
@RunWith(AndroidJUnit4::class)
class PackageUpdatedTaskTest {
- @get:Rule(order = 0) val setFlagsRule = SetFlagsRule()
- @get:Rule(order = 1) val modelTestRule = ModelTestRule()
+ @get:Rule val setFlagsRule = SetFlagsRule()
private val mUser = UserHandle(0)
private val mDataModel: BgDataModel = BgDataModel()
diff --git a/tests/src/com/android/launcher3/ui/workspace/TaplThemeIconsTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplThemeIconsTest.java
index a148744..d653317 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TaplThemeIconsTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TaplThemeIconsTest.java
@@ -16,8 +16,6 @@
package com.android.launcher3.ui.workspace;
import static com.android.launcher3.util.TestConstants.AppNames.TEST_APP_NAME;
-import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
-import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -40,8 +38,6 @@
import com.android.launcher3.tapl.HomeAppIconMenuItem;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.util.Executors;
-import com.android.launcher3.util.rule.ScreenRecordRule;
-import com.android.launcher3.util.rule.TestStabilityRule;
import org.junit.Test;
@@ -115,8 +111,6 @@
}
@Test
- @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/350557998
- @ScreenRecordRule.ScreenRecord // b/350557998
public void testShortcutIconWithTheme() throws Exception {
setThemeEnabled(true);
initialize(this);
diff --git a/tests/src/com/android/launcher3/util/RoboApiWrapper.kt b/tests/src/com/android/launcher3/util/RoboApiWrapper.kt
index 583652d..7f74e56 100644
--- a/tests/src/com/android/launcher3/util/RoboApiWrapper.kt
+++ b/tests/src/com/android/launcher3/util/RoboApiWrapper.kt
@@ -24,12 +24,10 @@
object RoboApiWrapper {
- fun initialize() {}
-
fun registerInputStream(
contentResolver: ContentResolver,
uri: Uri,
- inputStreamSupplier: Supplier<InputStream>
+ inputStreamSupplier: Supplier<InputStream>,
) {}
fun waitForLooperSync(looper: Looper) {}
diff --git a/tests/src_deviceless/com/android/launcher3/util/RoboApiWrapper.kt b/tests/src_deviceless/com/android/launcher3/util/RoboApiWrapper.kt
index 9232268..a2b8303 100644
--- a/tests/src_deviceless/com/android/launcher3/util/RoboApiWrapper.kt
+++ b/tests/src_deviceless/com/android/launcher3/util/RoboApiWrapper.kt
@@ -16,70 +16,19 @@
package com.android.launcher3.util
-import android.content.ComponentName
import android.content.ContentResolver
-import android.content.Intent
-import android.content.IntentFilter
-import android.content.pm.ApplicationInfo
-import android.content.pm.LauncherActivityInfo
-import android.content.pm.LauncherApps
import android.net.Uri
import android.os.Looper
-import android.os.Process
-import androidx.test.platform.app.InstrumentationRegistry
import java.io.InputStream
import java.util.function.Supplier
-import org.mockito.Mockito
-import org.mockito.kotlin.whenever
-import org.robolectric.RuntimeEnvironment
import org.robolectric.Shadows
object RoboApiWrapper {
- fun initialize() {
- Shadows.shadowOf(
- RuntimeEnvironment.getApplication().getSystemService(LauncherApps::class.java)
- )
- .addEnabledPackage(
- Process.myUserHandle(),
- InstrumentationRegistry.getInstrumentation().context.packageName
- )
- LauncherModelHelper.ACTIVITY_LIST.forEach {
- installApp(ComponentName(InstrumentationRegistry.getInstrumentation().context, it))
- }
- }
-
- private fun installApp(componentName: ComponentName) {
- val app = RuntimeEnvironment.getApplication()
- val user = Process.myUserHandle()
-
- val pm = Shadows.shadowOf(app.packageManager)
- val ai = pm.addActivityIfNotPresent(componentName)
- pm.addIntentFilterForActivity(
- componentName,
- IntentFilter(Intent.ACTION_MAIN).apply { addCategory(Intent.CATEGORY_LAUNCHER) }
- )
-
- val li = Mockito.mock(LauncherActivityInfo::class.java)
- val appInfo = ApplicationInfo().apply { flags = 0 }
- Mockito.doReturn(ai).whenever(li).activityInfo
- Mockito.doReturn(appInfo).whenever(li).applicationInfo
- Mockito.doReturn(user).whenever(li).user
- Mockito.doReturn(1f).whenever(li).loadingProgress
- Mockito.doReturn(componentName).whenever(li).componentName
-
- Shadows.shadowOf(app.getSystemService(LauncherApps::class.java)).apply {
- addActivity(user, li)
- addEnabledPackage(user, componentName.packageName)
- setActivityEnabled(user, componentName)
- addApplicationInfo(user, componentName.packageName, ai.applicationInfo)
- }
- }
-
fun registerInputStream(
contentResolver: ContentResolver,
uri: Uri,
- inputStreamSupplier: Supplier<InputStream>
+ inputStreamSupplier: Supplier<InputStream>,
) {
Shadows.shadowOf(contentResolver).registerInputStreamSupplier(uri, inputStreamSupplier)
}