Merge "Add flag for expressive dismiss task motion in Overview." into main
diff --git a/quickstep/res/drawable/task_header_close_button.xml b/quickstep/res/drawable/task_header_close_button.xml
new file mode 100644
index 0000000..b409158
--- /dev/null
+++ b/quickstep/res/drawable/task_header_close_button.xml
@@ -0,0 +1,24 @@
+<!-- Copyright (C) 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="18dp"
+ android:height="18dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M256,760L200,704L424,480L200,256L256,200L480,424L704,200L760,256L536,480L760,704L704,760L480,536L256,760Z"/>
+</vector>
diff --git a/quickstep/res/drawable/task_thumbnail_header_bg.xml b/quickstep/res/drawable/task_thumbnail_header_bg.xml
new file mode 100644
index 0000000..52ac1ae
--- /dev/null
+++ b/quickstep/res/drawable/task_thumbnail_header_bg.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2025 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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/materialColorSurfaceBright" />
+ <corners android:topLeftRadius="@dimen/task_thumbnail_header_round_corner_radius"
+ android:topRightRadius="@dimen/task_thumbnail_header_round_corner_radius"/>
+</shape>
diff --git a/quickstep/res/layout/task_thumbnail_view_header.xml b/quickstep/res/layout/task_thumbnail_view_header.xml
new file mode 100644
index 0000000..ecc1559
--- /dev/null
+++ b/quickstep/res/layout/task_thumbnail_view_header.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<com.android.quickstep.views.TaskThumbnailViewHeader
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@drawable/task_thumbnail_header_bg">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/task_thumbnail_header_height"
+ android:layout_marginStart="@dimen/task_thumbnail_header_margin_edge"
+ android:layout_marginEnd="@dimen/task_thumbnail_header_margin_edge"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent">
+ <ImageView
+ android:id="@+id/header_app_icon"
+ android:contentDescription="@string/header_app_icon_description"
+ android:layout_width="@dimen/task_thumbnail_header_icon_size"
+ android:layout_height="@dimen/task_thumbnail_header_icon_size"
+ android:layout_marginEnd="@dimen/task_thumbnail_header_margin_between_views"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/header_app_title"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintHorizontal_bias="0"
+ app:layout_constraintVertical_bias="0.5"
+ app:layout_constraintHorizontal_chainStyle="spread_inside" />
+ <TextView
+ android:id="@+id/header_app_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/task_thumbnail_header_margin_between_views"
+ android:layout_marginEnd="@dimen/task_thumbnail_header_margin_between_views"
+ android:text="@string/header_default_app_title"
+ app:layout_constraintStart_toEndOf="@id/header_app_icon"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintHorizontal_bias="0"
+ app:layout_constraintVertical_bias="0.5" />
+ <ImageButton
+ android:id="@+id/header_close_button"
+ android:contentDescription="@string/header_close_icon_description"
+ android:layout_width="@dimen/task_thumbnail_header_icon_size"
+ android:layout_height="@dimen/task_thumbnail_header_icon_size"
+ android:layout_marginStart="@dimen/task_thumbnail_header_margin_between_views"
+ android:src="@drawable/task_header_close_button"
+ android:tint="@android:color/darker_gray"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintHorizontal_bias="1"
+ app:layout_constraintVertical_bias="0.5" />
+ </androidx.constraintlayout.widget.ConstraintLayout>
+</com.android.quickstep.views.TaskThumbnailViewHeader>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index de0b2c7..e57e650 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -84,6 +84,12 @@
<!-- The size of the icon menu's icon touch target -->
<dimen name="task_thumbnail_icon_menu_drawable_touch_size">44dp</dimen>
<dimen name="task_thumbnail_icon_menu_elevation">4dp</dimen>
+ <!-- The size of the task thumbnail header -->
+ <dimen name="task_thumbnail_header_height">30dp</dimen>
+ <dimen name="task_thumbnail_header_margin_edge">18dp</dimen>
+ <dimen name="task_thumbnail_header_margin_between_views">9dp</dimen>
+ <dimen name="task_thumbnail_header_icon_size">18dp</dimen>
+ <dimen name="task_thumbnail_header_round_corner_radius">16dp</dimen>
<dimen name="task_icon_cache_default_icon_size">72dp</dimen>
<item name="overview_modal_max_scale" format="float" type="dimen">1.1</item>
@@ -465,6 +471,7 @@
<dimen name="bubblebar_icon_spacing_persistent_taskbar">@dimen/bubblebar_icon_spacing</dimen>
<dimen name="bubblebar_expanded_icon_spacing">12dp</dimen>
<dimen name="bubblebar_icon_elevation">1dp</dimen>
+ <dimen name="bubblebar_transient_taskbar_min_distance">12dp</dimen>
<!-- Bubble bar dismiss view -->
<dimen name="bubblebar_dismiss_target_size">@dimen/floating_dismiss_background_size</dimen>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index f126568..324ea31 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -357,4 +357,12 @@
<!-- Name of Google's new feature to circle to search anything on your phone screen, without
switching apps. [CHAR_LIMIT=60] -->
<string name="search_gesture_feature_title">Circle to Search</string>
+
+ <!-- Strings for task thumbnail header in Overview -->
+ <!-- Content description for the header app icon. [CHAR LIMIT=NONE] -->
+ <string name="header_app_icon_description">App icon</string>
+ <!-- Default app title for a task view in Overview. [CHAR LIMIT=NONE] -->
+ <string name="header_default_app_title">App title</string>
+ <!-- Content description for the header close button. [CHAR LIMIT=NONE] -->
+ <string name="header_close_icon_description">Close button</string>
</resources>
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index ee9c6a1..2745129 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -140,6 +140,7 @@
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.NavigationMode;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.SettingsCache;
@@ -194,6 +195,7 @@
private WindowManager.LayoutParams mWindowLayoutParams;
private WindowManager.LayoutParams mLastUpdatedLayoutParams;
private boolean mIsFullscreen;
+ private boolean mIsNotificationShadeExpanded = false;
// The size we should return to when we call setTaskbarWindowFullscreen(false)
private int mLastRequestedNonFullscreenSize;
/**
@@ -269,8 +271,7 @@
mWindowManager = c.getSystemService(WindowManager.class);
// Inflate views.
- final boolean isTransientTaskbar = DisplayController.isTransientTaskbar(this)
- && !isPhoneMode();
+ boolean isTransientTaskbar = isTransientTaskbar();
int taskbarLayout = isTransientTaskbar ? R.layout.transient_taskbar : R.layout.taskbar;
mDragLayer = (TaskbarDragLayer) mLayoutInflater.inflate(taskbarLayout, null, false);
TaskbarView taskbarView = mDragLayer.findViewById(R.id.taskbar_view);
@@ -383,6 +384,11 @@
dispatchDeviceProfileChanged();
}
+ /** Returns whether current taskbar is transient. */
+ public boolean isTransientTaskbar() {
+ return DisplayController.isTransientTaskbar(this) && !isPhoneMode();
+ }
+
/**
* Copy the original DeviceProfile, match the number of hotseat icons and qsb width and update
* the icon size
@@ -1005,6 +1011,8 @@
* Hides the taskbar icons and background when the notification shade is expanded.
*/
private void onNotificationShadeExpandChanged(boolean isExpanded, boolean skipAnim) {
+ boolean isExpandedUpdated = isExpanded != mIsNotificationShadeExpanded;
+ mIsNotificationShadeExpanded = isExpanded;
// Close all floating views within the Taskbar window to make sure nothing is shown over
// the notification shade.
if (isExpanded) {
@@ -1018,11 +1026,18 @@
anim.play(mControllers.taskbarDragLayerController.getNotificationShadeBgTaskbar()
.animateToValue(alpha));
- mControllers.bubbleControllers.ifPresent(controllers -> {
- BubbleBarViewController bubbleBarViewController = controllers.bubbleBarViewController;
- anim.play(bubbleBarViewController.getBubbleBarAlpha().get(0).animateToValue(alpha));
- });
-
+ if (isExpandedUpdated) {
+ mControllers.bubbleControllers.ifPresent(controllers -> {
+ BubbleBarViewController bubbleBarViewController =
+ controllers.bubbleBarViewController;
+ anim.play(bubbleBarViewController.getBubbleBarAlpha().get(0).animateToValue(alpha));
+ MultiPropertyFactory<View>.MultiProperty handleAlpha =
+ controllers.bubbleStashController.getHandleViewAlpha();
+ if (handleAlpha != null) {
+ anim.play(handleAlpha.animateToValue(alpha));
+ }
+ });
+ }
anim.start();
if (skipAnim) {
anim.end();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
index ea6d82b..e44bce1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
@@ -57,6 +57,7 @@
var backgroundHeight = context.deviceProfile.taskbarHeight.toFloat()
var translationYForSwipe = 0f
var translationYForStash = 0f
+ var translationXForBubbleBar = 0f
private val transientBackgroundBounds = context.transientTaskbarBounds
@@ -244,12 +245,12 @@
setColorAlphaBound(Color.BLACK, Math.round(newShadowAlpha)),
)
strokePaint.alpha = (paint.alpha * strokeAlpha) / 255
-
+ val currentTranslationX = translationXForBubbleBar * progress
lastDrawnTransientRect.set(
- transientBackgroundBounds.left + halfWidthDelta,
+ transientBackgroundBounds.left + halfWidthDelta + currentTranslationX,
bottom - newBackgroundHeight,
- transientBackgroundBounds.right - halfWidthDelta,
- bottom
+ transientBackgroundBounds.right - halfWidthDelta + currentTranslationX,
+ bottom,
)
val horizontalInset = fullWidth * widthInsetPercentage
lastDrawnTransientRect.inset(horizontalInset, 0f)
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
index 8b52112..59ef577 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
@@ -254,6 +254,11 @@
invalidate();
}
+ protected void setBackgroundTranslationXForBubbleBar(float translationX) {
+ mBackgroundRenderer.setTranslationXForBubbleBar(translationX);
+ invalidate();
+ }
+
/** Returns the bounds in DragLayer coordinates of where the transient background was drawn. */
protected RectF getLastDrawnTransientRect() {
return mBackgroundRenderer.getLastDrawnTransientRect();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
index 925e10b..68c252a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
@@ -198,6 +198,13 @@
}
/**
+ * Sets the translation of the background for the bubble bar.
+ */
+ public void setTranslationXForBubbleBar(float transX) {
+ mTaskbarDragLayer.setBackgroundTranslationXForBubbleBar(transX);
+ }
+
+ /**
* Sets the translation of the background during the spring on stash animation.
*/
public void setTranslationYForStash(float transY) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 60de066..e0be39d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -40,6 +40,7 @@
import static com.android.launcher3.taskbar.bubbles.BubbleBarView.FADE_IN_ANIM_ALPHA_DURATION_MS;
import static com.android.launcher3.taskbar.bubbles.BubbleBarView.FADE_OUT_ANIM_POSITION_DURATION_MS;
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
+import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_BUBBLE_BAR_ANIM;
import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_NAV_BAR_ANIM;
import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_ALIGNMENT_ANIM;
import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_PINNING_ANIM;
@@ -81,6 +82,7 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.TaskItemInfo;
import com.android.launcher3.taskbar.bubbles.BubbleBarController;
+import com.android.launcher3.taskbar.bubbles.BubbleControllers;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.LauncherBindableItemsContainer;
@@ -109,6 +111,8 @@
private static final Runnable NO_OP = () -> { };
+ public static long TRANSLATION_X_FOR_BUBBLEBAR_ANIM_DURATION_MS = 250;
+
public static final int ALPHA_INDEX_HOME = 0;
public static final int ALPHA_INDEX_KEYGUARD = 1;
public static final int ALPHA_INDEX_STASH = 2;
@@ -132,6 +136,7 @@
private static final int TRANSITION_FADE_OUT_DURATION = 83;
private final TaskbarActivityContext mActivity;
+ private @Nullable TaskbarDragLayerController mDragLayerController;
private final TaskbarView mTaskbarView;
private final MultiValueAlpha mTaskbarIconAlpha;
private final AnimatedFloat mTaskbarIconScaleForStash = new AnimatedFloat(this::updateScale);
@@ -144,15 +149,22 @@
this::updateTaskbarIconsScale);
private final AnimatedFloat mTaskbarIconTranslationXForPinning = new AnimatedFloat(
- this::updateTaskbarIconTranslationXForPinning);
+ () -> updateTaskbarIconTranslationXForPinning());
private final AnimatedFloat mIconsTranslationXForNavbar = new AnimatedFloat(
this::updateTranslationXForNavBar);
+ private final AnimatedFloat mTranslationXForBubbleBar = new AnimatedFloat(
+ this::updateTranslationXForBubbleBar);
+
@Nullable
private Animator mTaskbarShiftXAnim;
@Nullable
private BubbleBarLocation mCurrentBubbleBarLocation;
+ @Nullable
+ private BubbleControllers mBubbleControllers = null;
+ @Nullable
+ private ObjectAnimator mTranslationXAnimation;
private final AnimatedFloat mTaskbarIconTranslationYForPinning = new AnimatedFloat(
this::updateTranslationY);
@@ -174,11 +186,12 @@
private final View.OnLayoutChangeListener mTaskbarViewLayoutChangeListener =
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
if (!taskbarRecentsLayoutTransition()) {
- updateTaskbarIconTranslationXForPinning();
+ // update shiftX is handled with the animation at the end of the method
+ updateTaskbarIconTranslationXForPinning(/* updateShiftXForBubbleBar = */ false);
}
- if (BubbleBarController.isBubbleBarEnabled()) {
- mControllers.navbarButtonsViewController.onLayoutsUpdated();
- }
+ if (mBubbleControllers == null) return;
+ mControllers.navbarButtonsViewController.onLayoutsUpdated();
+ adjustTaskbarXForBubbleBar();
};
// Animation to align icons with Launcher, created lazily. This allows the controller to be
@@ -222,11 +235,11 @@
mIsRtl = Utilities.isRtl(mTaskbarView.getResources());
mTaskbarLeftRightMargin = mActivity.getResources().getDimensionPixelSize(
R.dimen.transient_taskbar_padding);
-
}
public void init(TaskbarControllers controllers) {
mControllers = controllers;
+ controllers.bubbleControllers.ifPresent(bc -> mBubbleControllers = bc);
mTaskbarView.init(TaskbarViewCallbacksFactory.newInstance(mActivity).create(
mActivity, mControllers, mTaskbarView));
mTaskbarView.getLayoutParams().height = mActivity.isPhoneMode()
@@ -252,7 +265,7 @@
controllers.navbarButtonsViewController.getTaskbarNavButtonTranslationY();
mTaskbarNavButtonTranslationYForInAppDisplay = controllers.navbarButtonsViewController
.getTaskbarNavButtonTranslationYForInAppDisplay();
-
+ mDragLayerController = controllers.taskbarDragLayerController;
mActivity.addOnDeviceProfileChangeListener(mDeviceProfileChangeListener);
if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
@@ -270,24 +283,59 @@
@Override
public void onBubbleBarLocationUpdated(BubbleBarLocation location) {
updateCurrentBubbleBarLocation(location);
- if (!shouldMoveTaskbarOnBubbleBarLocationUpdate()) return;
- cancelTaskbarShiftAnimation();
- // reset translation x, taskbar will position icons with the updated location
- mIconsTranslationXForNavbar.updateValue(0);
- mTaskbarView.onBubbleBarLocationUpdated(location);
+ if (mActivity.isTransientTaskbar()) {
+ translateTaskbarXForBubbleBar(/* animate= */ false);
+ } else if (mActivity.shouldStartAlignTaskbar()) {
+ cancelTaskbarShiftAnimation();
+ // reset translation x, taskbar will position icons with the updated location
+ mIconsTranslationXForNavbar.updateValue(0);
+ mTaskbarView.onBubbleBarLocationUpdated(location);
+ }
}
/** Animates start aligned taskbar accordingly to the bubble bar position. */
@Override
public void onBubbleBarLocationAnimated(BubbleBarLocation location) {
- if (!updateCurrentBubbleBarLocation(location)
- || !shouldMoveTaskbarOnBubbleBarLocationUpdate()) {
- return;
+ boolean locationUpdated = updateCurrentBubbleBarLocation(location);
+ if (mActivity.isTransientTaskbar()) {
+ translateTaskbarXForBubbleBar(/* animate= */ true);
+ } else if (locationUpdated && mActivity.shouldStartAlignTaskbar()) {
+ cancelTaskbarShiftAnimation();
+ float translationX = mTaskbarView.getTranslationXForBubbleBarPosition(location);
+ mTaskbarShiftXAnim = createTaskbarIconsShiftAnimator(translationX);
+ mTaskbarShiftXAnim.start();
}
- cancelTaskbarShiftAnimation();
- float translationX = mTaskbarView.getTranslationXForBubbleBarPosition(location);
- mTaskbarShiftXAnim = createTaskbarIconsShiftAnimator(translationX);
- mTaskbarShiftXAnim.start();
+ }
+
+ private void translateTaskbarXForBubbleBar(boolean animate) {
+ cancelCurrentTranslationXAnimation();
+ if (!mActivity.isTransientTaskbar()) return;
+ int shiftX = getTransientTaskbarShiftXForBubbleBar();
+ if (animate) {
+ mTranslationXAnimation = mTranslationXForBubbleBar.animateToValue(shiftX);
+ mTranslationXAnimation.setInterpolator(EMPHASIZED);
+ mTranslationXAnimation.setDuration(TRANSLATION_X_FOR_BUBBLEBAR_ANIM_DURATION_MS);
+ mTranslationXAnimation.start();
+ } else {
+ mTranslationXForBubbleBar.updateValue(shiftX);
+ }
+ }
+
+ private void cancelCurrentTranslationXAnimation() {
+ if (mTranslationXAnimation != null) {
+ if (mTranslationXAnimation.isRunning()) {
+ mTranslationXAnimation.cancel();
+ }
+ mTranslationXAnimation = null;
+ }
+ }
+
+ private int getTransientTaskbarShiftXForBubbleBar() {
+ if (mBubbleControllers == null || !mActivity.isTransientTaskbar()) {
+ return 0;
+ }
+ return mBubbleControllers.bubbleBarViewController
+ .getTransientTaskbarTranslationXForBubbleBar(mCurrentBubbleBarLocation);
}
/** Updates the mCurrentBubbleBarLocation, returns {@code} true if location is updated. */
@@ -300,13 +348,6 @@
}
}
- /** Returns whether taskbar should be moved on the bubble bar location update. */
- private boolean shouldMoveTaskbarOnBubbleBarLocationUpdate() {
- return mControllers.bubbleControllers.isPresent()
- && mActivity.shouldStartAlignTaskbar()
- && mActivity.isThreeButtonNav();
- }
-
private void cancelTaskbarShiftAnimation() {
if (mTaskbarShiftXAnim != null) {
mTaskbarShiftXAnim.cancel();
@@ -450,16 +491,26 @@
}
void updateTaskbarIconTranslationXForPinning() {
+ updateTaskbarIconTranslationXForPinning(/* updateShiftXForBubbleBar = */ true);
+ }
+
+ void updateTaskbarIconTranslationXForPinning(boolean updateShiftXForBubbleBar) {
View[] iconViews = mTaskbarView.getIconViews();
float scale = mTaskbarIconTranslationXForPinning.value;
float transientTaskbarAllAppsOffset = mActivity.getResources().getDimension(
mTaskbarView.getAllAppsButtonContainer().getAllAppsButtonTranslationXOffset(true));
float persistentTaskbarAllAppsOffset = mActivity.getResources().getDimension(
mTaskbarView.getAllAppsButtonContainer().getAllAppsButtonTranslationXOffset(false));
-
+ if (mBubbleControllers != null && updateShiftXForBubbleBar) {
+ cancelCurrentTranslationXAnimation();
+ int translationXForTransientTaskbar = mBubbleControllers.bubbleBarViewController
+ .getTransientTaskbarTranslationXForBubbleBar(mCurrentBubbleBarLocation);
+ float currentTranslationXForTransientTaskbar = mapRange(scale,
+ translationXForTransientTaskbar, 0);
+ mTranslationXForBubbleBar.updateValue(currentTranslationXForTransientTaskbar);
+ }
float allAppIconTranslateRange = mapRange(scale, transientTaskbarAllAppsOffset,
persistentTaskbarAllAppsOffset);
-
// Task icons are laid out so the taskbar content is centered. The taskbar width (used for
// centering taskbar icons) depends on the all apps button X translation, and is different
// for persistent and transient taskbar. If the offset used for current taskbar layout is
@@ -551,13 +602,23 @@
}
private void updateTranslationXForNavBar() {
+ updateIconViewsTranslationX(INDEX_NAV_BAR_ANIM, mIconsTranslationXForNavbar.value);
+ }
+
+ private void updateTranslationXForBubbleBar() {
+ float translationX = mTranslationXForBubbleBar.value;
+ updateIconViewsTranslationX(INDEX_BUBBLE_BAR_ANIM, translationX);
+ if (mDragLayerController != null) {
+ mDragLayerController.setTranslationXForBubbleBar(translationX);
+ }
+ }
+
+ private void updateIconViewsTranslationX(int translationXChannel, float translationX) {
View[] iconViews = mTaskbarView.getIconViews();
- float translationX = mIconsTranslationXForNavbar.value;
- for (int iconIndex = 0; iconIndex < iconViews.length; iconIndex++) {
- View iconView = iconViews[iconIndex];
+ for (View iconView : iconViews) {
MultiTranslateDelegate translateDelegate =
((Reorderable) iconView).getTranslateDelegate();
- translateDelegate.getTranslationX(INDEX_NAV_BAR_ANIM).setValue(translationX);
+ translateDelegate.getTranslationX(translationXChannel).setValue(translationX);
}
}
@@ -811,6 +872,13 @@
if (mTaskbarView.updateMaxNumIcons()) {
commitRunningAppsToUI();
}
+ adjustTaskbarXForBubbleBar();
+ }
+
+ private void adjustTaskbarXForBubbleBar() {
+ if (mBubbleControllers != null && mActivity.isTransientTaskbar()) {
+ translateTaskbarXForBubbleBar(/* animate= */ true);
+ }
}
/**
@@ -841,7 +909,17 @@
setter.setFloat(mTaskbarIconTranslationYForHome, VALUE, -offsetY, interpolator);
setter.setFloat(mTaskbarNavButtonTranslationY, VALUE, -offsetY, interpolator);
setter.setFloat(mTaskbarNavButtonTranslationYForInAppDisplay, VALUE, offsetY, interpolator);
-
+ if (mBubbleControllers != null
+ && mCurrentBubbleBarLocation != null
+ && mActivity.isTransientTaskbar()) {
+ int offsetX = mBubbleControllers.bubbleBarViewController
+ .getTransientTaskbarTranslationXForBubbleBar(mCurrentBubbleBarLocation);
+ if (offsetX != 0) {
+ // if taskbar should be adjusted for the bubble bar adjust the taskbar translation
+ mTranslationXForBubbleBar.updateValue(offsetX);
+ setter.setFloat(mTranslationXForBubbleBar, VALUE, 0, interpolator);
+ }
+ }
int collapsedHeight = mActivity.getDefaultTaskbarWindowSize();
int expandedHeight = Math.max(collapsedHeight, taskbarDp.taskbarHeight + offsetY);
setter.addOnFrameListener(anim -> mActivity.setTaskbarWindowSize(
@@ -1042,8 +1120,8 @@
}
private boolean bubbleBarHasBubbles() {
- return mControllers.bubbleControllers.isPresent()
- && mControllers.bubbleControllers.get().bubbleBarViewController.hasBubbles();
+ return mBubbleControllers != null
+ && mBubbleControllers.bubbleBarViewController.hasBubbles();
}
public void onRotationChanged(DeviceProfile deviceProfile) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 2d4d279..c001123 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -1339,6 +1339,14 @@
return getScaledIconSize() + mIconOverlapAmount + 2 * mBubbleBarPadding;
}
+ float getCollapsedWidthForIconSizeAndPadding(int iconSize, int bubbleBarPadding) {
+ final int bubbleChildCount = Math.min(getBubbleChildCount(), MAX_VISIBLE_BUBBLES_COLLAPSED);
+ if (bubbleChildCount == 0) return 0;
+ final int spacesCount = bubbleChildCount - 1;
+ final float horizontalPadding = 2 * bubbleBarPadding;
+ return iconSize * bubbleChildCount + mIconOverlapAmount * spacesCount + horizontalPadding;
+ }
+
/** Returns the child count excluding the overflow if it's present. */
int getBubbleChildCount() {
return hasOverflow() ? getChildCount() - 1 : getChildCount();
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 0b627d2..afbc932 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -122,7 +122,8 @@
private float mBubbleBarSwipeUpTranslationY;
// Modified when bubble bar is springing back into the stash handle.
private float mBubbleBarStashTranslationY;
-
+ // Minimum distance between the BubbleBar and the taskbar
+ private final int mBubbleBarTaskbarMinDistance;
// Whether the bar is hidden for a sysui state.
private boolean mHiddenForSysui;
// Whether the bar is hidden because there are no bubbles.
@@ -150,10 +151,11 @@
mBubbleBarContainer = bubbleBarContainer;
mSystemUiProxy = SystemUiProxy.INSTANCE.get(mActivity);
mBubbleBarAlpha = new MultiValueAlpha(mBarView, 1 /* num alpha channels */);
- mIconSize = activity.getResources().getDimensionPixelSize(
- R.dimen.bubblebar_icon_size);
- mDragElevation = activity.getResources().getDimensionPixelSize(
- R.dimen.bubblebar_drag_elevation);
+ Resources res = activity.getResources();
+ mIconSize = res.getDimensionPixelSize(R.dimen.bubblebar_icon_size);
+ mBubbleBarTaskbarMinDistance = res.getDimensionPixelSize(
+ R.dimen.bubblebar_transient_taskbar_min_distance);
+ mDragElevation = res.getDimensionPixelSize(R.dimen.bubblebar_drag_elevation);
mTaskbarTranslationDelta = getBubbleBarTranslationDeltaForTaskbar(activity);
}
@@ -664,6 +666,45 @@
}
}
+ /**
+ * Returns the translation X of the transient taskbar according to the bubble bar location
+ * regardless of the current taskbar mode.
+ */
+ public int getTransientTaskbarTranslationXForBubbleBar(BubbleBarLocation location) {
+ int taskbarShift = 0;
+ if (!isBubbleBarVisible() || mTaskbarViewPropertiesProvider == null) return taskbarShift;
+ Rect taskbarViewBounds = mTaskbarViewPropertiesProvider.getTaskbarViewBounds();
+ if (taskbarViewBounds.isEmpty()) return taskbarShift;
+ int actualDistance =
+ getDistanceBetweenTransientTaskbarAndBubbleBar(location, taskbarViewBounds);
+ if (actualDistance < mBubbleBarTaskbarMinDistance) {
+ taskbarShift = mBubbleBarTaskbarMinDistance - actualDistance;
+ if (!location.isOnLeft(mBarView.isLayoutRtl())) {
+ taskbarShift = -taskbarShift;
+ }
+ }
+ return taskbarShift;
+ }
+
+ private int getDistanceBetweenTransientTaskbarAndBubbleBar(BubbleBarLocation location,
+ Rect taskbarViewBounds) {
+ Resources res = mActivity.getResources();
+ DeviceProfile transientDp = mActivity.getTransientTaskbarDeviceProfile();
+ int transientIconSize = getBubbleBarIconSizeFromDeviceProfile(res, transientDp);
+ int transientPadding = getBubbleBarPaddingFromDeviceProfile(res, transientDp);
+ int transientWidthWithMargin = (int) (mBarView.getCollapsedWidthForIconSizeAndPadding(
+ transientIconSize, transientPadding) + mBarView.getHorizontalMargin());
+ int distance;
+ if (location.isOnLeft(mBarView.isLayoutRtl())) {
+ distance = taskbarViewBounds.left - transientWidthWithMargin;
+ } else {
+ int displayWidth = res.getDisplayMetrics().widthPixels;
+ int bubbleBarLeft = displayWidth - transientWidthWithMargin;
+ distance = bubbleBarLeft - taskbarViewBounds.right;
+ }
+ return distance;
+ }
+
//
// Modifying view related properties.
//
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt
index 595dac3..fec1eaf 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt
@@ -24,6 +24,7 @@
import com.android.launcher3.taskbar.bubbles.BubbleBarView
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController
import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController
+import com.android.launcher3.util.MultiPropertyFactory
import com.android.wm.shell.shared.animation.PhysicsAnimator
import com.android.wm.shell.shared.bubbles.BubbleBarLocation
import java.io.PrintWriter
@@ -172,6 +173,9 @@
/** Returns bounds of the handle */
fun getHandleBounds(bounds: Rect)
+ /** Returns MultiValueAlpha of the handle view when the handle view is shown. */
+ fun getHandleViewAlpha(): MultiPropertyFactory<View>.MultiProperty? = null
+
/**
* Returns bubble bar Y position according to [isBubblesShowingOnHome] and
* [isBubblesShowingOnOverview] values. Default implementation only analyse
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
index 3e3f569..9c148e2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
@@ -512,6 +512,14 @@
}
}
+ override fun getHandleViewAlpha(): MultiPropertyFactory<View>.MultiProperty? =
+ // only return handle alpha if the bubble bar is stashed and has bubbles
+ if (isStashed && bubbleBarViewController.hasBubbles()) {
+ stashHandleViewAlpha
+ } else {
+ null
+ }
+
private fun Animator.updateTouchRegionOnAnimationEnd(): Animator {
doOnEnd { onIsStashedChanged() }
return this
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt b/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt
index 505f2cb..e7e9f51 100644
--- a/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt
@@ -17,7 +17,6 @@
package com.android.quickstep.fallback.window
import android.content.Context
-import android.os.Handler
import android.util.Log
import android.view.Display
import com.android.launcher3.Flags
@@ -25,7 +24,7 @@
import com.android.launcher3.dagger.LauncherAppSingleton
import com.android.launcher3.util.DaggerSingletonObject
import com.android.launcher3.util.DaggerSingletonTracker
-import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.Executors
import com.android.quickstep.DisplayModel
import com.android.quickstep.FallbackWindowInterface
import com.android.quickstep.dagger.QuickstepBaseAppComponent
@@ -51,15 +50,13 @@
init {
if (Flags.enableFallbackOverviewInWindow() || Flags.enableLauncherOverviewInWindow()) {
- MAIN_EXECUTOR.execute {
- displayManager.registerDisplayListener(displayListener, Handler.getMain())
- // In the scenario where displays were added before this display listener was
- // registered, we should store the RecentsDisplayResources for those displays
- // directly.
- displayManager.displays
- .filter { getDisplayResource(it.displayId) == null }
- .forEach { storeRecentsDisplayResource(it.displayId, it) }
- }
+ displayManager.registerDisplayListener(displayListener, Executors.MAIN_EXECUTOR.handler)
+ // In the scenario where displays were added before this display listener was
+ // registered, we should store the RecentsDisplayResources for those displays
+ // directly.
+ displayManager.displays
+ .filter { getDisplayResource(it.displayId) == null }
+ .forEach { storeRecentsDisplayResource(it.displayId, it) }
tracker.addCloseable { destroy() }
}
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
index f5bef05e..afe988d 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
@@ -242,7 +242,7 @@
private void cancelLongPress(String reason) {
if (DEBUG_NAV_HANDLE) {
- Log.d(TAG, "cancelLongPress");
+ Log.d(TAG, "cancelLongPress: " + reason);
}
mGestureState.setIsInExtendedSlopRegion(false);
MAIN_EXECUTOR.getHandler().removeCallbacks(mTriggerLongPress);
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 870a479..f33eb5e 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -293,15 +293,15 @@
float upDist = -displacement;
boolean isTrackpadGesture = mGestureState.isTrackpadGesture();
float squaredHypot = squaredHypot(displacementX, displacementY);
- boolean isInExtendedSlopRegion = !mGestureState.isInExtendedSlopRegion();
+ boolean isInExtendedSlopRegion = mGestureState.isInExtendedSlopRegion();
boolean passedSlop = isTrackpadGesture
|| (squaredHypot >= mSquaredTouchSlop
- && isInExtendedSlopRegion);
+ && !isInExtendedSlopRegion);
if (DEBUG) {
Log.d(TAG, "ACTION_MOVE: passedSlop=" + passedSlop
+ " ( " + isTrackpadGesture
+ " || (" + squaredHypot + " >= " + mSquaredTouchSlop
- + " && " + isInExtendedSlopRegion + " ))");
+ + " && " + !isInExtendedSlopRegion + " ))");
}
if (!mPassedSlopOnThisGesture && passedSlop) {
diff --git a/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfile.kt b/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfile.kt
index d2cb595..0ee2bd2 100644
--- a/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfile.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfile.kt
@@ -20,7 +20,6 @@
* Container to hold [com.android.launcher3.DeviceProfile] related to Recents.
*
* @property isLargeScreen whether the current device posture has a large screen
+ * @property canEnterDesktopMode whether the current device can enter Desktop UI mode
*/
-data class RecentsDeviceProfile(
- val isLargeScreen: Boolean,
-)
+data class RecentsDeviceProfile(val isLargeScreen: Boolean, val canEnterDesktopMode: Boolean)
diff --git a/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryImpl.kt b/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryImpl.kt
index c64453d..8450f09 100644
--- a/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryImpl.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryImpl.kt
@@ -17,6 +17,7 @@
package com.android.quickstep.recents.data
import com.android.quickstep.views.RecentsViewContainer
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
/**
* Repository for shrink down version of [com.android.launcher3.DeviceProfile] that only contains
@@ -26,5 +27,10 @@
RecentsDeviceProfileRepository {
override fun getRecentsDeviceProfile() =
- with(container.deviceProfile) { RecentsDeviceProfile(isLargeScreen = isTablet) }
+ with(container.deviceProfile) {
+ RecentsDeviceProfile(
+ isLargeScreen = isTablet,
+ canEnterDesktopMode = DesktopModeStatus.canEnterDesktopMode(container.asContext()),
+ )
+ }
}
diff --git a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
index dd83af6..2b364f9 100644
--- a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
+++ b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
@@ -182,6 +182,7 @@
dispatcherProvider = inject(),
getThumbnailPositionUseCase = inject(),
tasksRepository = inject(),
+ deviceProfileRepository = inject(),
splashAlphaUseCase = inject(scopeId),
)
TaskOverlayViewModel::class.java -> {
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
index 36a86f2..6118544 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
@@ -24,18 +24,35 @@
sealed class TaskThumbnailUiState {
data object Uninitialized : TaskThumbnailUiState()
- data object LiveTile : TaskThumbnailUiState()
-
data class BackgroundOnly(@ColorInt val backgroundColor: Int) : TaskThumbnailUiState()
- data class SnapshotSplash(
- val snapshot: Snapshot,
- val splash: Drawable?,
- ) : TaskThumbnailUiState()
+ data class SnapshotSplash(val snapshot: Snapshot, val splash: Drawable?) :
+ TaskThumbnailUiState()
- data class Snapshot(
- val bitmap: Bitmap,
- @Surface.Rotation val thumbnailRotation: Int,
- @ColorInt val backgroundColor: Int
- )
+ sealed class LiveTile : TaskThumbnailUiState() {
+ data class WithHeader(val header: ThumbnailHeader) : LiveTile()
+
+ data object WithoutHeader : LiveTile()
+ }
+
+ sealed class Snapshot {
+ abstract val bitmap: Bitmap
+ abstract val thumbnailRotation: Int
+ abstract val backgroundColor: Int
+
+ data class WithHeader(
+ override val bitmap: Bitmap,
+ @Surface.Rotation override val thumbnailRotation: Int,
+ @ColorInt override val backgroundColor: Int,
+ val header: ThumbnailHeader,
+ ) : Snapshot()
+
+ data class WithoutHeader(
+ override val bitmap: Bitmap,
+ @Surface.Rotation override val thumbnailRotation: Int,
+ @ColorInt override val backgroundColor: Int,
+ ) : Snapshot()
+ }
+
+ data class ThumbnailHeader(val icon: Drawable, val title: String)
}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
index b040723..4a78729 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
@@ -22,11 +22,13 @@
import android.graphics.Rect
import android.util.AttributeSet
import android.util.Log
+import android.view.LayoutInflater
import android.view.View
import android.view.ViewOutlineProvider
import android.widget.FrameLayout
import androidx.annotation.ColorInt
import androidx.core.view.isInvisible
+import com.android.launcher3.Flags.enableDesktopExplodedView
import com.android.launcher3.R
import com.android.launcher3.util.ViewPool
import com.android.launcher3.util.coroutines.DispatcherProvider
@@ -39,6 +41,7 @@
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
import com.android.quickstep.views.FixedSizeImageView
+import com.android.quickstep.views.TaskThumbnailViewHeader
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -66,6 +69,8 @@
private val splashBackground: View by lazy { findViewById(R.id.splash_background) }
private val splashIcon: FixedSizeImageView by lazy { findViewById(R.id.splash_icon) }
+ private var taskThumbnailViewHeader: TaskThumbnailViewHeader? = null
+
private var uiState: TaskThumbnailUiState = Uninitialized
private val bounds = Rect()
@@ -86,6 +91,12 @@
defStyleAttr: Int,
) : super(context, attrs, defStyleAttr)
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+
+ maybeCreateHeader()
+ }
+
override fun onAttachedToWindow() {
super.onAttachedToWindow()
viewAttachedScope =
@@ -102,7 +113,7 @@
resetViews()
when (viewModelUiState) {
is Uninitialized -> {}
- is LiveTile -> drawLiveWindow()
+ is LiveTile -> drawLiveWindow(viewModelUiState)
is SnapshotSplash -> drawSnapshotSplash(viewModelUiState)
is BackgroundOnly -> drawBackground(viewModelUiState.backgroundColor)
}
@@ -179,14 +190,20 @@
splashIcon.alpha = 0f
scrimView.alpha = 0f
setBackgroundColor(Color.BLACK)
+ taskThumbnailViewHeader?.isInvisible = true
}
private fun drawBackground(@ColorInt background: Int) {
setBackgroundColor(background)
}
- private fun drawLiveWindow() {
+ private fun drawLiveWindow(liveTile: LiveTile) {
liveTileView.isInvisible = false
+
+ if (liveTile is LiveTile.WithHeader) {
+ taskThumbnailViewHeader?.isInvisible = false
+ taskThumbnailViewHeader?.setHeader(liveTile.header)
+ }
}
private fun drawSnapshotSplash(snapshotSplash: SnapshotSplash) {
@@ -197,6 +214,11 @@
}
private fun drawSnapshot(snapshot: Snapshot) {
+ if (snapshot is Snapshot.WithHeader) {
+ taskThumbnailViewHeader?.isInvisible = false
+ taskThumbnailViewHeader?.setHeader(snapshot.header)
+ }
+
drawBackground(snapshot.backgroundColor)
thumbnailView.setImageBitmap(snapshot.bitmap)
thumbnailView.isInvisible = false
@@ -210,4 +232,14 @@
private companion object {
const val TAG = "TaskThumbnailView"
}
+
+ private fun maybeCreateHeader() {
+ if (enableDesktopExplodedView() && taskThumbnailViewHeader == null) {
+ taskThumbnailViewHeader =
+ LayoutInflater.from(context)
+ .inflate(R.layout.task_thumbnail_view_header, this, false)
+ as TaskThumbnailViewHeader
+ addView(taskThumbnailViewHeader)
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
index b5b2fc9..a154c3c 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
@@ -18,11 +18,14 @@
import android.annotation.ColorInt
import android.app.ActivityTaskManager.INVALID_TASK_ID
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.graphics.Matrix
import android.util.Log
import androidx.core.graphics.ColorUtils
+import com.android.launcher3.Flags.enableDesktopExplodedView
import com.android.launcher3.util.coroutines.DispatcherProvider
import com.android.quickstep.recents.data.RecentTasksRepository
+import com.android.quickstep.recents.data.RecentsDeviceProfileRepository
import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
import com.android.quickstep.recents.usecase.ThumbnailPositionState
import com.android.quickstep.recents.viewmodel.RecentsViewData
@@ -32,6 +35,7 @@
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.ThumbnailHeader
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
import com.android.systemui.shared.recents.model.Task
import kotlin.math.max
@@ -51,6 +55,7 @@
taskContainerData: TaskContainerData,
dispatcherProvider: DispatcherProvider,
private val tasksRepository: RecentTasksRepository,
+ private val deviceProfileRepository: RecentsDeviceProfileRepository,
private val getThumbnailPositionUseCase: GetThumbnailPositionUseCase,
private val splashAlphaUseCase: SplashAlphaUseCase,
) : TaskThumbnailViewModel {
@@ -90,7 +95,7 @@
// )
when {
taskVal == null -> Uninitialized
- isRunning -> LiveTile
+ isRunning -> createLiveTileState(taskVal)
isBackgroundOnly(taskVal) ->
BackgroundOnly(taskVal.colorBackground.removeAlpha())
isSnapshotSplashState(taskVal) ->
@@ -129,7 +134,46 @@
private fun createSnapshotState(task: Task): Snapshot {
val thumbnailData = task.thumbnail
val bitmap = thumbnailData?.thumbnail!!
- return Snapshot(bitmap, thumbnailData.rotation, task.colorBackground.removeAlpha())
+ var thumbnailHeader = maybeCreateHeader(task)
+ return if (thumbnailHeader != null)
+ Snapshot.WithHeader(
+ bitmap,
+ thumbnailData.rotation,
+ task.colorBackground.removeAlpha(),
+ thumbnailHeader,
+ )
+ else
+ Snapshot.WithoutHeader(
+ bitmap,
+ thumbnailData.rotation,
+ task.colorBackground.removeAlpha(),
+ )
+ }
+
+ private fun shouldHaveThumbnailHeader(task: Task): Boolean {
+ return deviceProfileRepository.getRecentsDeviceProfile().canEnterDesktopMode &&
+ enableDesktopExplodedView() &&
+ task.key.windowingMode == WINDOWING_MODE_FREEFORM
+ }
+
+ private fun maybeCreateHeader(task: Task): ThumbnailHeader? {
+ // Header is only needed when this task is a desktop task and Overivew exploded view is
+ // enabled.
+ if (!shouldHaveThumbnailHeader(task)) {
+ return null
+ }
+
+ // TODO(http://b/353965691): figure out what to do when `icon` or `titleDescription` is
+ // null.
+ val icon = task.icon ?: return null
+ val titleDescription = task.titleDescription ?: return null
+ return ThumbnailHeader(icon, titleDescription)
+ }
+
+ private fun createLiveTileState(task: Task): LiveTile {
+ val thumbnailHeader = maybeCreateHeader(task)
+ return if (thumbnailHeader != null) LiveTile.WithHeader(thumbnailHeader)
+ else LiveTile.WithoutHeader
}
@ColorInt private fun Int.removeAlpha(): Int = ColorUtils.setAlphaComponent(this, 0xff)
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewHeader.kt b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewHeader.kt
new file mode 100644
index 0000000..9eb294a
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewHeader.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2025 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.views
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.FrameLayout
+import android.widget.ImageView
+import android.widget.TextView
+import com.android.launcher3.R
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.ThumbnailHeader
+
+class TaskThumbnailViewHeader
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs) {
+
+ private val headerTitleView: TextView by lazy { findViewById(R.id.header_app_title) }
+ private val headerIconView: ImageView by lazy { findViewById(R.id.header_app_icon) }
+
+ fun setHeader(header: ThumbnailHeader) {
+ headerTitleView.setText(header.title)
+ headerIconView.setImageDrawable(header.icon)
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt
index f642345..b24926b 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt
@@ -507,6 +507,45 @@
assertThat(height).isEqualTo(BUBBLE_BAR_HEIGHT)
}
+ @Test
+ fun getHandleViewAlpha_stashedHasBubbles_alphaPropertyReturned() {
+ // Given BubbleBar is stashed and has bubbles
+ whenever(bubbleBarViewController.hasBubbles()).thenReturn(true)
+ mTransientBubbleStashController.isStashed = true
+
+ // When handle view alpha property
+ val alphaProperty = mTransientBubbleStashController.getHandleViewAlpha()
+
+ // Then the stash handle alpha property should not be null
+ assertThat(alphaProperty).isNotNull()
+ }
+
+ @Test
+ fun getHandleViewAlpha_stashedHasNoBubblesBar_alphaPropertyIsNull() {
+ // Given BubbleBar is stashed and has no bubbles
+ whenever(bubbleBarViewController.hasBubbles()).thenReturn(false)
+ mTransientBubbleStashController.isStashed = true
+
+ // When handle view alpha property
+ val alphaProperty = mTransientBubbleStashController.getHandleViewAlpha()
+
+ // Then the stash handle alpha property should be null
+ assertThat(alphaProperty).isNull()
+ }
+
+ @Test
+ fun getHandleViewAlpha_unstashedHasBubbles_alphaPropertyIsNull() {
+ // Given BubbleBar is not stashed and has bubbles
+ whenever(bubbleBarViewController.hasBubbles()).thenReturn(true)
+ mTransientBubbleStashController.isStashed = false
+
+ // When handle view alpha property
+ val alphaProperty = mTransientBubbleStashController.getHandleViewAlpha()
+
+ // Then the stash handle alpha property should be null
+ assertThat(alphaProperty).isNull()
+ }
+
private fun advanceTimeBy(advanceMs: Long) {
// Advance animator for on-device tests
getInstrumentation().runOnMainSync { animatorTestRule.advanceTimeBy(advanceMs) }
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
index 0738336..2b337c7 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
@@ -17,6 +17,12 @@
package com.android.quickstep
import android.graphics.PointF
+import android.hardware.display.DisplayManager
+import android.hardware.display.DisplayManagerGlobal
+import android.view.Display
+import android.view.Display.DEFAULT_DISPLAY
+import android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
+import android.view.DisplayInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.launcher3.R
@@ -39,6 +45,7 @@
import org.mockito.junit.MockitoJUnit
import org.mockito.kotlin.eq
import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -63,8 +70,21 @@
private val flingSpeed =
-(sandboxContext.resources.getDimension(R.dimen.quickstep_fling_threshold_speed) + 1)
+ private val displayManager: DisplayManager =
+ sandboxContext.spyService(DisplayManager::class.java)
+
@Before
fun setup() {
+ val display =
+ Display(
+ DisplayManagerGlobal.getInstance(),
+ DEFAULT_DISPLAY,
+ DisplayInfo(),
+ DEFAULT_DISPLAY_ADJUSTMENTS,
+ )
+ whenever(displayManager.getDisplay(eq(DEFAULT_DISPLAY))).thenReturn(display)
+ whenever(displayManager.displays).thenReturn(arrayOf(display))
+
sandboxContext.initDaggerComponent(
DaggerTestComponent.builder().bindSystemUiProxy(systemUiProxy)
)
@@ -72,8 +92,10 @@
RotationTouchHelper.INSTANCE,
mock(RotationTouchHelper::class.java),
)
- val deviceState = mock(RecentsAnimationDeviceState::class.java)
- sandboxContext.putObject(RecentsAnimationDeviceState.INSTANCE, deviceState)
+ sandboxContext.putObject(
+ RecentsAnimationDeviceState.INSTANCE,
+ mock(RecentsAnimationDeviceState::class.java),
+ )
gestureState = spy(GestureState(OverviewComponentObserver.INSTANCE.get(sandboxContext), 0))
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentsDeviceProfileRepository.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentsDeviceProfileRepository.kt
index fc2f029..4e90903 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentsDeviceProfileRepository.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentsDeviceProfileRepository.kt
@@ -18,9 +18,7 @@
class FakeRecentsDeviceProfileRepository : RecentsDeviceProfileRepository {
private var recentsDeviceProfile =
- RecentsDeviceProfile(
- isLargeScreen = false,
- )
+ RecentsDeviceProfile(isLargeScreen = false, canEnterDesktopMode = false)
override fun getRecentsDeviceProfile() = recentsDeviceProfile
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryImplTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryImplTest.kt
deleted file mode 100644
index abe4142..0000000
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryImplTest.kt
+++ /dev/null
@@ -1,44 +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.quickstep.recents.data
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.launcher3.FakeInvariantDeviceProfileTest
-import com.android.quickstep.views.RecentsViewContainer
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.whenever
-
-/** Test for [RecentsDeviceProfileRepositoryImpl] */
-@RunWith(AndroidJUnit4::class)
-class RecentsDeviceProfileRepositoryImplTest : FakeInvariantDeviceProfileTest() {
- private val recentsViewContainer = mock<RecentsViewContainer>()
-
- private val systemUnderTest = RecentsDeviceProfileRepositoryImpl(recentsViewContainer)
-
- @Test
- fun deviceProfileMappedCorrectly() {
- initializeVarsForTablet()
- val tabletDeviceProfile = newDP()
- whenever(recentsViewContainer.deviceProfile).thenReturn(tabletDeviceProfile)
-
- assertThat(systemUnderTest.getRecentsDeviceProfile())
- .isEqualTo(RecentsDeviceProfile(isLargeScreen = true))
- }
-}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt
index a777bd4..a956c9c 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt
@@ -16,16 +16,23 @@
package com.android.quickstep.task.thumbnail
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.content.ComponentName
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.Matrix
import android.graphics.drawable.Drawable
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
import android.view.Surface
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.Flags
import com.android.launcher3.util.TestDispatcherProvider
+import com.android.quickstep.recents.data.FakeRecentsDeviceProfileRepository
import com.android.quickstep.recents.data.FakeTasksRepository
+import com.android.quickstep.recents.data.RecentsDeviceProfile
import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
import com.android.quickstep.recents.usecase.ThumbnailPositionState.MatrixScaling
import com.android.quickstep.recents.usecase.ThumbnailPositionState.MissingThumbnail
@@ -34,6 +41,7 @@
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.ThumbnailHeader
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
import com.android.quickstep.task.viewmodel.TaskContainerData
import com.android.quickstep.task.viewmodel.TaskThumbnailViewModelImpl
@@ -44,6 +52,7 @@
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
@@ -52,6 +61,8 @@
/** Test for [TaskThumbnailView] */
@RunWith(AndroidJUnit4::class)
class TaskThumbnailViewModelImplTest {
+ @get:Rule val setFlagsRule = SetFlagsRule()
+
private val dispatcher = StandardTestDispatcher()
private val testScope = TestScope(dispatcher)
@@ -59,6 +70,7 @@
private val taskContainerData = TaskContainerData()
private val dispatcherProvider = TestDispatcherProvider(dispatcher)
private val tasksRepository = FakeTasksRepository()
+ private val deviceProfileRepository = FakeRecentsDeviceProfileRepository()
private val mGetThumbnailPositionUseCase = mock<GetThumbnailPositionUseCase>()
private val splashAlphaUseCase: SplashAlphaUseCase = mock()
@@ -68,12 +80,18 @@
taskContainerData,
dispatcherProvider,
tasksRepository,
+ deviceProfileRepository,
mGetThumbnailPositionUseCase,
splashAlphaUseCase,
)
}
- private val tasks = (0..5).map(::createTaskWithId)
+ private val fullscreenTaskIdRange: IntRange = 0..5
+ private val freeformTaskIdRange: IntRange = 6..10
+
+ private val fullscreenTasks = fullscreenTaskIdRange.map(::createTaskWithId)
+ private val freeformTasks = freeformTaskIdRange.map(::createFreeformTaskWithId)
+ private val tasks = fullscreenTasks + freeformTasks
@Test
fun initialStateIsUninitialized() =
@@ -88,7 +106,7 @@
recentsViewData.runningTaskIds.value = setOf(taskId)
systemUnderTest.bind(taskId)
- assertThat(systemUnderTest.uiState.first()).isEqualTo(LiveTile)
+ assertThat(systemUnderTest.uiState.first()).isEqualTo(LiveTile.WithoutHeader)
}
@Test
@@ -108,7 +126,7 @@
assertThat(systemUnderTest.uiState.first())
.isEqualTo(
SnapshotSplash(
- Snapshot(
+ Snapshot.WithoutHeader(
backgroundColor = Color.rgb(1, 1, 1),
bitmap = expectedThumbnailData.thumbnail!!,
thumbnailRotation = Surface.ROTATION_0,
@@ -127,7 +145,7 @@
tasksRepository.setVisibleTasks(setOf(runningTaskId, stoppedTaskId))
recentsViewData.runningTaskIds.value = setOf(runningTaskId)
systemUnderTest.bind(runningTaskId)
- assertThat(systemUnderTest.uiState.first()).isEqualTo(LiveTile)
+ assertThat(systemUnderTest.uiState.first()).isEqualTo(LiveTile.WithoutHeader)
systemUnderTest.bind(stoppedTaskId)
assertThat(systemUnderTest.uiState.first())
@@ -175,7 +193,7 @@
assertThat(systemUnderTest.uiState.first())
.isEqualTo(
SnapshotSplash(
- Snapshot(
+ Snapshot.WithoutHeader(
backgroundColor = Color.rgb(2, 2, 2),
bitmap = expectedThumbnailData.thumbnail!!,
thumbnailRotation = Surface.ROTATION_270,
@@ -202,7 +220,7 @@
assertThat(systemUnderTest.uiState.first())
.isEqualTo(
SnapshotSplash(
- Snapshot(
+ Snapshot.WithoutHeader(
backgroundColor = Color.rgb(2, 2, 2),
bitmap = expectedThumbnailData.thumbnail!!,
thumbnailRotation = Surface.ROTATION_0,
@@ -213,6 +231,57 @@
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
+ fun bindRunningTask_inDesktop_thenStateIs_LiveTile_withHeader() =
+ testScope.runTest {
+ deviceProfileRepository.setRecentsDeviceProfile(
+ RecentsDeviceProfile(isLargeScreen = true, canEnterDesktopMode = true)
+ )
+
+ val taskId = freeformTaskIdRange.first
+ val expectedIconData = mock<Drawable>()
+ tasksRepository.seedIconData(taskId, "Task $taskId", "Task $taskId", expectedIconData)
+ tasksRepository.seedTasks(freeformTasks)
+ tasksRepository.setVisibleTasks(setOf(taskId))
+ recentsViewData.runningTaskIds.value = setOf(taskId)
+ systemUnderTest.bind(taskId)
+
+ assertThat(systemUnderTest.uiState.first())
+ .isEqualTo(LiveTile.WithHeader(ThumbnailHeader(expectedIconData, "Task $taskId")))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
+ fun bindStoppedTaskWithThumbnail_inDesktop_thenStateIs_SnapshotSplash_withHeader() =
+ testScope.runTest {
+ deviceProfileRepository.setRecentsDeviceProfile(
+ RecentsDeviceProfile(isLargeScreen = true, canEnterDesktopMode = true)
+ )
+
+ val taskId = freeformTaskIdRange.first
+ val expectedThumbnailData = createThumbnailData(rotation = Surface.ROTATION_0)
+ tasksRepository.seedThumbnailData(mapOf(taskId to expectedThumbnailData))
+ val expectedIconData = mock<Drawable>()
+ tasksRepository.seedIconData(taskId, "Task $taskId", "Task $taskId", expectedIconData)
+ tasksRepository.seedTasks(freeformTasks)
+ tasksRepository.setVisibleTasks(setOf(taskId))
+
+ systemUnderTest.bind(taskId)
+ assertThat(systemUnderTest.uiState.first())
+ .isEqualTo(
+ SnapshotSplash(
+ Snapshot.WithHeader(
+ backgroundColor = Color.rgb(taskId, taskId, taskId),
+ bitmap = expectedThumbnailData.thumbnail!!,
+ thumbnailRotation = Surface.ROTATION_0,
+ header = ThumbnailHeader(expectedIconData, "Task $taskId"),
+ ),
+ expectedIconData,
+ )
+ )
+ }
+
+ @Test
fun getSnapshotMatrix_MissingThumbnail() =
testScope.runTest {
val taskId = 2
@@ -269,9 +338,38 @@
}
private fun createTaskWithId(taskId: Int) =
- Task(Task.TaskKey(taskId, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
- colorBackground = Color.argb(taskId, taskId, taskId, taskId)
- }
+ Task(
+ Task.TaskKey(
+ taskId,
+ WINDOWING_MODE_FULLSCREEN,
+ Intent(),
+ ComponentName("", ""),
+ 0,
+ 2000,
+ )
+ )
+ .apply {
+ colorBackground = Color.argb(taskId, taskId, taskId, taskId)
+ titleDescription = "Task $taskId"
+ icon = mock<Drawable>()
+ }
+
+ private fun createFreeformTaskWithId(taskId: Int) =
+ Task(
+ Task.TaskKey(
+ taskId,
+ WINDOWING_MODE_FREEFORM,
+ Intent(),
+ ComponentName("", ""),
+ 0,
+ 2000,
+ )
+ )
+ .apply {
+ colorBackground = Color.argb(taskId, taskId, taskId, taskId)
+ titleDescription = "Task $taskId"
+ icon = mock<Drawable>()
+ }
private fun createThumbnailData(rotation: Int = Surface.ROTATION_0): ThumbnailData {
val bitmap = mock<Bitmap>()
diff --git a/quickstep/tests/src/com/android/quickstep/RecentsDeviceProfileRepositoryImplTest.kt b/quickstep/tests/src/com/android/quickstep/RecentsDeviceProfileRepositoryImplTest.kt
new file mode 100644
index 0000000..418d66c
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/RecentsDeviceProfileRepositoryImplTest.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2025 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
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.launcher3.FakeInvariantDeviceProfileTest
+import com.android.quickstep.recents.data.RecentsDeviceProfile
+import com.android.quickstep.recents.data.RecentsDeviceProfileRepositoryImpl
+import com.android.quickstep.views.RecentsViewContainer
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+import org.mockito.quality.Strictness
+
+class RecentsDeviceProfileRepositoryImplTest : FakeInvariantDeviceProfileTest() {
+ private val recentsViewContainer: RecentsViewContainer = mock()
+
+ private lateinit var mockitoSession: StaticMockitoSession
+
+ @Before
+ override fun setUp() {
+ super.setUp()
+ mockitoSession =
+ mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .mockStatic(DesktopModeStatus::class.java)
+ .startMocking()
+ whenever(recentsViewContainer.asContext()).thenReturn(context)
+ }
+
+ @After
+ fun tearDown() {
+ mockitoSession.finishMocking()
+ }
+
+ @Test
+ fun deviceProfileMappedCorrectlyForPhone() {
+ val deviceProfileRepo = RecentsDeviceProfileRepositoryImpl(recentsViewContainer)
+ initializeVarsForPhone()
+ val phoneDeviceProfile = newDP()
+ whenever(recentsViewContainer.deviceProfile).thenReturn(phoneDeviceProfile)
+
+ whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(false)
+ assertThat(deviceProfileRepo.getRecentsDeviceProfile())
+ .isEqualTo(RecentsDeviceProfile(isLargeScreen = false, canEnterDesktopMode = false))
+ }
+
+ @Test
+ fun deviceProfileMappedCorrectlyForTablet() {
+ val deviceProfileRepo = RecentsDeviceProfileRepositoryImpl(recentsViewContainer)
+ initializeVarsForTablet()
+ val tabletDeviceProfile = newDP()
+ whenever(recentsViewContainer.deviceProfile).thenReturn(tabletDeviceProfile)
+
+ whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true)
+ assertThat(deviceProfileRepo.getRecentsDeviceProfile())
+ .isEqualTo(RecentsDeviceProfile(isLargeScreen = true, canEnterDesktopMode = true))
+ }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java b/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java
index c24e974..08ce5e7 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java
@@ -124,6 +124,7 @@
}
@Test
+ @ScreenRecordRule.ScreenRecord // b/373417111
public void testLaunchShortcut_fromTaskbarAllApps() {
getTaskbar().openAllApps()
.getAppIcon(TEST_APP_NAME)
@@ -134,7 +135,6 @@
@Test
@PortraitLandscape
- @ScreenRecordRule.ScreenRecord // b/349439239
public void testLaunchAppInSplitscreen_fromTaskbarAllApps() {
getTaskbar().openAllApps()
.getAppIcon(TEST_APP_NAME)
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index e44caa4..7563493 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -681,7 +681,7 @@
} else {
// Wrap the main icon in AID
try (LauncherIcons li = LauncherIcons.obtain(context)) {
- result = li.wrapToAdaptiveIcon(mainIcon, null);
+ result = li.wrapToAdaptiveIcon(mainIcon);
}
}
if (result == null) {
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 67fe889..f1b461b 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -264,8 +264,7 @@
try (LauncherIcons li = LauncherIcons.obtain(mActivity)) {
// Since we just want the scale, avoid heavy drawing operations
Utilities.scaleRectAboutCenter(bounds, li.getNormalizer().getScale(
- new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK), null),
- null, null, null));
+ new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK), null)));
}
// Shrink very tiny bit so that the clip path is smaller than the original bitmap
diff --git a/src/com/android/launcher3/graphics/IconShape.kt b/src/com/android/launcher3/graphics/IconShape.kt
index 1377610..53ce33f 100644
--- a/src/com/android/launcher3/graphics/IconShape.kt
+++ b/src/com/android/launcher3/graphics/IconShape.kt
@@ -74,13 +74,11 @@
setBounds(0, 0, AREA_CALC_SIZE, AREA_CALC_SIZE)
}
- normalizationScale = IconNormalizer.normalizeAdaptiveIcon(drawable, AREA_CALC_SIZE, null)
+ normalizationScale = IconNormalizer.normalizeAdaptiveIcon(drawable, AREA_CALC_SIZE)
return pickBestShape(drawable.iconMask, themeManager.iconState.iconMask)
}
interface ShapeDelegate {
- fun enableShapeDetection() = false
-
fun drawShape(canvas: Canvas, offsetX: Float, offsetY: Float, radius: Float, paint: Paint)
fun addToPath(path: Path, offsetX: Float, offsetY: Float, radius: Float)
@@ -107,8 +105,6 @@
override fun addToPath(path: Path, offsetX: Float, offsetY: Float, radius: Float) =
path.addCircle(radius + offsetX, radius + offsetY, radius, Path.Direction.CW)
-
- override fun enableShapeDetection() = true
}
/** Rounded square with [radiusRatio] as a ratio of its half edge size */
diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java
index 04d88b0..d142066 100644
--- a/src/com/android/launcher3/icons/LauncherIcons.java
+++ b/src/com/android/launcher3/icons/LauncherIcons.java
@@ -22,7 +22,6 @@
import androidx.annotation.NonNull;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.graphics.ThemeManager;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.MainThreadInitializedObject;
@@ -56,8 +55,7 @@
protected LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize,
ConcurrentLinkedQueue<LauncherIcons> pool) {
- super(context, fillResIconDpi, iconBitmapSize,
- IconShape.INSTANCE.get(context).getShape().enableShapeDetection());
+ super(context, fillResIconDpi, iconBitmapSize);
mThemeController = ThemeManager.INSTANCE.get(context).getThemeController();
mPool = pool;
}
diff --git a/src/com/android/launcher3/model/GridSizeMigrationDBController.java b/src/com/android/launcher3/model/GridSizeMigrationDBController.java
index 211c351..0732379 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationDBController.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationDBController.java
@@ -120,7 +120,8 @@
@NonNull DeviceGridState destDeviceState,
@NonNull DatabaseHelper target,
@NonNull SQLiteDatabase source,
- boolean isDestNewDb) {
+ boolean isDestNewDb,
+ ModelDelegate modelDelegate) {
if (!needsToMigrate(srcDeviceState, destDeviceState)) {
return true;
@@ -174,7 +175,6 @@
return true;
} catch (Exception e) {
Log.e(TAG, "Error during grid migration", e);
-
return false;
} finally {
Log.v(TAG, "Workspace migration completed in "
@@ -182,6 +182,8 @@
// Save current configuration, so that the migration does not run again.
destDeviceState.writeToPrefs(context);
+ // Notify if we've migrated successfully
+ modelDelegate.gridMigrationComplete(srcDeviceState, destDeviceState);
}
}
diff --git a/src/com/android/launcher3/model/GridSizeMigrationLogic.kt b/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
index 0b12af8..1729153 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
+++ b/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
@@ -53,6 +53,7 @@
target: DatabaseHelper,
source: SQLiteDatabase,
isDestNewDb: Boolean,
+ modelDelegate: ModelDelegate,
) {
if (!GridSizeMigrationDBController.needsToMigrate(srcDeviceState, destDeviceState)) {
return
@@ -132,6 +133,9 @@
// Save current configuration, so that the migration does not run again.
destDeviceState.writeToPrefs(context)
+
+ // Notify if we've migrated successfully
+ modelDelegate.gridMigrationComplete(srcDeviceState, destDeviceState)
}
}
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 4e57944..44b7e8b 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -438,12 +438,12 @@
ModelDbController dbController = mApp.getModel().getModelDbController();
if (Flags.gridMigrationRefactor()) {
try {
- dbController.attemptMigrateDb(restoreEventLogger);
+ dbController.attemptMigrateDb(restoreEventLogger, mModelDelegate);
} catch (Exception e) {
FileLog.e(TAG, "Failed to migrate grid", e);
}
} else {
- dbController.tryMigrateDB(restoreEventLogger);
+ dbController.tryMigrateDB(restoreEventLogger, mModelDelegate);
}
Log.d(TAG, "loadWorkspace: loading default favorites if necessary");
dbController.loadDefaultFavoritesIfNecessary();
diff --git a/src/com/android/launcher3/model/ModelDbController.java b/src/com/android/launcher3/model/ModelDbController.java
index e76391f..0138390 100644
--- a/src/com/android/launcher3/model/ModelDbController.java
+++ b/src/com/android/launcher3/model/ModelDbController.java
@@ -361,7 +361,8 @@
/**
* Migrates the DB. If the migration failed, it clears the DB.
*/
- public void attemptMigrateDb(LauncherRestoreEventLogger restoreEventLogger) throws Exception {
+ public void attemptMigrateDb(LauncherRestoreEventLogger restoreEventLogger,
+ ModelDelegate modelDelegate) throws Exception {
createDbIfNotExists();
if (shouldResetDb()) {
@@ -389,7 +390,7 @@
boolean isDestNewDb = !existingDBs.contains(destDeviceState.getDbFile());
GridSizeMigrationLogic gridSizeMigrationLogic = new GridSizeMigrationLogic();
gridSizeMigrationLogic.migrateGrid(mContext, srcDeviceState, destDeviceState,
- mOpenHelper, oldHelper.getWritableDatabase(), isDestNewDb);
+ mOpenHelper, oldHelper.getWritableDatabase(), isDestNewDb, modelDelegate);
} catch (Exception e) {
resetLauncherDb(restoreEventLogger);
throw new Exception("attemptMigrateDb: Failed to migrate grid", e);
@@ -403,8 +404,9 @@
/**
* Migrates the DB if needed. If the migration failed, it clears the DB.
*/
- public void tryMigrateDB(@Nullable LauncherRestoreEventLogger restoreEventLogger) {
- if (!migrateGridIfNeeded()) {
+ public void tryMigrateDB(@Nullable LauncherRestoreEventLogger restoreEventLogger,
+ ModelDelegate modelDelegate) {
+ if (!migrateGridIfNeeded(modelDelegate)) {
if (restoreEventLogger != null) {
if (LauncherPrefs.get(mContext).get(NO_DB_FILES_RESTORED)) {
restoreEventLogger.logLauncherItemsRestoreFailed(DATA_TYPE_DB_FILE, 1,
@@ -434,7 +436,7 @@
* @return true if migration was success or ignored, false if migration failed
* and the DB should be reset.
*/
- private boolean migrateGridIfNeeded() {
+ private boolean migrateGridIfNeeded(ModelDelegate modelDelegate) {
createDbIfNotExists();
if (LauncherPrefs.get(mContext).get(getEmptyDbCreatedKey())) {
// If we have already create a new DB, ignore migration
@@ -468,7 +470,8 @@
DeviceGridState destDeviceState = new DeviceGridState(idp);
boolean isDestNewDb = !existingDBs.contains(destDeviceState.getDbFile());
return GridSizeMigrationDBController.migrateGridIfNeeded(mContext, srcDeviceState,
- destDeviceState, mOpenHelper, oldHelper.getWritableDatabase(), isDestNewDb);
+ destDeviceState, mOpenHelper, oldHelper.getWritableDatabase(), isDestNewDb,
+ modelDelegate);
} catch (Exception e) {
FileLog.e(TAG, "migrateGridIfNeeded: Failed to migrate grid", e);
return false;
diff --git a/src/com/android/launcher3/model/ModelDelegate.java b/src/com/android/launcher3/model/ModelDelegate.java
index 2264d35..5a2aef0 100644
--- a/src/com/android/launcher3/model/ModelDelegate.java
+++ b/src/com/android/launcher3/model/ModelDelegate.java
@@ -123,6 +123,11 @@
@WorkerThread
public void modelLoadComplete() { }
+ /** Called when grid migration has completed as part of grid size refactor. */
+ @WorkerThread
+ public void gridMigrationComplete(
+ @NonNull DeviceGridState src, @NonNull DeviceGridState dest) { }
+
/**
* Called when the delegate is no loner needed
*/
diff --git a/src/com/android/launcher3/util/MultiTranslateDelegate.java b/src/com/android/launcher3/util/MultiTranslateDelegate.java
index 38c87c8..ce006c4 100644
--- a/src/com/android/launcher3/util/MultiTranslateDelegate.java
+++ b/src/com/android/launcher3/util/MultiTranslateDelegate.java
@@ -38,6 +38,7 @@
public static final int INDEX_TASKBAR_REVEAL_ANIM = 4;
public static final int INDEX_TASKBAR_PINNING_ANIM = 5;
public static final int INDEX_NAV_BAR_ANIM = 6;
+ public static final int INDEX_BUBBLE_BAR_ANIM = 7;
// Affect all items inside of a MultipageCellLayout
public static final int INDEX_CELLAYOUT_MULTIPAGE_SPACING = 3;
@@ -48,7 +49,7 @@
// Specific for hotseat items when adjusting for bubbles
public static final int INDEX_BUBBLE_ADJUSTMENT_ANIM = 3;
- public static final int COUNT = 7;
+ public static final int COUNT = 8;
private final MultiPropertyFactory<View> mTranslationX;
private final MultiPropertyFactory<View> mTranslationY;
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 6739387..22857b1 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -465,8 +465,7 @@
bounds.inset(blurSizeOutline / 2, blurSizeOutline / 2);
try (LauncherIcons li = LauncherIcons.obtain(l)) {
- Utilities.scaleRectAboutCenter(bounds, li.getNormalizer().getScale(drawable, null,
- null, null));
+ Utilities.scaleRectAboutCenter(bounds, li.getNormalizer().getScale(drawable));
}
bounds.inset(
diff --git a/tests/multivalentTests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt b/tests/multivalentTests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
index 954dc8f..bfbdb18 100644
--- a/tests/multivalentTests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
@@ -58,7 +58,7 @@
@Rule @JvmField val limitDevicesRule = LimitDevicesRule()
@Before
- fun setUp() {
+ open fun setUp() {
context = ApplicationProvider.getApplicationContext()
// make sure to reset values
useTwoPanels = false
@@ -83,7 +83,7 @@
protected fun initializeVarsForPhone(
isGestureMode: Boolean = true,
- isVerticalBar: Boolean = false
+ isVerticalBar: Boolean = false,
) {
val (x, y) = if (isVerticalBar) Pair(2400, 1080) else Pair(1080, 2400)
@@ -94,8 +94,8 @@
if (isVerticalBar) 118 else 0,
if (isVerticalBar) 74 else 118,
if (!isGestureMode && isVerticalBar) 126 else 0,
- if (isGestureMode) 63 else if (isVerticalBar) 0 else 126
- )
+ if (isGestureMode) 63 else if (isVerticalBar) 0 else 126,
+ ),
)
whenever(info.isTablet(any())).thenReturn(false)
@@ -121,7 +121,7 @@
PointF(80f, 104f),
PointF(80f, 104f),
PointF(80f, 104f),
- PointF(80f, 104f)
+ PointF(80f, 104f),
)
.toTypedArray()
@@ -143,7 +143,7 @@
PointF(80f, 104f),
PointF(80f, 104f),
PointF(80f, 104f),
- PointF(80f, 104f)
+ PointF(80f, 104f),
)
.toTypedArray()
allAppsIconSize = floatArrayOf(60f, 60f, 60f, 60f)
@@ -174,7 +174,7 @@
protected fun initializeVarsForTablet(
isLandscape: Boolean = false,
- isGestureMode: Boolean = true
+ isGestureMode: Boolean = true,
) {
val (x, y) = if (isLandscape) Pair(2560, 1600) else Pair(1600, 2560)
@@ -203,7 +203,7 @@
PointF(102f, 120f),
PointF(120f, 104f),
PointF(102f, 120f),
- PointF(102f, 120f)
+ PointF(102f, 120f),
)
.toTypedArray()
@@ -225,7 +225,7 @@
PointF(96f, 142f),
PointF(126f, 126f),
PointF(96f, 142f),
- PointF(96f, 142f)
+ PointF(96f, 142f),
)
.toTypedArray()
allAppsIconSize = FloatArray(4) { 60f }
@@ -288,7 +288,7 @@
PointF(80f, 104f),
PointF(80f, 104f),
PointF(68f, 116f),
- PointF(80f, 102f)
+ PointF(80f, 102f),
)
.toTypedArray()
diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
index a9082e2..47110fa 100644
--- a/tests/multivalentTests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
@@ -61,9 +61,11 @@
ModelDbController controller = model.getModelDbController();
// Migrate any previous data so that the DB state is correct
if (Flags.gridMigrationRefactor()) {
- controller.attemptMigrateDb(null /* restoreEventLogger */);
+ controller.attemptMigrateDb(
+ null /* restoreEventLogger */, model.getModelDelegate());
} else {
- controller.tryMigrateDB(null /* restoreEventLogger */);
+ controller.tryMigrateDB(null /* restoreEventLogger */,
+ model.getModelDelegate());
}
// Create DB again to load fresh data
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/ModelTestExtensions.kt b/tests/multivalentTests/src/com/android/launcher3/util/ModelTestExtensions.kt
index 8d072d8..547d05e 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/ModelTestExtensions.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/ModelTestExtensions.kt
@@ -31,8 +31,9 @@
loadModelSync()
TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
modelDbController.run {
- if (Flags.gridMigrationRefactor()) attemptMigrateDb(null /* restoreEventLogger */)
- else tryMigrateDB(null /* restoreEventLogger */)
+ if (Flags.gridMigrationRefactor())
+ attemptMigrateDb(null /* restoreEventLogger */, modelDelegate)
+ else tryMigrateDB(null /* restoreEventLogger */, modelDelegate)
createEmptyDB()
clearEmptyDbFlag()
}
@@ -74,7 +75,7 @@
loadModelSync()
TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
val controller: ModelDbController = modelDbController
- controller.attemptMigrateDb(null /* restoreEventLogger */)
+ controller.attemptMigrateDb(null /* restoreEventLogger */, modelDelegate)
modelDbController.newTransaction().use { transaction ->
val values =
ContentValues().apply {
diff --git a/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt b/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt
index b4ee090..38fad6b 100644
--- a/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt
+++ b/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt
@@ -25,6 +25,7 @@
import com.android.launcher3.Flags
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.model.ModelDbController
+import com.android.launcher3.model.ModelDelegate
import com.android.launcher3.provider.RestoreDbTask
import com.android.launcher3.util.Executors.MODEL_EXECUTOR
import com.android.launcher3.util.TestUtil
@@ -34,6 +35,7 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
/**
* Makes sure to test {@code RestoreDbTask#removeOldDBs}, we need to remove all the dbs that are not
@@ -49,6 +51,8 @@
@Rule
val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT)
+ val modelDelegate = mock<ModelDelegate>()
+
@Before
fun setUp() {
setFlagsRule.setFlags(true, Flags.FLAG_ENABLE_NARROW_GRID_RESTORE)
@@ -68,9 +72,9 @@
fun oldDatabasesNotPresentAfterRestore() {
val dbController = ModelDbController(getInstrumentation().targetContext)
if (Flags.gridMigrationRefactor()) {
- dbController.attemptMigrateDb(null)
+ dbController.attemptMigrateDb(null, modelDelegate)
} else {
- dbController.tryMigrateDB(null)
+ dbController.tryMigrateDB(null, modelDelegate)
}
TestUtil.runOnExecutorSync(MODEL_EXECUTOR) {
assert(backAndRestoreRule.getDatabaseFiles().size == 1) {
diff --git a/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
index 20684b3..f6aa31a 100644
--- a/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
+++ b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
@@ -55,7 +55,6 @@
*/
@Test
@PortraitLandscape
- @ScreenRecordRule.ScreenRecord // b/349439239
public void testDeleteFromWorkspace() {
for (String appName : new String[]{GMAIL_APP_NAME, STORE_APP_NAME, TEST_APP_NAME}) {
final HomeAppIcon homeAppIcon = createShortcutInCenterIfNotExist(appName);
diff --git a/tests/src/com/android/launcher3/model/GridMigrationTest.kt b/tests/src/com/android/launcher3/model/GridMigrationTest.kt
index b8ffe74..8bd0c60 100644
--- a/tests/src/com/android/launcher3/model/GridMigrationTest.kt
+++ b/tests/src/com/android/launcher3/model/GridMigrationTest.kt
@@ -32,6 +32,8 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
private val phoneContext = InstrumentationRegistry.getInstrumentation().targetContext
@@ -87,6 +89,8 @@
@Rule
val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT)
+ val modelDelegate = mock<ModelDelegate>()
+
@Before
fun setup() {
setFlagsRule.setFlags(true, Flags.FLAG_ONE_GRID_SPECS)
@@ -102,6 +106,7 @@
dst.dbHelper,
src.dbHelper.readableDatabase,
true,
+ modelDelegate,
)
} else {
GridSizeMigrationDBController.migrateGridIfNeeded(
@@ -111,6 +116,7 @@
dst.dbHelper,
src.dbHelper.readableDatabase,
true,
+ modelDelegate,
)
}
}
@@ -149,11 +155,12 @@
}
/**
- * Migrate src into dst and compare to target. This method validates 3 things:
+ * Migrate src into dst and compare to target. This method validates 4 things:
* 1. dst has the same number of items as src after the migration, meaning, none of the items
* were removed during the migration.
* 2. dst is valid, meaning that none of the items overlap with each other.
* 3. dst is equal to target to ensure we don't unintentionally change the migration logic.
+ * 4. migration notifies the complete callback.
*/
private fun runTest(src: GridMigrationData, dst: GridMigrationData, target: GridMigrationData) {
migrate(src, dst)
@@ -162,6 +169,7 @@
}
validateDb(dst)
compare(dst, target, src)
+ verify(modelDelegate).gridMigrationComplete(src.gridState, dst.gridState)
}
// Copying the src db for all tests.
diff --git a/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
index 460ffc4..3e3e643 100644
--- a/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
@@ -51,7 +51,6 @@
@Test
@PortraitLandscape
public void testDragIcon() throws Throwable {
- mLauncher.enableDebugTracing(); // b/289161193
commitTransactionAndLoadHome(new FavoriteItemsTransaction(mTargetContext));
waitForLauncherCondition("Workspace didn't finish loading", l -> !l.isWorkspaceLoading());
@@ -72,7 +71,6 @@
TestUtil.DEFAULT_UI_TIMEOUT);
assertNotNull("Widget not found on the workspace", widget);
widget.launch(getAppPackageName());
- mLauncher.disableDebugTracing(); // b/289161193
}
/**