Snap for 13256841 from 500698152c20e4765585e83f7ff21fb23751d7d4 to 25Q2-release
Change-Id: Ia536b44ce15da1d9cf63eaab3abc9a35176c81ed
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index 4abfbbe..3aac1b6 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -29,8 +29,8 @@
launcher:hoverBorderColor="@color/materialColorPrimary">
<ViewStub
- android:id="@+id/task_content_view"
- android:inflatedId="@id/task_content_view"
+ android:id="@+id/snapshot"
+ android:inflatedId="@id/snapshot"
android:layout_width="match_parent"
android:layout_height="match_parent" />
diff --git a/quickstep/res/layout/task_content_view.xml b/quickstep/res/layout/task_content_view.xml
deleted file mode 100644
index 9055ccd..0000000
--- a/quickstep/res/layout/task_content_view.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?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.task.thumbnail.TaskContentView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/task_content_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical" />
\ No newline at end of file
diff --git a/quickstep/res/layout/task_grouped.xml b/quickstep/res/layout/task_grouped.xml
index a7c4856..3e6f5ed 100644
--- a/quickstep/res/layout/task_grouped.xml
+++ b/quickstep/res/layout/task_grouped.xml
@@ -34,14 +34,14 @@
launcher:hoverBorderColor="@color/materialColorPrimary">
<ViewStub
- android:id="@+id/task_content_view"
- android:inflatedId="@id/task_content_view"
+ android:id="@+id/snapshot"
+ android:inflatedId="@id/snapshot"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ViewStub
- android:id="@+id/bottomright_task_content_view"
- android:inflatedId="@id/bottomright_task_content_view"
+ android:id="@+id/bottomright_snapshot"
+ android:inflatedId="@id/bottomright_snapshot"
android:layout_width="match_parent"
android:layout_height="match_parent" />
diff --git a/quickstep/res/layout/task_header_view.xml b/quickstep/res/layout/task_header_view.xml
deleted file mode 100644
index 849153f..0000000
--- a/quickstep/res/layout/task_header_view.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?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.TaskHeaderView xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/task_header_view"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingTop="@dimen/task_thumbnail_header_padding_top_bottom"
- android:paddingBottom="@dimen/task_thumbnail_header_padding_top_bottom"
- android:paddingStart="@dimen/task_thumbnail_header_padding_start_end"
- android:paddingEnd="@dimen/task_thumbnail_header_padding_start_end"
- android:background="@drawable/task_thumbnail_header_bg">
- <ImageView
- android:id="@+id/header_app_icon"
- 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:layout_marginEnd="@dimen/task_thumbnail_header_margin_between_views"
- android:contentDescription="@string/header_app_icon_description"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
- <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:maxLines="1"
- android:text="@string/header_default_app_title"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toEndOf="@id/header_app_icon"
- app:layout_constraintTop_toTopOf="parent" />
- <ImageButton
- android:id="@+id/header_close_button"
- 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:layout_marginEnd="@dimen/task_thumbnail_header_margin_between_views"
- android:background="@null"
- android:contentDescription="@string/header_close_icon_description"
- android:src="@drawable/task_header_close_button"
- android:tint="@android:color/darker_gray"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintHorizontal_bias="1"
- app:layout_constraintStart_toEndOf="@id/header_app_title"
- app:layout_constraintTop_toTopOf="parent" />
-</com.android.quickstep.views.TaskHeaderView>
diff --git a/quickstep/res/layout/task_thumbnail.xml b/quickstep/res/layout/task_thumbnail.xml
index 8280e13..3b96615 100644
--- a/quickstep/res/layout/task_thumbnail.xml
+++ b/quickstep/res/layout/task_thumbnail.xml
@@ -17,8 +17,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/snapshot"
android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1" >
+ android:layout_height="match_parent" >
<com.android.quickstep.views.FixedSizeImageView
android:id="@+id/task_thumbnail"
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..70e4a42
--- /dev/null
+++ b/quickstep/res/layout/task_thumbnail_view_header.xml
@@ -0,0 +1,72 @@
+<?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:id="@+id/task_thumbnail_view_header"
+ 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"
+ android:background="@null"
+ 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 cb3c446..86d44c9 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -82,9 +82,9 @@
<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_padding_top_bottom">6dp</dimen>
- <dimen name="task_thumbnail_header_padding_start_end">12dp</dimen>
- <dimen name="task_thumbnail_header_margin_between_views">8dp</dimen>
+ <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>
diff --git a/quickstep/src/com/android/launcher3/taskbar/BarsLocationAnimatorHelper.kt b/quickstep/src/com/android/launcher3/taskbar/BarsLocationAnimatorHelper.kt
index b8060e1..ad847b4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/BarsLocationAnimatorHelper.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/BarsLocationAnimatorHelper.kt
@@ -20,6 +20,7 @@
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.animation.ValueAnimator
+import android.content.Context
import android.view.View
import androidx.dynamicanimation.animation.SpringForce
import com.android.app.animation.Interpolators
@@ -30,11 +31,10 @@
/** Animator helper that creates bars animators. */
object BarsLocationAnimatorHelper {
-
- private const val FADE_OUT_ANIM_ALPHA_DURATION_MS: Long = 50L
- private const val FADE_OUT_ANIM_ALPHA_DELAY_MS: Long = 50L
- private const val FADE_OUT_ANIM_POSITION_DURATION_MS: Long = 100L
- private const val FADE_IN_ANIM_ALPHA_DURATION_MS: Long = 100L
+ const val FADE_OUT_ANIM_ALPHA_DURATION_MS: Long = 50L
+ const val FADE_OUT_ANIM_ALPHA_DELAY_MS: Long = 50L
+ const val FADE_OUT_ANIM_POSITION_DURATION_MS: Long = 100L
+ const val FADE_IN_ANIM_ALPHA_DURATION_MS: Long = 100L
// Use STIFFNESS_MEDIUMLOW which is not defined in the API constants
private const val FADE_IN_ANIM_POSITION_SPRING_STIFFNESS: Float = 400f
@@ -45,13 +45,13 @@
// During fade in animation we shift the bubble bar 1/60th of the screen width
private const val FADE_IN_ANIM_POSITION_SHIFT: Float = 1 / 60f
- private val View.screenWidth: Int
+ private val Context.screenWidth: Int
get() = resources.displayMetrics.widthPixels
- private val View.outShift: Float
+ val Context.outShift: Float
get() = screenWidth * FADE_OUT_ANIM_POSITION_SHIFT
- private val View.inShiftX: Float
+ val Context.inShiftX: Float
get() = screenWidth * FADE_IN_ANIM_POSITION_SHIFT
/**
@@ -108,7 +108,7 @@
targetViewAlphaAnim: ObjectAnimator,
bubbleBarView: View,
): Animator {
- val shift: Float = bubbleBarView.outShift
+ val shift: Float = bubbleBarView.context.outShift
val onLeft = newLocation.isOnLeft(bubbleBarView.isLayoutRtl)
val startTx: Float
@@ -132,15 +132,19 @@
return createLocationInAnimator(startTx, finalTx, targetViewAlphaAnim, bubbleBarView)
}
- /** Creates an animator for the bubble bar view out part. */
+ /**
+ * Creates an animator for the bubble bar view out part.
+ *
+ * @param targetLocation the location bubble bar should animate to.
+ */
@JvmStatic
fun getBubbleBarLocationOutAnimator(
bubbleBarView: View,
- bubbleBarLocation: BubbleBarLocation,
+ targetLocation: BubbleBarLocation,
targetViewAlphaAnim: ObjectAnimator,
): Animator {
- val onLeft = bubbleBarLocation.isOnLeft(bubbleBarView.isLayoutRtl)
- val shift = bubbleBarView.outShift
+ val onLeft = targetLocation.isOnLeft(bubbleBarView.isLayoutRtl)
+ val shift = bubbleBarView.context.outShift
val finalTx = bubbleBarView.translationX + (if (onLeft) -shift else shift)
return this.createLocationOutAnimator(finalTx, targetViewAlphaAnim, bubbleBarView)
}
@@ -152,7 +156,7 @@
navButtonsView: View,
navBarTargetTranslationX: Float,
): Animator {
- val outShift: Float = navButtonsView.outShift
+ val outShift: Float = navButtonsView.context.outShift
val isNavBarOnRight: Boolean = location.isOnLeft(navButtonsView.isLayoutRtl)
val finalOutTx =
navButtonsView.translationX + (if (isNavBarOnRight) outShift else -outShift)
@@ -162,7 +166,7 @@
ObjectAnimator.ofFloat(navButtonsView, LauncherAnimUtils.VIEW_ALPHA, 0f),
navButtonsView,
)
- val inShift: Float = navButtonsView.inShiftX
+ val inShift: Float = navButtonsView.context.inShiftX
val inStartX = navBarTargetTranslationX + (if (isNavBarOnRight) -inShift else inShift)
val fadeIn: Animator =
createLocationInAnimator(
diff --git a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
index 7d82fc4..5b1e859 100644
--- a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
@@ -146,7 +146,8 @@
private boolean isIn3pHomeOrRecents() {
TopTaskTracker.CachedTaskInfo topTask = TopTaskTracker.INSTANCE
- .get(mControllers.taskbarActivityContext).getCachedTopTask(true);
+ .get(mControllers.taskbarActivityContext).getCachedTopTask(true,
+ mRecentsContainer.asContext().getDisplayId());
return topTask.isHomeTask() || topTask.isRecentsTask();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
index ab147bb..515cfe2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
@@ -47,7 +47,6 @@
import com.android.app.animation.Interpolators;
import com.android.internal.jank.Cuj;
-import com.android.launcher3.Flags;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatedFloat;
@@ -180,17 +179,6 @@
mIsRtl = Utilities.isRtl(resources);
- if (Flags.taskbarOverflow()) {
- initializeScrollArrows();
-
- if (mIsRtl) {
- mStartScrollArrow.setContentDescription(
- resources.getString(R.string.quick_switch_scroll_arrow_right));
- mEndScrollArrow.setContentDescription(
- resources.getString(R.string.quick_switch_scroll_arrow_left));
- }
- }
-
TypefaceUtils.setTypeface(
mNoRecentItemsPane.findViewById(R.id.no_recent_items_text),
FontFamily.GSF_LABEL_LARGE);
@@ -359,9 +347,21 @@
});
}
- private void initializeScrollArrows() {
+
+ void enableScrollArrowSupport() {
+ if (mSupportsScrollArrows) {
+ return;
+ }
mSupportsScrollArrows = true;
+ if (mIsRtl) {
+ mStartScrollArrow.setContentDescription(
+ getResources().getString(R.string.quick_switch_scroll_arrow_right));
+ mEndScrollArrow.setContentDescription(
+ getResources().getString(R.string.quick_switch_scroll_arrow_left));
+ }
+
+
mStartScrollArrow.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
index b5f2532..a312d5f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
@@ -34,6 +34,7 @@
import com.android.internal.jank.Cuj;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorListeners;
@@ -119,6 +120,10 @@
mWasDesktopTaskFilteredOut = wasDesktopTaskFilteredOut;
mWasOpenedFromTaskbar = wasOpenedFromTaskbar;
+ if (Flags.taskbarOverflow() && wasOpenedFromTaskbar) {
+ mKeyboardQuickSwitchView.enableScrollArrowSupport();
+ }
+
mKeyboardQuickSwitchView.applyLoadPlan(
mOverlayContext,
tasks,
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index f03b7d1..cf3b952 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -693,7 +693,11 @@
if (mBackButton == null) {
return;
}
- setupBackButtonAccessibility(mBackButton, accessibilityDelegate);
+ if (predictiveBackThreeButtonNav()) {
+ setupBackButtonAccessibility(mBackButton, accessibilityDelegate);
+ } else {
+ mBackButton.setAccessibilityDelegate(accessibilityDelegate);
+ }
}
public void setWallpaperVisible(boolean isVisible) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index 142f458..4b977e0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -510,6 +510,8 @@
} else {
// This will take care of calling maybeOnDragEnd() after the animation
animateGlobalDragViewToOriginalPosition(btv, dragEvent);
+ //TODO(b/399678274): hide drop target in shell
+ notifyBubbleBarItemDragCanceled();
}
mActivity.getDragLayer().setOnDragListener(null);
@@ -536,10 +538,10 @@
mControllers.taskbarAutohideSuspendController.updateFlag(
TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING, false);
mActivity.onDragEnd();
+ // If an item is dropped on the bubble bar, the bubble bar handles the drop,
+ // so it should not collapse along with the taskbar.
+ boolean droppedOnBubbleBar = notifyBubbleBarItemDropped();
if (mReturnAnimator == null) {
- // If an item is dropped on the bubble bar, the bubble bar handles the drop,
- // so it should not collapse along with the taskbar.
- boolean droppedOnBubbleBar = notifyBubbleBarItemDropped();
// Upon successful drag, immediately stash taskbar.
// Note, this must be done last to ensure no AutohideSuspendFlags are active, as
// that will prevent us from stashing until the timeout.
@@ -563,12 +565,17 @@
BubbleBarViewController bubbleBarViewController = bc.bubbleBarViewController;
boolean showingDropTarget = bubbleBarViewController.isShowingDropTarget();
if (showingDropTarget) {
- bubbleBarViewController.onItemDroppedInBubbleBarDragZone();
+ bubbleBarViewController.onItemDragCompleted();
}
return showingDropTarget;
}).orElse(false);
}
+ private void notifyBubbleBarItemDragCanceled() {
+ mControllers.bubbleControllers.ifPresent(bc ->
+ bc.bubbleBarViewController.onItemDraggedOutsideBubbleBarDropZone());
+ }
+
@Override
protected void endDrag() {
if (mDisallowGlobalDrag && !mIsDropHandledByDropTarget) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index f342fa5..cc6cc64 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -348,13 +348,17 @@
controllers.bubbleControllers.isPresent &&
controllers.bubbleControllers.get().bubbleBarViewController.isBubbleBarVisible()
var insetsIsTouchableRegion = true
+ // Prevents the taskbar from taking touches and conflicting with setup wizard
if (
context.isPhoneButtonNavMode &&
+ context.isUserSetupComplete &&
(!controllers.navbarButtonsViewController.isImeVisible ||
!controllers.navbarButtonsViewController.isImeRenderingNavButtons)
) {
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME)
insetsIsTouchableRegion = false
+ debugTouchableRegion.lastSetTouchableReason =
+ "Phone button nav mode: Fullscreen touchable, IME not affecting nav buttons"
} else if (context.dragLayer.alpha < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) {
// Let touches pass through us.
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index c3bc7d9..f3cbdb5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -17,8 +17,8 @@
import static android.content.Context.RECEIVER_EXPORTED;
import static android.content.Context.RECEIVER_NOT_EXPORTED;
-import static android.content.pm.PackageManager.FEATURE_PC;
import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
+import static android.content.pm.PackageManager.FEATURE_PC;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
@@ -82,6 +82,8 @@
import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.quickstep.AllAppsActionManager;
import com.android.quickstep.RecentsActivity;
+import com.android.quickstep.SystemDecorationChangeObserver;
+import com.android.quickstep.SystemDecorationChangeObserver.DisplayDecorationListener;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.fallback.window.RecentsDisplayModel;
import com.android.quickstep.fallback.window.RecentsWindowFlags;
@@ -107,7 +109,7 @@
/**
* Class to manage taskbar lifecycle
*/
-public class TaskbarManager {
+public class TaskbarManager implements DisplayDecorationListener {
private static final String TAG = "TaskbarManager";
private static final boolean DEBUG = false;
private static final int TASKBAR_DESTROY_DURATION = 100;
@@ -138,6 +140,7 @@
Settings.Secure.NAV_BAR_KIDS_MODE);
private final Context mBaseContext;
+ private final int mPrimaryDisplayId;
private final TaskbarNavButtonCallbacks mNavCallbacks;
// TODO: Remove this during the connected displays lifecycle refactor.
private final Context mPrimaryWindowContext;
@@ -434,26 +437,28 @@
TaskbarNavButtonCallbacks navCallbacks,
RecentsDisplayModel recentsDisplayModel) {
mBaseContext = context;
+ mPrimaryDisplayId = mBaseContext.getDisplayId();
mAllAppsActionManager = allAppsActionManager;
mNavCallbacks = navCallbacks;
mRecentsDisplayModel = recentsDisplayModel;
// Set up primary display.
- int primaryDisplayId = getDefaultDisplayId();
debugPrimaryTaskbar("TaskbarManager constructor");
- mPrimaryWindowContext = createWindowContext(primaryDisplayId);
+ mPrimaryWindowContext = createWindowContext(getDefaultDisplayId());
mPrimaryWindowManager = mPrimaryWindowContext.getSystemService(WindowManager.class);
DesktopVisibilityController.INSTANCE.get(
mPrimaryWindowContext).registerTaskbarDesktopModeListener(
mTaskbarDesktopModeListener);
- createTaskbarRootLayout(primaryDisplayId);
- createNavButtonController(primaryDisplayId);
- createAndRegisterComponentCallbacks(primaryDisplayId);
+ createTaskbarRootLayout(getDefaultDisplayId());
+ createNavButtonController(getDefaultDisplayId());
+ createAndRegisterComponentCallbacks(getDefaultDisplayId());
SettingsCache.INSTANCE.get(mPrimaryWindowContext)
.register(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
SettingsCache.INSTANCE.get(mPrimaryWindowContext)
.register(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
+ SystemDecorationChangeObserver.getINSTANCE().get(mPrimaryWindowContext)
+ .registerDisplayDecorationListener(this);
mShutdownReceiver =
new SimpleBroadcastReceiver(
mPrimaryWindowContext, UI_HELPER_EXECUTOR, i -> destroyAllTaskbars());
@@ -957,6 +962,7 @@
* Signal from SysUI indicating that a non-mirroring display was just connected to the
* primary device or a previously mirroring display is switched to extended mode.
*/
+ @Override
public void onDisplayAddSystemDecorations(int displayId) {
debugTaskbarManager("onDisplayAddSystemDecorations: ", displayId);
Display display = getDisplay(displayId);
@@ -1020,6 +1026,7 @@
* Signal from SysUI indicating that a previously connected non-mirroring display was just
* removed from the primary device.
*/
+ @Override
public void onDisplayRemoved(int displayId) {
debugTaskbarManager("onDisplayRemoved: ", displayId);
if (!DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue() || isDefaultDisplay(
@@ -1059,6 +1066,7 @@
/**
* Signal from SysUI indicating that system decorations should be removed from the display.
*/
+ @Override
public void onDisplayRemoveSystemDecorations(int displayId) {
// The display mirroring starts. The handling logic is the same as when removing a
// display.
@@ -1102,6 +1110,8 @@
.unregister(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
SettingsCache.INSTANCE.get(mPrimaryWindowContext)
.unregister(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
+ SystemDecorationChangeObserver.getINSTANCE().get(mPrimaryWindowContext)
+ .unregisterDisplayDecorationListener(this);
debugPrimaryTaskbar("destroy: unregistering component callbacks");
removeAndUnregisterComponentCallbacks(getDefaultDisplayId());
mShutdownReceiver.unregisterReceiverSafely();
@@ -1669,7 +1679,7 @@
}
private int getDefaultDisplayId() {
- return mBaseContext.getDisplayId();
+ return mPrimaryDisplayId;
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacksFactory.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacksFactory.kt
index 17da533..6e94889 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacksFactory.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacksFactory.kt
@@ -42,7 +42,8 @@
if (contextualSearchInvoked) {
val runningPackage =
TopTaskTracker.INSTANCE[activity].getCachedTopTask(
- /* filterOnlyVisibleRecents */ true
+ /* filterOnlyVisibleRecents */ true,
+ activity.display.displayId,
)
.getPackageName()
activity.statsLogManager
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 6340bb1..f15ef8c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -952,7 +952,7 @@
// or fade in while already in in-app state.
Interpolator interpolator = mIsHotseatIconOnTopWhenAligned ? LINEAR : FINAL_FRAME;
- int offsetY = launcherDp.getTaskbarOffsetY();
+ int offsetY = taskbarDp.getTaskbarOffsetY();
setter.setFloat(mTaskbarIconTranslationYForHome, VALUE, -offsetY, interpolator);
setter.setFloat(mTaskbarNavButtonTranslationY, VALUE, -offsetY, interpolator);
setter.setFloat(mTaskbarNavButtonTranslationYForInAppDisplay, VALUE, offsetY, interpolator);
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 1abef8a..6380c23 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -18,6 +18,7 @@
import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
@@ -560,30 +561,52 @@
// First animator hides the bar.
// After it completes, bubble positions in the bar and arrow position is updated.
// Second animator is started to show the bar.
- ObjectAnimator alphaOutAnim = ObjectAnimator.ofFloat(
- this, getLocationAnimAlphaProperty(), 0f);
- mBubbleBarLocationAnimator = BarsLocationAnimatorHelper.getBubbleBarLocationOutAnimator(
- this,
- bubbleBarLocation,
- alphaOutAnim);
+ mBubbleBarLocationAnimator = animateToBubbleBarLocationOut(bubbleBarLocation);
mBubbleBarLocationAnimator.addListener(AnimatorListeners.forEndCallback(() -> {
- updateBubblesLayoutProperties(bubbleBarLocation);
- mBubbleBarBackground.setAnchorLeft(bubbleBarLocation.isOnLeft(isLayoutRtl()));
- ObjectAnimator alphaInAnim = ObjectAnimator.ofFloat(BubbleBarView.this,
- getLocationAnimAlphaProperty(), 1f);
-
// Animate it in
- mBubbleBarLocationAnimator = BarsLocationAnimatorHelper.getBubbleBarLocationInAnimator(
- bubbleBarLocation,
- mBubbleBarLocation,
- getDistanceFromOtherSide(),
- alphaInAnim,
- BubbleBarView.this);
+ mBubbleBarLocationAnimator = animateToBubbleBarLocationIn(mBubbleBarLocation,
+ bubbleBarLocation);
mBubbleBarLocationAnimator.start();
}));
mBubbleBarLocationAnimator.start();
}
+ /** Creates animator for animating bubble bar in. */
+ public Animator animateToBubbleBarLocationIn(BubbleBarLocation fromLocation,
+ BubbleBarLocation toLocation) {
+ updateBubblesLayoutProperties(toLocation);
+ mBubbleBarBackground.setAnchorLeft(toLocation.isOnLeft(isLayoutRtl()));
+ ObjectAnimator alphaInAnim = ObjectAnimator.ofFloat(BubbleBarView.this,
+ getLocationAnimAlphaProperty(), 1f);
+ return BarsLocationAnimatorHelper.getBubbleBarLocationInAnimator(toLocation, fromLocation,
+ getDistanceFromOtherSide(), alphaInAnim, this);
+ }
+
+ /**
+ * Creates animator for animating bubble bar out.
+ *
+ * @param targetLocation the location bubble br should animate to.
+ */
+ public Animator animateToBubbleBarLocationOut(BubbleBarLocation targetLocation) {
+ ObjectAnimator alphaOutAnim = ObjectAnimator.ofFloat(
+ this, getLocationAnimAlphaProperty(), 0f);
+ Animator outAnimation = BarsLocationAnimatorHelper.getBubbleBarLocationOutAnimator(
+ this,
+ targetLocation,
+ alphaOutAnim);
+ outAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
+ // need to restore the original bar view state in case icon is dropped to the bubble
+ // bar original location
+ updateBubblesLayoutProperties(targetLocation);
+ mBubbleBarBackground.setAnchorLeft(targetLocation.isOnLeft(isLayoutRtl()));
+ setTranslationX(0f);
+ }
+ });
+ return outAnimation;
+ }
+
/**
* Get property that can be used to animate the alpha value for the bar.
* When a bubble is being dragged, uses {@link #BUBBLE_DRAG_ALPHA}.
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 9fb283c..ce4a14f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -194,9 +194,11 @@
private boolean mHiddenForStashed;
private boolean mShouldShowEducation;
public boolean mOverflowAdded;
- private boolean mIsLocationUpdatedForDropTarget = false;
private boolean mWasStashedBeforeEnteringBubbleDragZone = false;
+ /** This field is used solely to track the bubble bar location prior to the start of the drag */
+ private @Nullable BubbleBarLocation mBubbleBarDragLocation;
+
private BubbleBarViewAnimator mBubbleBarViewAnimator;
private final FrameLayout mBubbleBarContainer;
private BubbleBarFlyoutController mBubbleBarFlyoutController;
@@ -364,7 +366,7 @@
@Override
public boolean isOnLeft() {
boolean shouldRevertLocation =
- mBarView.isShowingDropTarget() && mIsLocationUpdatedForDropTarget;
+ mBarView.isShowingDropTarget() && isLocationUpdatedForDropTarget();
boolean isOnLeft = mBarView.getBubbleBarLocation().isOnLeft(mBarView.isLayoutRtl());
return shouldRevertLocation != isOnLeft;
}
@@ -616,6 +618,17 @@
mBarView.animateToBubbleBarLocation(bubbleBarLocation);
}
+ /** Return animator for animating bubble bar in. */
+ public Animator animateBubbleBarLocationIn(BubbleBarLocation fromLocation,
+ BubbleBarLocation toLocation) {
+ return mBarView.animateToBubbleBarLocationIn(fromLocation, toLocation);
+ }
+
+ /** Return animator for animating bubble bar out. */
+ public Animator animateBubbleBarLocationOut(BubbleBarLocation toLocation) {
+ return mBarView.animateToBubbleBarLocationOut(toLocation);
+ }
+
/** Returns whether the Bubble Bar is currently displaying a drop target. */
public boolean isShowingDropTarget() {
return mBarView.isShowingDropTarget();
@@ -628,25 +641,18 @@
* updated.
*/
public void onDragItemOverBubbleBarDragZone(@NonNull BubbleBarLocation bubbleBarLocation) {
- Log.w("BBAnimation", "onDragItemOverBubbleBarDragZone()");
+ mBubbleBarDragLocation = bubbleBarLocation;
mBarView.showDropTarget(/* isDropTarget = */ true);
- boolean isRtl = mBarView.isLayoutRtl();
- mIsLocationUpdatedForDropTarget = getBubbleBarLocation().isOnLeft(isRtl)
- != bubbleBarLocation.isOnLeft(isRtl);
mWasStashedBeforeEnteringBubbleDragZone = hasBubbles()
&& mBubbleStashController.isStashed();
if (mWasStashedBeforeEnteringBubbleDragZone) {
- if (mIsLocationUpdatedForDropTarget) {
- // bubble bar is stashed an location updated
- //TODO(b/399678274) add animation
- mBubbleStashController.showBubbleBarImmediate();
- animateBubbleBarLocation(bubbleBarLocation);
- } else {
- // bubble bar is stashed and location the same - just un-stash
- mBubbleStashController.showBubbleBar(/* expandBubbles = */ false);
- }
+ // bubble bar is stashed - un-stash at drag location
+ mBubbleStashController.showBubbleBarAtLocation(
+ /* fromLocation = */ getBubbleBarLocation(),
+ /* toLocation = */ mBubbleBarDragLocation
+ );
} else if (hasBubbles()) {
- if (mIsLocationUpdatedForDropTarget) {
+ if (isLocationUpdatedForDropTarget()) {
// bubble bar has bubbles and location is changed - animate bar to the opposite side
animateBubbleBarLocation(bubbleBarLocation);
}
@@ -661,7 +667,12 @@
* {@link #onDragItemOverBubbleBarDragZone}}.
*/
public boolean isLocationUpdatedForDropTarget() {
- return mIsLocationUpdatedForDropTarget;
+ if (mBubbleBarDragLocation == null) {
+ return false;
+ }
+ boolean isRtl = mBarView.isLayoutRtl();
+ return getBubbleBarLocation().isOnLeft(isRtl)
+ != mBubbleBarDragLocation.isOnLeft(isRtl);
}
/**
@@ -671,39 +682,34 @@
* mode and reset the value returned from {@link #isLocationUpdatedForDropTarget()} to false.
*/
public void onItemDraggedOutsideBubbleBarDropZone() {
- Log.w("BBAnimation", "onItemDraggedOutsideBubbleBarDropZone()");
- mBarView.showDropTarget(/* isDropTarget = */ false);
- if (mWasStashedBeforeEnteringBubbleDragZone) {
- if (mIsLocationUpdatedForDropTarget) {
- // bubble bar was stashed and location updated
- //TODO(b/399678274) add animation
- animateBubbleBarLocation(getBubbleBarLocation());
- mBubbleStashController.stashBubbleBarImmediate();
- } else {
- // bubble bar was stashed and location the same - just stash it back
- mBubbleStashController.stashBubbleBar();
- }
+ if (!isShowingDropTarget()) {
+ return;
+ }
+ if (mWasStashedBeforeEnteringBubbleDragZone && mBubbleBarDragLocation != null) {
+ // bubble bar was stashed - stash at original location
+ mBubbleStashController.stashBubbleBarToLocation(
+ /* fromLocation = */ mBubbleBarDragLocation,
+ /* toLocation = */ getBubbleBarLocation()
+ );
} else if (hasBubbles()) {
- if (mIsLocationUpdatedForDropTarget) {
- // bubble bar has bubbles and location was changed - return to the original location
+ if (isLocationUpdatedForDropTarget()) {
+ // bubble bar has bubbles and location was changed - return to the original
+ // location
animateBubbleBarLocation(getBubbleBarLocation());
}
}
- mBubbleBarPinController.hideDropTarget();
- mIsLocationUpdatedForDropTarget = false;
- mWasStashedBeforeEnteringBubbleDragZone = false;
+ onItemDragCompleted();
}
/**
* Notifies the controller that the drag has completed over the Bubble Bar drop zone.
* The controller will hide the drop target if there are no bubbles and exit drop target mode.
*/
- public void onItemDroppedInBubbleBarDragZone() {
- Log.w("BBAnimation", "onItemDroppedInBubbleBarDragZone()");
+ public void onItemDragCompleted() {
mBarView.showDropTarget(/* isDropTarget = */ false);
mBubbleBarPinController.hideDropTarget();
- mIsLocationUpdatedForDropTarget = false;
mWasStashedBeforeEnteringBubbleDragZone = false;
+ mBubbleBarDragLocation = null;
}
/**
@@ -842,6 +848,7 @@
boolean hiddenForStashedAndNotAnimating =
mHiddenForStashed && !mBubbleBarViewAnimator.isAnimating();
if (mHiddenForSysui || mHiddenForNoBubbles || hiddenForStashedAndNotAnimating) {
+ //TODO(b/404870188) this visibility change cause search view drag misbehavior
mBarView.setVisibility(INVISIBLE);
} else {
mBarView.setVisibility(VISIBLE);
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
index 3640c3b..ec540e0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
@@ -272,6 +272,11 @@
updateTranslationY();
}
+ /** Sets translation X for stash handle. */
+ public void setTranslationX(float translationX) {
+ mStashedHandleView.setTranslationX(translationX);
+ }
+
private void updateTranslationY() {
mStashedHandleView.setTranslationY(mTranslationForSwipeY + mTranslationForStashY);
}
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 fec1eaf..ec272ac 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt
@@ -131,6 +131,12 @@
*/
fun stashBubbleBar()
+ /**
+ * Animates the bubble bar to the handle at provided location. Does not update bubble bar
+ * location.
+ */
+ fun stashBubbleBarToLocation(fromLocation: BubbleBarLocation, toLocation: BubbleBarLocation) {}
+
/** Shows the bubble bar, and expands bubbles depending on [expandBubbles]. */
fun showBubbleBar(expandBubbles: Boolean) {
showBubbleBar(expandBubbles = expandBubbles, bubbleBarGesture = false)
@@ -144,6 +150,9 @@
*/
fun showBubbleBar(expandBubbles: Boolean, bubbleBarGesture: Boolean)
+ /** Animates the bubble bar at the provided location. Does not update bubble bar location. */
+ fun showBubbleBarAtLocation(fromLocation: BubbleBarLocation, toLocation: BubbleBarLocation) {}
+
// TODO(b/354218264): Move to BubbleBarViewAnimator
/**
* The difference on the Y axis between the center of the handle and the center of the bubble
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 9c148e2..82bb071 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
@@ -18,6 +18,7 @@
import android.animation.Animator
import android.animation.AnimatorSet
+import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Rect
import android.view.MotionEvent
@@ -31,6 +32,12 @@
import com.android.launcher3.R
import com.android.launcher3.anim.AnimatedFloat
import com.android.launcher3.anim.SpringAnimationBuilder
+import com.android.launcher3.taskbar.BarsLocationAnimatorHelper.FADE_IN_ANIM_ALPHA_DURATION_MS
+import com.android.launcher3.taskbar.BarsLocationAnimatorHelper.FADE_OUT_ANIM_ALPHA_DELAY_MS
+import com.android.launcher3.taskbar.BarsLocationAnimatorHelper.FADE_OUT_ANIM_ALPHA_DURATION_MS
+import com.android.launcher3.taskbar.BarsLocationAnimatorHelper.FADE_OUT_ANIM_POSITION_DURATION_MS
+import com.android.launcher3.taskbar.BarsLocationAnimatorHelper.inShiftX
+import com.android.launcher3.taskbar.BarsLocationAnimatorHelper.outShift
import com.android.launcher3.taskbar.TaskbarInsetsController
import com.android.launcher3.taskbar.TaskbarStashController.TASKBAR_STASH_ALPHA_START_DELAY
import com.android.launcher3.taskbar.TaskbarStashController.TRANSIENT_TASKBAR_STASH_ALPHA_DURATION
@@ -44,6 +51,7 @@
import com.android.launcher3.util.MultiPropertyFactory
import com.android.wm.shell.shared.animation.PhysicsAnimator
import com.android.wm.shell.shared.bubbles.BubbleBarLocation
+import com.android.wm.shell.shared.bubbles.ContextUtils.isRtl
import kotlin.math.max
class TransientBubbleStashController(
@@ -187,6 +195,11 @@
}
override fun showBubbleBarImmediate(bubbleBarTranslationY: Float) {
+ showBubbleBarImmediateVisually(bubbleBarTranslationY)
+ onIsStashedChanged()
+ }
+
+ private fun showBubbleBarImmediateVisually(bubbleBarTranslationY: Float) {
bubbleStashedHandleViewController?.setTranslationYForSwipe(0f)
stashHandleViewAlpha?.value = 0f
this.bubbleBarTranslationYAnimator.updateValue(bubbleBarTranslationY)
@@ -197,10 +210,14 @@
bubbleBarBackgroundScaleY.updateValue(1f)
isStashed = false
bubbleBarViewController.setHiddenForStashed(false)
- onIsStashedChanged()
}
override fun stashBubbleBarImmediate() {
+ stashBubbleBarImmediateVisually()
+ onIsStashedChanged()
+ }
+
+ private fun stashBubbleBarImmediateVisually() {
bubbleStashedHandleViewController?.setTranslationYForSwipe(0f)
stashHandleViewAlpha?.value = 1f
this.bubbleBarTranslationYAnimator.updateValue(getStashTranslation())
@@ -212,7 +229,6 @@
bubbleBarBackgroundScaleY.updateValue(getStashScaleY())
isStashed = true
bubbleBarViewController.setHiddenForStashed(true)
- onIsStashedChanged()
}
override fun getTouchableHeight(): Int =
@@ -247,6 +263,29 @@
updateStashedAndExpandedState(stash = true, expand = false)
}
+ override fun stashBubbleBarToLocation(
+ fromLocation: BubbleBarLocation,
+ toLocation: BubbleBarLocation,
+ ) {
+ if (fromLocation.isSameSideWith(toLocation)) {
+ updateStashedAndExpandedState(
+ stash = true,
+ expand = false,
+ updateTouchRegionOnEnd = false,
+ )
+ return
+ }
+ cancelAnimation()
+ animator =
+ AnimatorSet().apply {
+ playSequentially(
+ bubbleBarViewController.animateBubbleBarLocationOut(toLocation),
+ createHandleInAnimator(location = toLocation),
+ )
+ start()
+ }
+ }
+
override fun showBubbleBar(expandBubbles: Boolean, bubbleBarGesture: Boolean) {
updateStashedAndExpandedState(
stash = false,
@@ -255,6 +294,33 @@
)
}
+ override fun showBubbleBarAtLocation(
+ fromLocation: BubbleBarLocation,
+ toLocation: BubbleBarLocation,
+ ) {
+ if (fromLocation.isSameSideWith(toLocation)) {
+ updateStashedAndExpandedState(
+ stash = false,
+ expand = false,
+ updateTouchRegionOnEnd = false,
+ )
+ return
+ }
+ cancelAnimation()
+ val bubbleBarInAnimation =
+ bubbleBarViewController.animateBubbleBarLocationIn(fromLocation, toLocation).apply {
+ doOnStart { showBubbleBarImmediateVisually(bubbleBarTranslationY) }
+ }
+ animator =
+ AnimatorSet().apply {
+ playSequentially(
+ createHandleOutAnimator(location = toLocation),
+ bubbleBarInAnimation,
+ )
+ start()
+ }
+ }
+
override fun getDiffBetweenHandleAndBarCenters(): Float {
// the difference between the centers of the handle and the bubble bar is the difference
// between their distance from the bottom of the screen.
@@ -392,7 +458,7 @@
bubbleBarAlpha.value = 1f
}
animatorSet.doOnEnd {
- animator = null
+ cancelAnimation()
controllersAfterInitAction.runAfterInit {
if (isStashed) {
bubbleBarAlpha.value = 0f
@@ -486,6 +552,7 @@
stash: Boolean,
expand: Boolean,
bubbleBarGesture: Boolean = false,
+ updateTouchRegionOnEnd: Boolean = true,
) {
if (bubbleBarViewController.isHiddenForNoBubbles) {
// If there are no bubbles the bar and handle are invisible, nothing to do here.
@@ -498,11 +565,13 @@
// notify the view controller that the stash state is about to change so that it can
// cancel an ongoing animation if there is one.
bubbleBarViewController.onStashStateChanging()
- animator?.cancel()
+ cancelAnimation()
animator =
createStashAnimator(isStashed, BAR_STASH_DURATION).apply {
updateBarVisibility(isStashed)
- updateTouchRegionOnAnimationEnd()
+ if (updateTouchRegionOnEnd) {
+ updateTouchRegionOnAnimationEnd()
+ }
start()
}
}
@@ -512,6 +581,11 @@
}
}
+ private fun cancelAnimation() {
+ animator?.cancel()
+ animator = null
+ }
+
override fun getHandleViewAlpha(): MultiPropertyFactory<View>.MultiProperty? =
// only return handle alpha if the bubble bar is stashed and has bubbles
if (isStashed && bubbleBarViewController.hasBubbles()) {
@@ -534,6 +608,49 @@
return this
}
+ // TODO(b/399678274) add tests
+ private fun createHandleInAnimator(location: BubbleBarLocation): Animator? {
+ val stashHandleViewController = bubbleStashedHandleViewController ?: return null
+ val handleAlpha = stashHandleViewController.stashedHandleAlpha.get(0)
+ val shift = context.inShiftX
+ val startX = if (location.isOnLeft(context.isRtl)) shift else -shift
+ val handleTranslationX =
+ ValueAnimator.ofFloat(startX, 0f).apply {
+ addUpdateListener { v ->
+ stashHandleViewController.setTranslationX(v.animatedValue as Float)
+ }
+ duration = FADE_IN_ANIM_ALPHA_DURATION_MS
+ }
+ val handleAlphaAnimation =
+ handleAlpha.animateToValue(1f).apply { duration = FADE_IN_ANIM_ALPHA_DURATION_MS }
+ return AnimatorSet().apply {
+ playTogether(handleTranslationX, handleAlphaAnimation)
+ doOnStart { stashBubbleBarImmediateVisually() }
+ }
+ }
+
+ private fun createHandleOutAnimator(location: BubbleBarLocation): Animator? {
+ val stashHandleViewController = bubbleStashedHandleViewController ?: return null
+ val handleAlpha = stashHandleViewController.stashedHandleAlpha.get(0)
+ val shift = context.outShift
+ val endX = if (location.isOnLeft(context.isRtl)) -shift else shift
+ val handleTranslationX =
+ ValueAnimator.ofFloat(0f, endX).apply {
+ addUpdateListener { v ->
+ stashHandleViewController.setTranslationX(v.animatedValue as Float)
+ }
+ duration = FADE_OUT_ANIM_POSITION_DURATION_MS
+ // in case item dropped to the opposite side - need to clear translation
+ doOnEnd { stashHandleViewController.setTranslationX(0f) }
+ }
+ val handleAlphaAnimation =
+ handleAlpha.animateToValue(0f).apply {
+ duration = FADE_OUT_ANIM_ALPHA_DURATION_MS
+ startDelay = FADE_OUT_ANIM_ALPHA_DELAY_MS
+ }
+ return AnimatorSet().apply { playTogether(handleTranslationX, handleAlphaAnimation) }
+ }
+
private fun Animator.setBubbleBarPivotDuringAnim(pivotX: Float, pivotY: Float): Animator {
var initialPivotX = Float.NaN
var initialPivotY = Float.NaN
@@ -549,4 +666,9 @@
}
return this
}
+
+ private fun BubbleBarLocation.isSameSideWith(anotherLocation: BubbleBarLocation): Boolean {
+ val isRtl = context.isRtl
+ return this.isOnLeft(isRtl) == anotherLocation.isOnLeft(isRtl)
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/growth/ActionPerformers.kt b/quickstep/src/com/android/launcher3/taskbar/growth/ActionPerformers.kt
new file mode 100644
index 0000000..17c9509
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/growth/ActionPerformers.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.launcher3.taskbar.growth
+
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+
+object ActionPerformers {
+ fun interface DismissCallback {
+ fun invoke()
+ }
+
+ fun performActions(actions: List<Action>, context: Context, dismissCallback: DismissCallback) {
+ for (action in actions) {
+ performAction(action, context, dismissCallback)
+ }
+ }
+
+ private fun performAction(action: Action, context: Context, dismissCallback: DismissCallback) {
+ when (action) {
+ is Action.Dismiss -> {
+ // TODO: b/396239267 - Handle marking the campaign dismissed with dismissal
+ // retention.
+ dismissCallback.invoke()
+ }
+ is Action.OpenUrl -> openUrl(action.url, context)
+ // Handle other actions
+ }
+ }
+
+ fun openUrl(url: String, context: Context) {
+ val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ context.startActivity(intent)
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
index e487f9f..3712a76 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
@@ -27,7 +27,6 @@
import com.android.launcher3.DeviceProfile
import com.android.launcher3.R
import com.android.launcher3.Utilities
-import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter
/**
@@ -48,7 +47,7 @@
protected val startContextualContainer: ViewGroup,
protected val imeSwitcher: ImageView?,
protected val a11yButton: ImageView?,
- protected val space: Space?
+ protected val space: Space?,
) : NavButtonLayoutter {
protected val homeButton: ImageView? = navButtonContainer.findViewById(R.id.home)
protected val recentsButton: ImageView? = navButtonContainer.findViewById(R.id.recent_apps)
@@ -69,26 +68,34 @@
val params =
FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT
+ ViewGroup.LayoutParams.MATCH_PARENT,
)
params.gravity = Gravity.CENTER
return params
}
+ /**
+ * Adjusts the layout parameters of the nav bar container for setup in phone mode.
+ *
+ * @param nearestTouchFrameLayoutParams The layout parameters of the navButtonsView, which is
+ * the ViewGroup that contains start, end, nav button ViewGroups
+ * @param deviceProfile The device profile containing information about the device's
+ * configuration.
+ */
fun adjustForSetupInPhoneMode(
- navButtonsLayoutParams: FrameLayout.LayoutParams,
- navButtonsViewLayoutParams: FrameLayout.LayoutParams,
- deviceProfile: DeviceProfile
+ nearestTouchFrameLayoutParams: FrameLayout.LayoutParams,
+ deviceProfile: DeviceProfile,
) {
val phoneOrPortraitSetupMargin =
resources.getDimensionPixelSize(R.dimen.taskbar_contextual_button_suw_margin)
- navButtonsLayoutParams.marginStart = phoneOrPortraitSetupMargin
- navButtonsLayoutParams.bottomMargin =
+ nearestTouchFrameLayoutParams.marginStart = phoneOrPortraitSetupMargin
+ nearestTouchFrameLayoutParams.bottomMargin =
if (!deviceProfile.isLandscape) 0
else
phoneOrPortraitSetupMargin -
- resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size) / 2
- navButtonsViewLayoutParams.height =
+ resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size) / 2
+
+ nearestTouchFrameLayoutParams.height =
resources.getDimensionPixelSize(R.dimen.taskbar_contextual_button_suw_height)
}
@@ -97,7 +104,7 @@
buttonSize: Int,
barAxisMarginStart: Int,
barAxisMarginEnd: Int,
- gravity: Int
+ gravity: Int,
) {
val contextualContainerParams =
FrameLayout.LayoutParams(buttonSize, ViewGroup.LayoutParams.MATCH_PARENT)
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
index 2497fbb..a199dba 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
@@ -66,7 +66,7 @@
isInSetup: Boolean,
isThreeButtonNav: Boolean,
phoneMode: Boolean,
- @Rotation surfaceRotation: Int
+ @Rotation surfaceRotation: Int,
): NavButtonLayoutter {
val navButtonContainer =
navButtonsView.requireViewById<LinearLayout>(ID_END_NAV_BUTTONS)
@@ -77,6 +77,18 @@
val isPhoneNavMode = phoneMode && isThreeButtonNav
val isPhoneGestureMode = phoneMode && !isThreeButtonNav
return when {
+ isInSetup -> {
+ SetupNavLayoutter(
+ resources,
+ navButtonsView,
+ navButtonContainer,
+ endContextualContainer,
+ startContextualContainer,
+ imeSwitcher,
+ a11yButton,
+ space,
+ )
+ }
isPhoneNavMode -> {
if (!deviceProfile.isLandscape) {
navButtonsView.setIsVertical(false)
@@ -87,7 +99,7 @@
startContextualContainer,
imeSwitcher,
a11yButton,
- space
+ space,
)
} else if (surfaceRotation == ROTATION_90) {
navButtonsView.setIsVertical(true)
@@ -98,7 +110,7 @@
startContextualContainer,
imeSwitcher,
a11yButton,
- space
+ space,
)
} else {
navButtonsView.setIsVertical(true)
@@ -109,36 +121,23 @@
startContextualContainer,
imeSwitcher,
a11yButton,
- space
+ space,
)
}
}
isPhoneGestureMode -> {
PhoneGestureLayoutter(
resources,
- navButtonsView,
navButtonContainer,
endContextualContainer,
startContextualContainer,
imeSwitcher,
a11yButton,
- space
+ space,
)
}
deviceProfile.isTaskbarPresent -> {
return when {
- isInSetup -> {
- SetupNavLayoutter(
- resources,
- navButtonsView,
- navButtonContainer,
- endContextualContainer,
- startContextualContainer,
- imeSwitcher,
- a11yButton,
- space
- )
- }
isKidsMode -> {
KidsNavLayoutter(
resources,
@@ -147,7 +146,7 @@
startContextualContainer,
imeSwitcher,
a11yButton,
- space
+ space,
)
}
else ->
@@ -158,7 +157,7 @@
startContextualContainer,
imeSwitcher,
a11yButton,
- space
+ space,
)
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
index 390ec34..e0f2a22 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
@@ -17,25 +17,21 @@
package com.android.launcher3.taskbar.navbutton
import android.content.res.Resources
-import android.view.Gravity
import android.view.ViewGroup
-import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.Space
-import com.android.launcher3.DeviceProfile
import com.android.launcher3.taskbar.TaskbarActivityContext
/** Layoutter for showing gesture navigation on phone screen. No buttons here, no-op container */
class PhoneGestureLayoutter(
resources: Resources,
- navButtonsView: NearestTouchFrame,
navBarContainer: LinearLayout,
endContextualContainer: ViewGroup,
startContextualContainer: ViewGroup,
imeSwitcher: ImageView?,
a11yButton: ImageView?,
- space: Space?
+ space: Space?,
) :
AbstractNavButtonLayoutter(
resources,
@@ -44,33 +40,10 @@
startContextualContainer,
imeSwitcher,
a11yButton,
- space
+ space,
) {
- private val mNavButtonsView = navButtonsView
override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
- // TODO: look into if we should use SetupNavLayoutter instead.
- if (!context.isUserSetupComplete) {
- // Since setup wizard only has back button enabled, it looks strange to be
- // end-aligned, so start-align instead.
- val navButtonsLayoutParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams
- val navButtonsViewLayoutParams =
- mNavButtonsView.layoutParams as FrameLayout.LayoutParams
- val deviceProfile: DeviceProfile = context.deviceProfile
-
- navButtonsLayoutParams.marginEnd = 0
- navButtonsLayoutParams.gravity = Gravity.START
- context.setTaskbarWindowSize(context.setupWindowSize)
-
- adjustForSetupInPhoneMode(
- navButtonsLayoutParams,
- navButtonsViewLayoutParams,
- deviceProfile
- )
- mNavButtonsView.layoutParams = navButtonsViewLayoutParams
- navButtonContainer.layoutParams = navButtonsLayoutParams
- }
-
endContextualContainer.removeAllViews()
startContextualContainer.removeAllViews()
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
index e032430..eb3fdeb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
@@ -29,12 +29,15 @@
import com.android.launcher3.R
import com.android.launcher3.taskbar.TaskbarActivityContext
+const val SUW_THEME_SYSTEM_PROPERTY = "setupwizard.theme"
+const val GLIF_EXPRESSIVE_THEME = "glif_expressive"
+const val GLIF_EXPRESSIVE_LIGHT_THEME = "glif_expressive_light"
const val SQUARE_ASPECT_RATIO_BOTTOM_BOUND = 0.95
const val SQUARE_ASPECT_RATIO_UPPER_BOUND = 1.05
class SetupNavLayoutter(
resources: Resources,
- navButtonsView: NearestTouchFrame,
+ nearestTouchFrame: NearestTouchFrame,
navButtonContainer: LinearLayout,
endContextualContainer: ViewGroup,
startContextualContainer: ViewGroup,
@@ -51,17 +54,19 @@
a11yButton,
space,
) {
- private val mNavButtonsView = navButtonsView
+ // mNearestTouchFrame is a ViewGroup that contains start, end, nav button ViewGroups
+ private val mNearestTouchFrame = nearestTouchFrame
override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
- val SUWTheme = SystemProperties.get("setupwizard.theme", "")
- if (SUWTheme == "glif_expressive" || SUWTheme == "glif_expressive_light") {
+ val SUWTheme = SystemProperties.get(SUW_THEME_SYSTEM_PROPERTY, "")
+ if (SUWTheme == GLIF_EXPRESSIVE_THEME || SUWTheme == GLIF_EXPRESSIVE_LIGHT_THEME) {
return
}
// Since setup wizard only has back button enabled, it looks strange to be
// end-aligned, so start-align instead.
val navButtonsLayoutParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams
- val navButtonsViewLayoutParams = mNavButtonsView.layoutParams as FrameLayout.LayoutParams
+ val navButtonsOverallViewGroupLayoutParams =
+ mNearestTouchFrame.layoutParams as FrameLayout.LayoutParams
val deviceProfile: DeviceProfile = context.deviceProfile
navButtonsLayoutParams.marginEnd = 0
@@ -77,18 +82,14 @@
) {
navButtonsLayoutParams.marginStart =
resources.getDimensionPixelSize(R.dimen.taskbar_back_button_suw_start_margin)
- navButtonsViewLayoutParams.bottomMargin =
+ navButtonsOverallViewGroupLayoutParams.bottomMargin =
resources.getDimensionPixelSize(R.dimen.taskbar_back_button_suw_bottom_margin)
navButtonsLayoutParams.height =
resources.getDimensionPixelSize(R.dimen.taskbar_back_button_suw_height)
} else {
- adjustForSetupInPhoneMode(
- navButtonsLayoutParams,
- navButtonsViewLayoutParams,
- deviceProfile,
- )
+ adjustForSetupInPhoneMode(navButtonsOverallViewGroupLayoutParams, deviceProfile)
}
- mNavButtonsView.layoutParams = navButtonsViewLayoutParams
+ mNearestTouchFrame.layoutParams = navButtonsOverallViewGroupLayoutParams
navButtonContainer.layoutParams = navButtonsLayoutParams
endContextualContainer.removeAllViews()
diff --git a/quickstep/src/com/android/quickstep/DisplayModel.kt b/quickstep/src/com/android/quickstep/DisplayModel.kt
index 3de6fd0..0b8af40 100644
--- a/quickstep/src/com/android/quickstep/DisplayModel.kt
+++ b/quickstep/src/com/android/quickstep/DisplayModel.kt
@@ -23,10 +23,13 @@
import android.view.Display
import androidx.core.util.valueIterator
import com.android.quickstep.DisplayModel.DisplayResource
+import com.android.quickstep.SystemDecorationChangeObserver.Companion.INSTANCE
+import com.android.quickstep.SystemDecorationChangeObserver.DisplayDecorationListener
import java.io.PrintWriter
/** data model for managing resources with lifecycles that match that of the connected display */
-abstract class DisplayModel<RESOURCE_TYPE : DisplayResource>(val context: Context) {
+abstract class DisplayModel<RESOURCE_TYPE : DisplayResource>(val context: Context) :
+ DisplayDecorationListener {
companion object {
private const val TAG = "DisplayModel"
@@ -34,19 +37,20 @@
}
private val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
+ private var systemDecorationChangeObserver: SystemDecorationChangeObserver? = null
protected val displayResourceArray = SparseArray<RESOURCE_TYPE>()
- fun onDisplayAddSystemDecorations(displayId: Int) {
+ override fun onDisplayAddSystemDecorations(displayId: Int) {
if (DEBUG) Log.d(TAG, "onDisplayAdded: displayId=$displayId")
storeDisplayResource(displayId)
}
- fun onDisplayRemoved(displayId: Int) {
+ override fun onDisplayRemoved(displayId: Int) {
if (DEBUG) Log.d(TAG, "onDisplayRemoved: displayId=$displayId")
deleteDisplayResource(displayId)
}
- fun onDisplayRemoveSystemDecorations(displayId: Int) {
+ override fun onDisplayRemoveSystemDecorations(displayId: Int) {
if (DEBUG) Log.d(TAG, "onDisplayRemoveSystemDecorations: displayId=$displayId")
deleteDisplayResource(displayId)
}
@@ -54,12 +58,16 @@
protected abstract fun createDisplayResource(display: Display): RESOURCE_TYPE
protected fun initializeDisplays() {
+ systemDecorationChangeObserver = INSTANCE[context]
+ systemDecorationChangeObserver?.registerDisplayDecorationListener(this)
displayManager.displays
.filter { getDisplayResource(it.displayId) == null }
.forEach { storeDisplayResource(it.displayId) }
}
fun destroy() {
+ systemDecorationChangeObserver?.unregisterDisplayDecorationListener(this)
+ systemDecorationChangeObserver = null
displayResourceArray.valueIterator().forEach { displayResource ->
displayResource.cleanup()
}
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index faaade9..fa8e484 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -261,7 +261,8 @@
return launcher != null
&& launcher.getStateManager().getState() == OVERVIEW
&& launcher.isStarted()
- && TopTaskTracker.INSTANCE.get(launcher).getCachedTopTask(false).isHomeTask();
+ && TopTaskTracker.INSTANCE.get(launcher).getCachedTopTask(false,
+ launcher.getDisplayId()).isHomeTask();
}
private boolean isInMinusOne() {
@@ -270,7 +271,8 @@
return launcher != null
&& launcher.getStateManager().getState() == NORMAL
&& !launcher.isStarted()
- && TopTaskTracker.INSTANCE.get(launcher).getCachedTopTask(false).isHomeTask();
+ && TopTaskTracker.INSTANCE.get(launcher).getCachedTopTask(false,
+ launcher.getDisplayId()).isHomeTask();
}
@Override
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index dab78c5..a772415 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -504,7 +504,7 @@
for (TaskInfo taskInfo : recentTaskInfo.getTaskInfoList()) {
Task task = createTask(taskInfo, minimizedTaskIds);
List<Task> tasks = perDisplayTasks.computeIfAbsent(
- ExternalDisplaysKt.getDisplayId(task),
+ ExternalDisplaysKt.getSafeDisplayId(task),
k -> new ArrayList<>());
tasks.add(task);
}
diff --git a/quickstep/src/com/android/quickstep/SystemDecorationChangeObserver.kt b/quickstep/src/com/android/quickstep/SystemDecorationChangeObserver.kt
new file mode 100644
index 0000000..4559478
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/SystemDecorationChangeObserver.kt
@@ -0,0 +1,81 @@
+/*
+ * 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 android.content.Context
+import android.util.Log
+import com.android.launcher3.dagger.ApplicationContext
+import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.util.DaggerSingletonObject
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.quickstep.dagger.QuickstepBaseAppComponent
+import javax.inject.Inject
+
+@LauncherAppSingleton
+class SystemDecorationChangeObserver @Inject constructor(@ApplicationContext context: Context) {
+ companion object {
+ private const val TAG = "SystemDecorationChangeObserver"
+ private const val DEBUG = false
+
+ @JvmStatic
+ val INSTANCE: DaggerSingletonObject<SystemDecorationChangeObserver> =
+ DaggerSingletonObject<SystemDecorationChangeObserver>(
+ QuickstepBaseAppComponent::getSystemDecorationChangeObserver
+ )
+ }
+
+ interface DisplayDecorationListener {
+ fun onDisplayAddSystemDecorations(displayId: Int)
+
+ fun onDisplayRemoved(displayId: Int)
+
+ fun onDisplayRemoveSystemDecorations(displayId: Int)
+ }
+
+ fun notifyAddSystemDecorations(displayId: Int) {
+ if (DEBUG) Log.d(TAG, "SystemDecorationAdded: $displayId")
+ for (listener in mDisplayDecorationListeners) {
+ MAIN_EXECUTOR.execute { listener.onDisplayAddSystemDecorations(displayId) }
+ }
+ }
+
+ fun notifyOnDisplayRemoved(displayId: Int) {
+ if (DEBUG) Log.d(TAG, "displayRemoved: $displayId")
+ for (listener in mDisplayDecorationListeners) {
+ MAIN_EXECUTOR.execute { listener.onDisplayRemoved(displayId) }
+ }
+ }
+
+ fun notifyDisplayRemoveSystemDecorations(displayId: Int) {
+ if (DEBUG) Log.d(TAG, "SystemDecorationRemoved: $displayId")
+ for (listener in mDisplayDecorationListeners) {
+ MAIN_EXECUTOR.execute { listener.onDisplayRemoveSystemDecorations(displayId) }
+ }
+ }
+
+ private val mDisplayDecorationListeners = ArrayList<DisplayDecorationListener>()
+
+ fun registerDisplayDecorationListener(listener: DisplayDecorationListener) {
+ if (DEBUG) Log.d(TAG, "registerDisplayDecorationListener")
+ mDisplayDecorationListeners.add(listener)
+ }
+
+ fun unregisterDisplayDecorationListener(listener: DisplayDecorationListener) {
+ if (DEBUG) Log.d(TAG, "unregisterDisplayDecorationListener")
+ mDisplayDecorationListeners.remove(listener)
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/TopTaskTracker.java b/quickstep/src/com/android/quickstep/TopTaskTracker.java
index 8116a88..7e773e3 100644
--- a/quickstep/src/com/android/quickstep/TopTaskTracker.java
+++ b/quickstep/src/com/android/quickstep/TopTaskTracker.java
@@ -22,8 +22,10 @@
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.launcher3.Flags.enableOverviewOnConnectedDisplays;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_A;
+import static com.android.quickstep.fallback.window.RecentsWindowFlags.enableOverviewOnConnectedDisplays;
import static com.android.wm.shell.Flags.enableShellTopTaskTracking;
import static com.android.wm.shell.Flags.enableFlexibleSplit;
import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_SPLIT;
@@ -47,6 +49,7 @@
import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.dagger.QuickstepBaseAppComponent;
import com.android.quickstep.util.DesksUtils;
+import com.android.quickstep.util.ExternalDisplaysKt;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.Task.TaskKey;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -316,21 +319,26 @@
*/
@NonNull
@UiThread
- public CachedTaskInfo getCachedTopTask(boolean filterOnlyVisibleRecents) {
+ public CachedTaskInfo getCachedTopTask(boolean filterOnlyVisibleRecents, int displayId) {
if (enableShellTopTaskTracking()) {
// TODO(346588978): Currently ignore filterOnlyVisibleRecents, but perhaps make this an
// explicit filter For things to ignore (ie. PIP/Bubbles/Assistant/etc/so that this is
// explicit)
- // TODO(346588978): This assumes default display as gesture nav is only supported there
- return new CachedTaskInfo(mVisibleTasks.get(DEFAULT_DISPLAY));
+ return new CachedTaskInfo(mVisibleTasks.get(displayId));
} else {
if (filterOnlyVisibleRecents) {
// Since we only know about the top most task, any filtering may not be applied on
// the cache. The second to top task may change while the top task is still the
// same.
- RunningTaskInfo[] tasks = TraceHelper.allowIpcs("getCachedTopTask.true", () ->
+ TaskInfo[] tasks = TraceHelper.allowIpcs("getCachedTopTask.true", () ->
ActivityManagerWrapper.getInstance().getRunningTasks(true));
- return new CachedTaskInfo(Arrays.asList(tasks));
+ if (enableOverviewOnConnectedDisplays()) {
+ return new CachedTaskInfo(Arrays.stream(tasks).filter(
+ info -> ExternalDisplaysKt.getSafeDisplayId(info)
+ == displayId).toList());
+ } else {
+ return new CachedTaskInfo(Arrays.asList(tasks));
+ }
}
if (mOrderedTaskList.isEmpty()) {
@@ -344,7 +352,12 @@
// Strip the pinned task and recents task
tasks.removeIf(t -> t.taskId == mPinnedTaskId || isRecentsTask(t)
|| DesksUtils.isDesktopWallpaperTask(t));
- return new CachedTaskInfo(tasks);
+ if (enableOverviewOnConnectedDisplays()) {
+ return new CachedTaskInfo(tasks.stream().filter(
+ info -> ExternalDisplaysKt.getSafeDisplayId(info) == displayId).toList());
+ } else {
+ return new CachedTaskInfo(tasks);
+ }
}
}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 4ab8d1b..30936ad 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -314,32 +314,25 @@
@BinderThread
@Override
public void onDisplayAddSystemDecorations(int displayId) {
- executeForTaskbarManager(taskbarManager ->
- taskbarManager.onDisplayAddSystemDecorations(displayId));
-
- executeForRecentsDisplayModel(displayModel ->
- displayModel.onDisplayAddSystemDecorations(displayId));
+ executeForTouchInteractionService(tis ->
+ tis.mSystemDecorationChangeObserver.notifyAddSystemDecorations(displayId));
}
@BinderThread
@Override
public void onDisplayRemoved(int displayId) {
- executeForTaskbarManager(taskbarManager ->
- taskbarManager.onDisplayRemoved(displayId));
executeForTouchInteractionService(tis -> {
+ tis.mSystemDecorationChangeObserver.notifyOnDisplayRemoved(displayId);
tis.mDeviceState.clearSysUIStateFlagsForDisplay(displayId);
});
- executeForRecentsDisplayModel(displayModel ->
- displayModel.onDisplayRemoved(displayId));
}
@BinderThread
@Override
public void onDisplayRemoveSystemDecorations(int displayId) {
- executeForTaskbarManager(taskbarManager ->
- taskbarManager.onDisplayRemoveSystemDecorations(displayId));
- executeForRecentsDisplayModel(displayModel ->
- displayModel.onDisplayRemoveSystemDecorations(displayId));
+ executeForTouchInteractionService(tis -> {
+ tis.mSystemDecorationChangeObserver.notifyDisplayRemoveSystemDecorations(displayId);
+ });
}
@BinderThread
@@ -451,15 +444,6 @@
}));
}
- private void executeForRecentsDisplayModel(
- @NonNull Consumer<RecentsDisplayModel> recentsDisplayModelConsumer) {
- MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
- RecentsDisplayModel recentsDisplayModel = tis.mRecentsDisplayModel;
- if (recentsDisplayModel == null) return;
- recentsDisplayModelConsumer.accept(recentsDisplayModel);
- }));
- }
-
/**
* Returns the {@link TaskbarManager}.
* <p>
@@ -596,6 +580,8 @@
private RecentsDisplayModel mRecentsDisplayModel;
+ private SystemDecorationChangeObserver mSystemDecorationChangeObserver;
+
@Override
public void onCreate() {
super.onCreate();
@@ -607,6 +593,7 @@
mDeviceState = RecentsAnimationDeviceState.INSTANCE.get(this);
mRotationTouchHelper = RotationTouchHelper.INSTANCE.get(this);
mRecentsDisplayModel = RecentsDisplayModel.getINSTANCE().get(this);
+ mSystemDecorationChangeObserver = SystemDecorationChangeObserver.getINSTANCE().get(this);
mAllAppsActionManager = new AllAppsActionManager(
this, UI_HELPER_EXECUTOR, this::createAllAppsPendingIntent);
mTrackpadsConnected = new ActiveTrackpadList(this, () -> {
@@ -1061,7 +1048,8 @@
private boolean isHoverActionWithoutConsumer(MotionEvent event) {
// Only process these events when taskbar is present.
- TaskbarActivityContext tac = mTaskbarManager.getCurrentActivityContext();
+ int displayId = event.getDisplayId();
+ TaskbarActivityContext tac = mTaskbarManager.getTaskbarForDisplay(displayId);
boolean isTaskbarPresent = tac != null && tac.getDeviceProfile().isTaskbarPresent
&& !tac.isPhoneMode();
return event.isHoverEvent() && (mUncheckedConsumer.getType() & TYPE_CURSOR_HOVER) == 0
@@ -1088,7 +1076,7 @@
// previousTaskInfo can be null iff previousGestureState == GestureState.DEFAULT_STATE
taskInfo = previousTaskInfo != null
? previousTaskInfo
- : TopTaskTracker.INSTANCE.get(this).getCachedTopTask(false);
+ : TopTaskTracker.INSTANCE.get(this).getCachedTopTask(false, displayId);
gestureState.updateRunningTask(taskInfo);
gestureState.updateLastStartedTaskIds(previousGestureState.getLastStartedTaskIds());
gestureState.updatePreviouslyAppearedTaskIds(
@@ -1098,7 +1086,7 @@
mOverviewComponentObserver,
displayId,
ActiveGestureLog.INSTANCE.incrementLogId());
- taskInfo = TopTaskTracker.INSTANCE.get(this).getCachedTopTask(false);
+ taskInfo = TopTaskTracker.INSTANCE.get(this).getCachedTopTask(false, displayId);
gestureState.updateRunningTask(taskInfo);
}
gestureState.setTrackpadGestureType(trackpadGestureType);
diff --git a/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java b/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
index d79a8ea..23b8a82 100644
--- a/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
+++ b/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
@@ -25,6 +25,7 @@
import com.android.quickstep.RecentsModel;
import com.android.quickstep.RotationTouchHelper;
import com.android.quickstep.SimpleOrientationTouchTransformer;
+import com.android.quickstep.SystemDecorationChangeObserver;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TopTaskTracker;
import com.android.quickstep.fallback.window.RecentsDisplayModel;
@@ -71,4 +72,5 @@
SimpleOrientationTouchTransformer getSimpleOrientationTouchTransformer();
+ SystemDecorationChangeObserver getSystemDecorationChangeObserver();
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandler.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandler.java
index 107babd..571a546 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandler.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandler.java
@@ -88,7 +88,7 @@
*/
@Nullable
@VisibleForTesting
- final Runnable getLongPressRunnable(NavHandle navHandle) {
+ final Runnable getLongPressRunnable(NavHandle navHandle, int displayId) {
if (!isContextualSearchEntrypointEnabled(navHandle)) {
Log.i(TAG, "Contextual Search invocation failed: entry point disabled");
mVibratorWrapper.cancelVibrate();
@@ -116,7 +116,7 @@
Log.i(TAG, "Contextual Search invocation successful");
String runningPackage = TopTaskTracker.INSTANCE.get(mContext).getCachedTopTask(
- /* filterOnlyVisibleRecents */ true).getPackageName();
+ /* filterOnlyVisibleRecents */ true, displayId).getPackageName();
mStatsLogManager.logger().withPackageName(runningPackage)
.log(LAUNCHER_LAUNCH_ASSISTANT_SUCCESSFUL_NAV_HANDLE);
} else {
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
index baabde8..af7c975 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
@@ -214,7 +214,7 @@
&& !mDeepPressLogged) {
// Log deep press even if feature is disabled.
String runningPackage = mTopTaskTracker.getCachedTopTask(
- /* filterOnlyVisibleRecents */ true).getPackageName();
+ /* filterOnlyVisibleRecents */ true, getDisplayId()).getPackageName();
mStatsLogManager.logger().withPackageName(runningPackage).log(
mNavHandle.isNavHandleStashedTaskbar() ? LAUNCHER_DEEP_PRESS_STASHED_TASKBAR
: LAUNCHER_DEEP_PRESS_NAVBAR);
@@ -233,12 +233,13 @@
Log.d(TAG, "triggerLongPress");
}
String runningPackage = mTopTaskTracker.getCachedTopTask(
- /* filterOnlyVisibleRecents */ true).getPackageName();
+ /* filterOnlyVisibleRecents */ true, getDisplayId()).getPackageName();
mStatsLogManager.logger().withPackageName(runningPackage).log(
mNavHandle.isNavHandleStashedTaskbar() ? LAUNCHER_LONG_PRESS_STASHED_TASKBAR
: LAUNCHER_LONG_PRESS_NAVBAR);
- Runnable longPressRunnable = mNavHandleLongPressHandler.getLongPressRunnable(mNavHandle);
+ Runnable longPressRunnable = mNavHandleLongPressHandler.getLongPressRunnable(mNavHandle,
+ getDisplayId());
if (longPressRunnable == null) {
return;
}
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index 5995ca2..c7e2ade6 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -30,8 +30,11 @@
import android.graphics.Matrix;
import android.graphics.Outline;
import android.graphics.Rect;
+import android.graphics.Typeface;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.RippleDrawable;
+import android.os.SystemProperties;
+import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -56,11 +59,12 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorListeners;
+import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.views.ClipIconView;
import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureAttemptCallback;
import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureAttemptCallback;
import com.android.systemui.shared.system.QuickStepContract;
-import com.android.wm.shell.shared.TypefaceUtils;
+import com.android.wm.shell.Flags;
import com.android.wm.shell.shared.TypefaceUtils.FontFamily;
import com.airbnb.lottie.LottieAnimationView;
@@ -80,6 +84,10 @@
private static final String PIXEL_TIPS_APP_PACKAGE_NAME = "com.google.android.apps.tips";
private static final CharSequence DEFAULT_PIXEL_TIPS_APP_NAME = "Pixel Tips";
+ private static final String SUW_THEME_SYSTEM_PROPERTY = "setupwizard.theme";
+ private static final String GLIF_EXPRESSIVE_THEME = "glif_expressive";
+ private static final String GLIF_EXPRESSIVE_LIGHT_THEME = "glif_expressive_light";
+
private static final int FEEDBACK_ANIMATION_MS = 133;
private static final int SUBTITLE_ANNOUNCE_DELAY_MS = 3000;
private static final int DONE_BUTTON_ANNOUNCE_DELAY_MS = 4000;
@@ -115,6 +123,7 @@
protected View mExitingAppView;
protected int mExitingAppRadius;
private final AlertDialog mSkipTutorialDialog;
+ private final boolean mIsExpressiveThemeEnabledInSUW;
private boolean mGestureCompleted = false;
protected LottieAnimationView mAnimatedGestureDemonstration;
@@ -172,7 +181,11 @@
mFeedbackTitleView.setText(getIntroductionTitle());
mFeedbackSubtitleView.setText(getIntroductionSubtitle());
- setTitleTypefaces();
+
+ String SUWTheme = SystemProperties.get(SUW_THEME_SYSTEM_PROPERTY, "");
+ mIsExpressiveThemeEnabledInSUW = SUWTheme.equals(GLIF_EXPRESSIVE_THEME) || SUWTheme.equals(
+ GLIF_EXPRESSIVE_LIGHT_THEME);
+ maybeSetTitleTypefaces();
mExitingAppView.setClipToOutline(true);
mExitingAppView.setOutlineProvider(new ViewOutlineProvider() {
@@ -406,12 +419,21 @@
mFeedbackTitleView.setText(titleResId);
mFeedbackSubtitleView.setText(subtitleResId);
+
+ boolean isUserSetupComplete = SettingsCache.INSTANCE.get(mContext).getValue(
+ Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), 0);
+ boolean userSetupNotCompleteAndExpressiveThemeEnabled =
+ !isUserSetupComplete && mIsExpressiveThemeEnabledInSUW;
+ boolean userSetupCompleteAndNewFontsEnabled = isUserSetupComplete && Flags.enableGsf();
+
if (isGestureSuccessful) {
if (mTutorialFragment.isAtFinalStep()) {
- TypefaceUtils.setTypeface(
- mDoneButton,
- FontFamily.GSF_LABEL_LARGE
- );
+ if (userSetupCompleteAndNewFontsEnabled
+ || userSetupNotCompleteAndExpressiveThemeEnabled) {
+ mDoneButton.setTypeface(
+ Typeface.create(FontFamily.GSF_LABEL_LARGE.getValue(),
+ Typeface.NORMAL));
+ }
showActionButton();
}
@@ -437,7 +459,7 @@
mCheckmarkAnimation.setVisibility(View.VISIBLE);
mCheckmarkAnimation.playAnimation();
mFeedbackTitleView.setTextAppearance(getSuccessTitleTextAppearance());
- setTitleTypefaces();
+ maybeSetTitleTypefaces();
}
public boolean isGestureCompleted() {
@@ -492,7 +514,7 @@
mFeedbackTitleView.setTextAppearance(getTitleTextAppearance());
mDoneButton.setTextAppearance(getDoneButtonTextAppearance());
- setTitleTypefaces();
+ maybeSetTitleTypefaces();
mDoneButton.getBackground().setTint(getDoneButtonColor());
mCheckmarkAnimation.setAnimation(mTutorialFragment.isAtFinalStep()
? R.raw.checkmark_animation_end
@@ -514,16 +536,15 @@
/**
* Apply expressive typefaces to the feedback title and subtitle views.
*/
- private void setTitleTypefaces() {
- TypefaceUtils.setTypeface(
- mFeedbackTitleView,
- mTutorialFragment.isLargeScreen()
- ? FontFamily.GSF_DISPLAY_MEDIUM_EMPHASIZED
- : FontFamily.GSF_DISPLAY_SMALL_EMPHASIZED);
- TypefaceUtils.setTypeface(
- mFeedbackSubtitleView,
- FontFamily.GSF_BODY_LARGE
- );
+ private void maybeSetTitleTypefaces() {
+ if (mIsExpressiveThemeEnabledInSUW || Flags.enableGsf()) {
+ mFeedbackTitleView.setTypeface(Typeface.create(mTutorialFragment.isLargeScreen()
+ ? FontFamily.GSF_DISPLAY_MEDIUM_EMPHASIZED.getValue()
+ : FontFamily.GSF_DISPLAY_SMALL_EMPHASIZED.getValue(),
+ Typeface.NORMAL));
+ mFeedbackSubtitleView.setTypeface(
+ Typeface.create(FontFamily.GSF_BODY_LARGE.getValue(), Typeface.NORMAL));
+ }
}
protected void resetViewsForBackGesture() {
diff --git a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
index b8f43a4..4b9eb9e 100644
--- a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
@@ -275,7 +275,7 @@
desiredTaskId: Int,
banner: View,
): Pair<Float, Float> {
- val snapshotParams = thumbnailViews[0].layoutParams as LinearLayout.LayoutParams
+ val snapshotParams = thumbnailViews[0].layoutParams as FrameLayout.LayoutParams
val translationX = banner.height.toFloat()
val translationY: Float
if (splitBounds == null) {
diff --git a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.kt
index 15eb69e..74ae688 100644
--- a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.kt
@@ -255,7 +255,7 @@
}
} else {
if (desiredTaskId == splitBounds.leftTopTaskId) {
- val snapshotParams = thumbnailViews[0].layoutParams as LinearLayout.LayoutParams
+ val snapshotParams = thumbnailViews[0].layoutParams as FrameLayout.LayoutParams
val bottomRightTaskPlusDividerPercent =
(splitBounds.rightBottomTaskPercent + splitBounds.dividerPercent)
translationY =
diff --git a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
index 80b50cb..456115f 100644
--- a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
@@ -26,7 +26,6 @@
import android.view.View
import android.view.View.MeasureSpec
import android.widget.FrameLayout
-import android.widget.LinearLayout
import androidx.core.util.component1
import androidx.core.util.component2
import androidx.core.view.updateLayoutParams
@@ -152,7 +151,7 @@
desiredTaskId: Int,
banner: View,
): Pair<Float, Float> {
- val snapshotParams = thumbnailViews[0].layoutParams as LinearLayout.LayoutParams
+ val snapshotParams = thumbnailViews[0].layoutParams as FrameLayout.LayoutParams
val translationX: Float = (taskViewWidth - banner.height).toFloat()
val translationY: Float
if (splitBounds == null) {
diff --git a/quickstep/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapper.kt b/quickstep/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapper.kt
index 679daf8..619075f 100644
--- a/quickstep/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapper.kt
+++ b/quickstep/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapper.kt
@@ -17,51 +17,18 @@
package com.android.quickstep.recents.ui.mapper
import android.view.View.OnClickListener
-import com.android.launcher3.Flags.enableDesktopExplodedView
import com.android.quickstep.recents.ui.viewmodel.TaskData
-import com.android.quickstep.task.thumbnail.TaskHeaderUiState
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.ThumbnailHeader
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
object TaskUiStateMapper {
/**
- * Converts a [TaskData] object into a [TaskHeaderUiState] for display in the UI.
- *
- * This function handles different types of [TaskData] and determines the appropriate UI state
- * based on the data and provided flags.
- *
- * @param taskData The [TaskData] to convert. Can be null or a specific subclass.
- * @param hasHeader A flag indicating whether the UI should display a header.
- * @param clickCloseListener A callback when the close button in the UI is clicked.
- * @return A [TaskHeaderUiState] representing the UI state for the given task data.
- */
- fun toTaskHeaderState(
- taskData: TaskData?,
- hasHeader: Boolean,
- clickCloseListener: OnClickListener?,
- ): TaskHeaderUiState =
- when {
- taskData !is TaskData.Data -> TaskHeaderUiState.HideHeader
- canHeaderBeCreated(taskData, hasHeader, clickCloseListener) -> {
- TaskHeaderUiState.ShowHeader(
- TaskHeaderUiState.ThumbnailHeader(
- // TODO(http://b/353965691): figure out what to do when `icon` or
- // `titleDescription` is null.
- taskData.icon!!,
- taskData.titleDescription!!,
- clickCloseListener!!,
- )
- )
- }
- else -> TaskHeaderUiState.HideHeader
- }
-
- /**
* Converts a [TaskData] object into a [TaskThumbnailUiState] for display in the UI.
*
* This function handles different types of [TaskData] and determines the appropriate UI state
@@ -69,26 +36,48 @@
*
* @param taskData The [TaskData] to convert. Can be null or a specific subclass.
* @param isLiveTile A flag indicating whether the task data represents live tile.
+ * @param hasHeader A flag indicating whether the UI should display a header.
+ * @param clickCloseListener A callback when the close button in the UI is clicked.
* @return A [TaskThumbnailUiState] representing the UI state for the given task data.
*/
- fun toTaskThumbnailUiState(taskData: TaskData?, isLiveTile: Boolean): TaskThumbnailUiState =
+ fun toTaskThumbnailUiState(
+ taskData: TaskData?,
+ isLiveTile: Boolean,
+ hasHeader: Boolean,
+ clickCloseListener: OnClickListener?,
+ ): TaskThumbnailUiState =
when {
taskData !is TaskData.Data -> Uninitialized
- isLiveTile -> LiveTile
+ isLiveTile -> createLiveTileState(taskData, hasHeader, clickCloseListener)
isBackgroundOnly(taskData) -> BackgroundOnly(taskData.backgroundColor)
isSnapshotSplash(taskData) ->
SnapshotSplash(
- Snapshot(
- taskData.thumbnailData?.thumbnail!!,
- taskData.thumbnailData.rotation,
- taskData.backgroundColor,
- ),
+ createSnapshotState(taskData, hasHeader, clickCloseListener),
taskData.icon,
)
-
else -> Uninitialized
}
+ private fun createSnapshotState(
+ taskData: TaskData.Data,
+ hasHeader: Boolean,
+ clickCloseListener: OnClickListener?,
+ ): Snapshot =
+ if (canHeaderBeCreated(taskData, hasHeader, clickCloseListener)) {
+ Snapshot.WithHeader(
+ taskData.thumbnailData?.thumbnail!!,
+ taskData.thumbnailData.rotation,
+ taskData.backgroundColor,
+ ThumbnailHeader(taskData.icon!!, taskData.titleDescription!!, clickCloseListener!!),
+ )
+ } else {
+ Snapshot.WithoutHeader(
+ taskData.thumbnailData?.thumbnail!!,
+ taskData.thumbnailData.rotation,
+ taskData.backgroundColor,
+ )
+ }
+
private fun isBackgroundOnly(taskData: TaskData.Data) =
taskData.isLocked || taskData.thumbnailData == null
@@ -100,9 +89,21 @@
hasHeader: Boolean,
clickCloseListener: OnClickListener?,
) =
- enableDesktopExplodedView() &&
- hasHeader &&
+ hasHeader &&
taskData.icon != null &&
taskData.titleDescription != null &&
clickCloseListener != null
+
+ private fun createLiveTileState(
+ taskData: TaskData.Data,
+ hasHeader: Boolean,
+ clickCloseListener: OnClickListener?,
+ ) =
+ if (canHeaderBeCreated(taskData, hasHeader, clickCloseListener)) {
+ // TODO(http://b/353965691): figure out what to do when `icon` or `titleDescription` is
+ // null.
+ LiveTile.WithHeader(
+ ThumbnailHeader(taskData.icon!!, taskData.titleDescription!!, clickCloseListener!!)
+ )
+ } else LiveTile.WithoutHeader
}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskContentView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskContentView.kt
deleted file mode 100644
index a40929c..0000000
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskContentView.kt
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * 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.task.thumbnail
-
-import android.content.Context
-import android.graphics.Outline
-import android.graphics.Path
-import android.graphics.Rect
-import android.util.AttributeSet
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewOutlineProvider
-import android.widget.LinearLayout
-import androidx.core.view.isInvisible
-import com.android.launcher3.Flags.enableRefactorTaskThumbnail
-import com.android.launcher3.R
-import com.android.launcher3.util.ViewPool
-import com.android.quickstep.views.TaskHeaderView
-import com.android.quickstep.views.TaskThumbnailViewDeprecated
-
-/**
- * TaskContentView is a wrapper around the TaskHeaderView and TaskThumbnailView. It is a sibling to
- * DWB, AiAi (TaskOverlay).
- */
-class TaskContentView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
- LinearLayout(context, attrs), ViewPool.Reusable {
-
- private var taskHeaderView: TaskHeaderView? = null
- private var taskThumbnailView: TaskThumbnailView? = null
- private var taskThumbnailViewDeprecated: TaskThumbnailViewDeprecated? = null
- private var onSizeChanged: ((width: Int, height: Int) -> Unit)? = null
- private val outlinePath = Path()
-
- /**
- * Sets the outline bounds of the view. Default to use view's bound as outline when set to null.
- */
- var outlineBounds: Rect? = null
- set(value) {
- field = value
- invalidateOutline()
- }
-
- private val bounds = Rect()
-
- var cornerRadius: Float = 0f
- set(value) {
- field = value
- invalidateOutline()
- }
-
- override fun onFinishInflate() {
- super.onFinishInflate()
- createTaskThumbnailView()
- }
-
- override fun setScaleX(scaleX: Float) {
- super.setScaleX(scaleX)
- taskThumbnailView?.parentScaleXUpdated(scaleX)
- }
-
- override fun setScaleY(scaleY: Float) {
- super.setScaleY(scaleY)
- taskThumbnailView?.parentScaleYUpdated(scaleY)
- }
-
- override fun onAttachedToWindow() {
- super.onAttachedToWindow()
- clipToOutline = true
- outlineProvider =
- object : ViewOutlineProvider() {
- override fun getOutline(view: View, outline: Outline) {
- val outlineRect = outlineBounds ?: bounds
- outlinePath.apply {
- rewind()
- addRoundRect(
- outlineRect.left.toFloat(),
- outlineRect.top.toFloat(),
- outlineRect.right.toFloat(),
- outlineRect.bottom.toFloat(),
- cornerRadius / scaleX,
- cornerRadius / scaleY,
- Path.Direction.CW,
- )
- }
- outline.setPath(outlinePath)
- }
- }
- }
-
- override fun onRecycle() {
- taskHeaderView?.isInvisible = true
- onSizeChanged = null
- outlineBounds = null
- taskThumbnailView?.onRecycle()
- taskThumbnailViewDeprecated?.onRecycle()
- }
-
- fun doOnSizeChange(action: (width: Int, height: Int) -> Unit) {
- onSizeChanged = action
- }
-
- override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
- super.onSizeChanged(w, h, oldw, oldh)
- onSizeChanged?.invoke(width, height)
- bounds.set(0, 0, w, h)
- invalidateOutline()
- }
-
- private fun createHeaderView(taskHeaderState: TaskHeaderUiState) {
- if (taskHeaderView == null && taskHeaderState is TaskHeaderUiState.ShowHeader) {
- taskHeaderView =
- LayoutInflater.from(context).inflate(R.layout.task_header_view, this, false)
- as TaskHeaderView
- addView(taskHeaderView, 0)
- }
- }
-
- private fun createTaskThumbnailView() {
- if (taskThumbnailView == null) {
- if (enableRefactorTaskThumbnail()) {
- taskThumbnailView =
- LayoutInflater.from(context).inflate(R.layout.task_thumbnail, this, false)
- as TaskThumbnailView
- addView(taskThumbnailView)
- } else {
- taskThumbnailViewDeprecated =
- LayoutInflater.from(context)
- .inflate(R.layout.task_thumbnail_deprecated, this, false)
- as TaskThumbnailViewDeprecated
- addView(taskThumbnailViewDeprecated)
- }
- }
- }
-
- fun setState(
- taskHeaderState: TaskHeaderUiState,
- taskThumbnailUiState: TaskThumbnailUiState,
- taskId: Int?,
- ) {
- createHeaderView(taskHeaderState)
- taskHeaderView?.setState(taskHeaderState)
- taskThumbnailView?.setState(taskThumbnailUiState, taskId)
- }
-}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskHeaderUiState.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskHeaderUiState.kt
deleted file mode 100644
index 09fb540..0000000
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskHeaderUiState.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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.task.thumbnail
-
-import android.graphics.drawable.Drawable
-import android.view.View
-
-sealed class TaskHeaderUiState {
- data class ShowHeader(val header: ThumbnailHeader) : TaskHeaderUiState()
-
- data object HideHeader : TaskHeaderUiState()
-
- data class ThumbnailHeader(
- val icon: Drawable,
- val title: String,
- val clickCloseListener: View.OnClickListener,
- )
-}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
index a5c9ac0..db593d3 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
@@ -19,6 +19,7 @@
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.view.Surface
+import android.view.View.OnClickListener
import androidx.annotation.ColorInt
sealed class TaskThumbnailUiState {
@@ -26,14 +27,37 @@
data class BackgroundOnly(@ColorInt val backgroundColor: Int) : TaskThumbnailUiState()
- data object LiveTile : 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,
+ val clickCloseListener: OnClickListener,
)
}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
index 78a16f1..0edbacc 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
@@ -19,24 +19,32 @@
import android.content.Context
import android.graphics.Color
import android.graphics.Matrix
+import android.graphics.Outline
+import android.graphics.Path
+import android.graphics.Rect
import android.graphics.drawable.ShapeDrawable
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.LauncherAnimUtils.VIEW_ALPHA
import com.android.launcher3.R
import com.android.launcher3.util.MultiPropertyFactory
+import com.android.launcher3.util.ViewPool
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.quickstep.views.FixedSizeImageView
+import com.android.quickstep.views.TaskThumbnailViewHeader
-class TaskThumbnailView : FrameLayout {
+class TaskThumbnailView : FrameLayout, ViewPool.Reusable {
private val scrimView: View by lazy { findViewById(R.id.task_thumbnail_scrim) }
private val liveTileView: LiveTileView by lazy { findViewById(R.id.task_thumbnail_live_tile) }
private val thumbnailView: FixedSizeImageView by lazy { findViewById(R.id.task_thumbnail) }
@@ -45,9 +53,30 @@
private val dimAlpha: MultiPropertyFactory<View> by lazy {
MultiPropertyFactory(scrimView, VIEW_ALPHA, ScrimViewAlpha.entries.size, ::maxOf)
}
+ private val outlinePath = Path()
+ private var onSizeChanged: ((width: Int, height: Int) -> Unit)? = null
+
+ private var taskThumbnailViewHeader: TaskThumbnailViewHeader? = null
private var uiState: TaskThumbnailUiState = Uninitialized
+ /**
+ * Sets the outline bounds of the view. Default to use view's bound as outline when set to null.
+ */
+ var outlineBounds: Rect? = null
+ set(value) {
+ field = value
+ invalidateOutline()
+ }
+
+ private val bounds = Rect()
+
+ var cornerRadius: Float = 0f
+ set(value) {
+ field = value
+ invalidateOutline()
+ }
+
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
@@ -58,8 +87,39 @@
defStyleAttr: Int,
) : super(context, attrs, defStyleAttr)
- fun onRecycle() {
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ maybeCreateHeader()
+ }
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ clipToOutline = true
+ outlineProvider =
+ object : ViewOutlineProvider() {
+ override fun getOutline(view: View, outline: Outline) {
+ val outlineRect = outlineBounds ?: bounds
+ outlinePath.apply {
+ rewind()
+ addRoundRect(
+ outlineRect.left.toFloat(),
+ outlineRect.top.toFloat(),
+ outlineRect.right.toFloat(),
+ outlineRect.bottom.toFloat(),
+ cornerRadius / scaleX,
+ cornerRadius / scaleY,
+ Path.Direction.CW,
+ )
+ }
+ outline.setPath(outlinePath)
+ }
+ }
+ }
+
+ override fun onRecycle() {
uiState = Uninitialized
+ onSizeChanged = null
+ outlineBounds = null
resetViews()
}
@@ -70,7 +130,7 @@
resetViews()
when (state) {
is Uninitialized -> {}
- is LiveTile -> drawLiveWindow()
+ is LiveTile -> drawLiveWindow(state)
is SnapshotSplash -> drawSnapshotSplash(state)
is BackgroundOnly -> drawBackground(state.backgroundColor)
}
@@ -95,12 +155,25 @@
splashIcon.alpha = value
}
- fun parentScaleXUpdated(scaleX: Float) {
+ fun doOnSizeChange(action: (width: Int, height: Int) -> Unit) {
+ onSizeChanged = action
+ }
+
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+ super.onSizeChanged(w, h, oldw, oldh)
+ onSizeChanged?.invoke(width, height)
+ bounds.set(0, 0, w, h)
+ invalidateOutline()
+ }
+
+ override fun setScaleX(scaleX: Float) {
+ super.setScaleX(scaleX)
// Splash icon should ignore scale on TTV
splashIcon.scaleX = 1 / scaleX
}
- fun parentScaleYUpdated(scaleY: Float) {
+ override fun setScaleY(scaleY: Float) {
+ super.setScaleY(scaleY)
// Splash icon should ignore scale on TTV
splashIcon.scaleY = 1 / scaleY
}
@@ -114,14 +187,20 @@
splashIcon.setImageDrawable(null)
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) {
@@ -133,6 +212,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
@@ -148,6 +232,16 @@
Log.d(TAG, "[TaskThumbnailView@${Integer.toHexString(hashCode())}] $message")
}
+ private fun maybeCreateHeader() {
+ if (enableDesktopExplodedView() && taskThumbnailViewHeader == null) {
+ taskThumbnailViewHeader =
+ LayoutInflater.from(context)
+ .inflate(R.layout.task_thumbnail_view_header, this, false)
+ as TaskThumbnailViewHeader
+ addView(taskThumbnailViewHeader)
+ }
+ }
+
private companion object {
const val TAG = "TaskThumbnailView"
private const val MAX_SCRIM_ALPHA = 0.4f
diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java
index 8385485..24c01ae 100644
--- a/quickstep/src/com/android/quickstep/util/AppPairsController.java
+++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java
@@ -32,7 +32,6 @@
import static com.android.wm.shell.shared.split.SplitScreenConstants.getIndex;
import static com.android.wm.shell.shared.split.SplitScreenConstants.isPersistentSnapPosition;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.util.Log;
@@ -93,10 +92,10 @@
private static final int BITMASK_SIZE = 16;
private static final int BITMASK_FOR_SNAP_POSITION = (1 << BITMASK_SIZE) - 1;
- private Context mContext;
+ private ActivityContext mContext;
private final SplitSelectStateController mSplitSelectStateController;
private final StatsLogManager mStatsLogManager;
- public AppPairsController(Context context,
+ public AppPairsController(ActivityContext context,
SplitSelectStateController splitSelectStateController,
StatsLogManager statsLogManager) {
mContext = context;
@@ -208,7 +207,7 @@
}
AppPairInfo newAppPair = new AppPairInfo(apps);
- IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache();
+ IconCache iconCache = LauncherAppState.getInstance(mContext.asContext()).getIconCache();
MODEL_EXECUTOR.execute(() -> {
newAppPair.getAppContents().forEach(member -> {
member.title = "";
@@ -216,8 +215,8 @@
iconCache.getTitleAndIcon(member, member.getMatchingLookupFlag());
});
MAIN_EXECUTOR.execute(() -> {
- LauncherAccessibilityDelegate delegate =
- QuickstepLauncher.getLauncher(mContext).getAccessibilityDelegate();
+ LauncherAccessibilityDelegate delegate = QuickstepLauncher.getLauncher(
+ mContext.asContext()).getAccessibilityDelegate();
if (delegate != null) {
delegate.addToWorkspace(newAppPair, true, (success) -> {
if (success) {
@@ -300,7 +299,7 @@
*/
@Nullable
private AppInfo resolveAppInfoByComponent(@NonNull ComponentKey key) {
- AllAppsStore appsStore = ActivityContext.lookupContext(mContext)
+ AllAppsStore appsStore = ActivityContext.lookupContext(mContext.asContext())
.getAppsView().getAppsStore();
// First look up the app info in order of:
@@ -326,7 +325,7 @@
if (appInfo == null) {
return null;
}
- return appInfo.makeWorkspaceItem(mContext);
+ return appInfo.makeWorkspaceItem(mContext.asContext());
}
/**
@@ -418,7 +417,7 @@
} else {
// Tapped an app pair while in a single app
final TopTaskTracker.CachedTaskInfo runningTask = topTaskTracker
- .getCachedTopTask(false /* filterOnlyVisibleRecents */);
+ .getCachedTopTask(false /* filterOnlyVisibleRecents */, context.getDisplayId());
mSplitSelectStateController.findLastActiveTasksAndRunCallback(
componentKeys,
@@ -548,6 +547,6 @@
*/
@VisibleForTesting
public TopTaskTracker getTopTaskTracker() {
- return TopTaskTracker.INSTANCE.get(mContext);
+ return TopTaskTracker.INSTANCE.get(mContext.asContext());
}
}
diff --git a/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt b/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt
index 3bc9adc..e574cc7 100644
--- a/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt
+++ b/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt
@@ -87,7 +87,8 @@
if (success) {
val runningPackage =
TopTaskTracker.INSTANCE[context].getCachedTopTask(
- /* filterOnlyVisibleRecents */ true
+ /* filterOnlyVisibleRecents */ true,
+ DEFAULT_DISPLAY,
)
.getPackageName()
statsLogManager
diff --git a/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java b/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java
index a8d3c6d..136c496 100644
--- a/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java
+++ b/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java
@@ -18,6 +18,7 @@
import static android.app.contextualsearch.ContextualSearchManager.ACTION_LAUNCH_CONTEXTUAL_SEARCH;
import static android.app.contextualsearch.ContextualSearchManager.ENTRYPOINT_SYSTEM_ACTION;
import static android.app.contextualsearch.ContextualSearchManager.FEATURE_CONTEXTUAL_SEARCH;
+import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_OMNI_SUCCESSFUL_SYSTEM_ACTION;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
@@ -244,7 +245,8 @@
ENTRYPOINT_SYSTEM_ACTION);
if (contextualSearchInvoked) {
String runningPackage = mTopTaskTracker.getCachedTopTask(
- /* filterOnlyVisibleRecents */ true).getPackageName();
+ /* filterOnlyVisibleRecents */ true,
+ DEFAULT_DISPLAY).getPackageName();
StatsLogManager.newInstance(mContext).logger()
.withPackageName(runningPackage)
.log(LAUNCHER_LAUNCH_OMNI_SUCCESSFUL_SYSTEM_ACTION);
diff --git a/quickstep/src/com/android/quickstep/util/ExternalDisplays.kt b/quickstep/src/com/android/quickstep/util/ExternalDisplays.kt
index 455b312..0aaca31 100644
--- a/quickstep/src/com/android/quickstep/util/ExternalDisplays.kt
+++ b/quickstep/src/com/android/quickstep/util/ExternalDisplays.kt
@@ -16,6 +16,7 @@
package com.android.quickstep.util
+import android.app.TaskInfo
import android.view.Display.DEFAULT_DISPLAY
import android.view.Display.INVALID_DISPLAY
import com.android.systemui.shared.recents.model.Task
@@ -24,10 +25,9 @@
val Int.isExternalDisplay
get() = this != DEFAULT_DISPLAY
-/** Returns displayId of this [Task], default to [DEFAULT_DISPLAY] */
-val Task?.displayId
+val Int?.safeDisplayId
get() =
- this?.key?.displayId.let { displayId ->
+ this.let { displayId ->
when (displayId) {
null -> DEFAULT_DISPLAY
INVALID_DISPLAY -> DEFAULT_DISPLAY
@@ -35,6 +35,14 @@
}
}
+/** Returns displayId of this [Task], default to [DEFAULT_DISPLAY] */
+val Task?.safeDisplayId
+ get() = this?.key?.displayId.safeDisplayId
+
/** Returns if this task belongs tto [DEFAULT_DISPLAY] */
val Task?.isExternalDisplay
- get() = displayId.isExternalDisplay
+ get() = safeDisplayId.isExternalDisplay
+
+/** Returns displayId of this [TaskInfo], default to [DEFAULT_DISPLAY] */
+val TaskInfo?.safeDisplayId
+ get() = this?.displayId.safeDisplayId
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index d6e553d..96a5733 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -192,7 +192,7 @@
taskViewHeight: Int,
isPrimaryTaskSplitting: Boolean,
) {
- val taskContentView = taskContainer.taskContentView
+ val snapshot = taskContainer.snapshotView
val iconView: View = taskContainer.iconView.asView()
if (enableRefactorTaskThumbnail()) {
builder.add(
@@ -241,11 +241,7 @@
val centerThumbnailTranslationX: Float = (taskViewWidth - snapshotViewSize.x) / 2f
val finalScaleX: Float = taskViewWidth.toFloat() / snapshotViewSize.x
builder.add(
- ObjectAnimator.ofFloat(
- taskContentView,
- View.TRANSLATION_X,
- centerThumbnailTranslationX,
- )
+ ObjectAnimator.ofFloat(snapshot, View.TRANSLATION_X, centerThumbnailTranslationX)
)
if (!enableOverviewIconMenu()) {
// icons are anchored from Gravity.END, so need to use negative translation
@@ -254,17 +250,15 @@
ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X, -centerIconTranslationX)
)
}
- builder.add(ObjectAnimator.ofFloat(taskContentView, View.SCALE_X, finalScaleX))
+ builder.add(ObjectAnimator.ofFloat(snapshot, View.SCALE_X, finalScaleX))
// Reset other dimensions
// TODO(b/271468547), can't set Y translate to 0, need to account for top space
- taskContentView.scaleY = 1f
+ snapshot.scaleY = 1f
val translateYResetVal: Float =
if (!isPrimaryTaskSplitting) 0f
else deviceProfile.overviewTaskThumbnailTopMarginPx.toFloat()
- builder.add(
- ObjectAnimator.ofFloat(taskContentView, View.TRANSLATION_Y, translateYResetVal)
- )
+ builder.add(ObjectAnimator.ofFloat(snapshot, View.TRANSLATION_Y, translateYResetVal))
} else {
val thumbnailSize = taskViewHeight - deviceProfile.overviewTaskThumbnailTopMarginPx
// Center view first so scaling happens uniformly, alternatively we can move pivotY to 0
@@ -287,22 +281,18 @@
}
val finalScaleY: Float = thumbnailSize.toFloat() / snapshotViewSize.y
builder.add(
- ObjectAnimator.ofFloat(
- taskContentView,
- View.TRANSLATION_Y,
- centerThumbnailTranslationY,
- )
+ ObjectAnimator.ofFloat(snapshot, View.TRANSLATION_Y, centerThumbnailTranslationY)
)
if (!enableOverviewIconMenu()) {
// icons are anchored from Gravity.END, so need to use negative translation
builder.add(ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X, 0f))
}
- builder.add(ObjectAnimator.ofFloat(taskContentView, View.SCALE_Y, finalScaleY))
+ builder.add(ObjectAnimator.ofFloat(snapshot, View.SCALE_Y, finalScaleY))
// Reset other dimensions
- taskContentView.scaleX = 1f
- builder.add(ObjectAnimator.ofFloat(taskContentView, View.TRANSLATION_X, 0f))
+ snapshot.scaleX = 1f
+ builder.add(ObjectAnimator.ofFloat(snapshot, View.TRANSLATION_X, 0f))
}
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index fd8b356..08f2552 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -195,7 +195,7 @@
mRecentTasksModel = recentsModel;
mActivityBackCallback = activityBackCallback;
mSplitAnimationController = new SplitAnimationController(this);
- mAppPairsController = new AppPairsController(mContainer.asContext(), this, statsLogManager);
+ mAppPairsController = new AppPairsController(mContainer, this, statsLogManager);
mSplitSelectDataHolder = new SplitSelectDataHolder(mContainer.asContext());
}
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
index 8876633..8b12455 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
@@ -56,7 +56,6 @@
import com.android.quickstep.recents.domain.model.DesktopTaskBoundsData
import com.android.quickstep.recents.ui.viewmodel.DesktopTaskViewModel
import com.android.quickstep.recents.ui.viewmodel.TaskData
-import com.android.quickstep.task.thumbnail.TaskContentView
import com.android.quickstep.task.thumbnail.TaskThumbnailView
import com.android.quickstep.util.DesktopTask
import com.android.quickstep.util.RecentsOrientedState
@@ -78,14 +77,27 @@
private val contentViewFullscreenParams = FullscreenDrawParams(context)
- private val taskContentViewPool =
- ViewPool<TaskContentView>(
- context,
- this,
- R.layout.task_content_view,
- VIEW_POOL_MAX_SIZE,
- VIEW_POOL_INITIAL_SIZE,
- )
+ private val taskThumbnailViewDeprecatedPool =
+ if (!enableRefactorTaskThumbnail()) {
+ ViewPool<TaskThumbnailViewDeprecated>(
+ context,
+ this,
+ R.layout.task_thumbnail_deprecated,
+ VIEW_POOL_MAX_SIZE,
+ VIEW_POOL_INITIAL_SIZE,
+ )
+ } else null
+
+ private val taskThumbnailViewPool =
+ if (enableRefactorTaskThumbnail()) {
+ ViewPool<TaskThumbnailView>(
+ context,
+ this,
+ R.layout.task_thumbnail,
+ VIEW_POOL_MAX_SIZE,
+ VIEW_POOL_INITIAL_SIZE,
+ )
+ } else null
private val tempPointF = PointF()
private val lastComputedTaskSize = Rect()
@@ -243,7 +255,7 @@
// for all cases where the progress is non-zero.
if (explodeProgress == 0.0f || explodeProgress == 1.0f) {
// Reset scaling and translation that may have been applied during animation.
- it.taskContentView.apply {
+ it.snapshotView.apply {
scaleX = 1.0f
scaleY = 1.0f
translationX = 0.0f
@@ -251,7 +263,7 @@
}
// Position the task to the same position as it would be on the desktop
- it.taskContentView?.updateLayoutParams<LayoutParams> {
+ it.snapshotView.updateLayoutParams<LayoutParams> {
gravity = Gravity.LEFT or Gravity.TOP
width = taskWidth.toInt()
height = taskHeight.toInt()
@@ -262,7 +274,7 @@
if (
enableDesktopRecentsTransitionsCornersBugfix() && enableRefactorTaskThumbnail()
) {
- it.taskContentView?.outlineBounds =
+ it.thumbnailView.outlineBounds =
if (intersects(overviewTaskPosition, screenRect))
Rect(overviewTaskPosition).apply {
intersectUnchecked(screenRect)
@@ -279,7 +291,7 @@
} else {
// During the animation, apply translation and scale such that the view is
// transformed to where we want, without triggering layout.
- it.taskContentView.apply {
+ it.snapshotView.apply {
pivotX = 0.0f
pivotY = 0.0f
translationX = taskLeft - left
@@ -313,19 +325,17 @@
val backgroundViewIndex = contentView.indexOfChild(backgroundView)
taskContainers =
tasks.map { task ->
- val taskContentView = taskContentViewPool.view
- contentView.addView(taskContentView, backgroundViewIndex + 1)
val snapshotView =
if (enableRefactorTaskThumbnail()) {
- taskContentView.findViewById<TaskThumbnailView>(R.id.snapshot)
+ taskThumbnailViewPool!!.view
} else {
- taskContentView.findViewById<TaskThumbnailViewDeprecated>(R.id.snapshot)
+ taskThumbnailViewDeprecatedPool!!.view
}
+ contentView.addView(snapshotView, backgroundViewIndex + 1)
TaskContainer(
this,
task,
- taskContentView,
snapshotView,
iconView,
TransformingTouchDelegate(iconView.asView()),
@@ -478,8 +488,12 @@
}
private fun removeAndRecycleThumbnailView(taskContainer: TaskContainer) {
- contentView.removeView(taskContainer.taskContentView)
- taskContentViewPool.recycle(taskContainer.taskContentView)
+ contentView.removeView(taskContainer.snapshotView)
+ if (enableRefactorTaskThumbnail()) {
+ taskThumbnailViewPool!!.recycle(taskContainer.thumbnailView)
+ } else {
+ taskThumbnailViewDeprecatedPool!!.recycle(taskContainer.thumbnailViewDeprecated)
+ }
}
private fun updateTaskPositions() {
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
index 10a2e90..faa9e28 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
@@ -24,6 +24,7 @@
import android.view.ViewStub
import com.android.internal.jank.Cuj
import com.android.launcher3.Flags.enableOverviewIconMenu
+import com.android.launcher3.Flags.enableRefactorTaskThumbnail
import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.util.RunnableList
@@ -77,8 +78,8 @@
val splitBoundsConfig = splitBoundsConfig ?: return
val inSplitSelection = getThisTaskCurrentlyInSplitSelection() != INVALID_TASK_ID
pagedOrientationHandler.measureGroupedTaskViewThumbnailBounds(
- leftTopTaskContainer.taskContentView,
- rightBottomTaskContainer.taskContentView,
+ leftTopTaskContainer.snapshotView,
+ rightBottomTaskContainer.snapshotView,
widthSize,
heightSize,
splitBoundsConfig,
@@ -94,8 +95,12 @@
override fun inflateViewStubs() {
super.inflateViewStubs()
- findViewById<ViewStub>(R.id.bottomright_task_content_view)
- ?.apply { layoutResource = R.layout.task_content_view }
+ findViewById<ViewStub>(R.id.bottomright_snapshot)
+ ?.apply {
+ layoutResource =
+ if (enableRefactorTaskThumbnail()) R.layout.task_thumbnail
+ else R.layout.task_thumbnail_deprecated
+ }
?.inflate()
findViewById<ViewStub>(R.id.bottomRight_icon)
?.apply {
@@ -123,7 +128,6 @@
listOf(
createTaskContainer(
primaryTask,
- R.id.task_content_view,
R.id.snapshot,
R.id.icon,
R.id.show_windows,
@@ -133,8 +137,7 @@
),
createTaskContainer(
secondaryTask,
- R.id.bottomright_task_content_view,
- R.id.snapshot,
+ R.id.bottomright_snapshot,
R.id.bottomRight_icon,
R.id.show_windows_right,
R.id.bottomRight_digital_wellbeing_toast,
@@ -237,8 +240,8 @@
leftTopTaskContainer.iconView.asView(),
rightBottomTaskContainer.iconView.asView(),
taskIconHeight,
- leftTopTaskContainer.taskContentView.measuredWidth,
- leftTopTaskContainer.taskContentView.measuredHeight,
+ leftTopTaskContainer.snapshotView.measuredWidth,
+ leftTopTaskContainer.snapshotView.measuredHeight,
measuredHeight,
measuredWidth,
isLayoutRtl,
diff --git a/quickstep/src/com/android/quickstep/views/IconAppChipView.kt b/quickstep/src/com/android/quickstep/views/IconAppChipView.kt
index 46ed29b..f4fd127 100644
--- a/quickstep/src/com/android/quickstep/views/IconAppChipView.kt
+++ b/quickstep/src/com/android/quickstep/views/IconAppChipView.kt
@@ -432,6 +432,11 @@
}
}
+ fun reset() {
+ setText(null)
+ setDrawable(null)
+ }
+
override fun asView(): View = this
enum class AppChipStatus {
diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
index afe7e92..0e769d0 100644
--- a/quickstep/src/com/android/quickstep/views/TaskContainer.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
@@ -22,6 +22,7 @@
import android.view.View
import android.view.View.OnClickListener
import com.android.app.tracing.traceSection
+import com.android.launcher3.Flags.enableOverviewIconMenu
import com.android.launcher3.Flags.enableRefactorTaskThumbnail
import com.android.launcher3.model.data.TaskViewItemInfo
import com.android.launcher3.util.SplitConfigurationOptions
@@ -31,7 +32,6 @@
import com.android.quickstep.recents.domain.usecase.ThumbnailPosition
import com.android.quickstep.recents.ui.mapper.TaskUiStateMapper
import com.android.quickstep.recents.ui.viewmodel.TaskData
-import com.android.quickstep.task.thumbnail.TaskContentView
import com.android.quickstep.task.thumbnail.TaskThumbnailView
import com.android.systemui.shared.recents.model.Task
import com.android.systemui.shared.recents.model.ThumbnailData
@@ -40,7 +40,6 @@
class TaskContainer(
val taskView: TaskView,
val task: Task,
- val taskContentView: TaskContentView,
val snapshotView: View,
val iconView: TaskViewIcon,
/**
@@ -112,8 +111,8 @@
fun destroy() =
traceSection("TaskContainer.destroy") {
digitalWellBeingToast?.destroy()
- taskContentView.scaleX = 1f
- taskContentView.scaleY = 1f
+ snapshotView.scaleX = 1f
+ snapshotView.scaleY = 1f
overlay.reset()
if (enableRefactorTaskThumbnail()) {
isThumbnailValid = false
@@ -122,6 +121,10 @@
} else {
thumbnailViewDeprecated.setShowSplashForSplitSelection(false)
}
+
+ if (enableOverviewIconMenu()) {
+ (iconView as IconAppChipView).reset()
+ }
}
fun setOverlayEnabled(enabled: Boolean) {
@@ -174,9 +177,13 @@
clickCloseListener: OnClickListener?,
) =
traceSection("TaskContainer.setState") {
- taskContentView.setState(
- TaskUiStateMapper.toTaskHeaderState(state, hasHeader, clickCloseListener),
- TaskUiStateMapper.toTaskThumbnailUiState(state, liveTile),
+ thumbnailView.setState(
+ TaskUiStateMapper.toTaskThumbnailUiState(
+ state,
+ liveTile,
+ hasHeader,
+ clickCloseListener,
+ ),
state?.taskId,
)
thumbnailData = if (state is TaskData.Data) state.thumbnailData else null
diff --git a/quickstep/src/com/android/quickstep/views/TaskHeaderView.kt b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewHeader.kt
similarity index 63%
rename from quickstep/src/com/android/quickstep/views/TaskHeaderView.kt
rename to quickstep/src/com/android/quickstep/views/TaskThumbnailViewHeader.kt
index 1fda5a3..9a8805b 100644
--- a/quickstep/src/com/android/quickstep/views/TaskHeaderView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewHeader.kt
@@ -18,33 +18,23 @@
import android.content.Context
import android.util.AttributeSet
+import android.widget.FrameLayout
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
-import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.core.view.isGone
import com.android.launcher3.R
-import com.android.quickstep.task.thumbnail.TaskHeaderUiState
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.ThumbnailHeader
-class TaskHeaderView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
- ConstraintLayout(context, attrs) {
+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) }
private val headerCloseButton: ImageButton by lazy { findViewById(R.id.header_close_button) }
- fun setState(taskHeaderState: TaskHeaderUiState) {
- when (taskHeaderState) {
- is TaskHeaderUiState.ShowHeader -> {
- setHeader(taskHeaderState.header)
- isGone = false
- }
- TaskHeaderUiState.HideHeader -> isGone = true
- }
- }
-
- private fun setHeader(header: TaskHeaderUiState.ThumbnailHeader) {
- headerTitleView.text = header.title
+ fun setHeader(header: ThumbnailHeader) {
+ headerTitleView.setText(header.title)
headerIconView.setImageDrawable(header.icon)
headerCloseButton.setOnClickListener(header.clickCloseListener)
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index b1561fa..fa3fd91 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -89,7 +89,6 @@
import com.android.quickstep.recents.ui.viewmodel.TaskData
import com.android.quickstep.recents.ui.viewmodel.TaskTileUiState
import com.android.quickstep.recents.ui.viewmodel.TaskViewModel
-import com.android.quickstep.task.thumbnail.TaskContentView
import com.android.quickstep.util.ActiveGestureErrorDetector
import com.android.quickstep.util.ActiveGestureLog
import com.android.quickstep.util.BorderAnimator
@@ -97,8 +96,8 @@
import com.android.quickstep.util.RecentsOrientedState
import com.android.quickstep.util.TaskCornerRadius
import com.android.quickstep.util.TaskRemovedDuringLaunchListener
-import com.android.quickstep.util.displayId
import com.android.quickstep.util.isExternalDisplay
+import com.android.quickstep.util.safeDisplayId
import com.android.quickstep.views.IconAppChipView.AppChipStatus
import com.android.quickstep.views.OverviewActionsView.DISABLED_NO_THUMBNAIL
import com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED
@@ -155,7 +154,7 @@
get() = this === recentsView?.selectedTaskView
open val displayId: Int
- get() = taskContainers.firstOrNull()?.task.displayId
+ get() = taskContainers.firstOrNull()?.task.safeDisplayId
val isExternalDisplay: Boolean
get() = displayId.isExternalDisplay
@@ -767,10 +766,13 @@
}
protected open fun inflateViewStubs() {
- findViewById<ViewStub>(R.id.task_content_view)
- ?.apply { layoutResource = R.layout.task_content_view }
+ findViewById<ViewStub>(R.id.snapshot)
+ ?.apply {
+ layoutResource =
+ if (enableRefactorTaskThumbnail()) R.layout.task_thumbnail
+ else R.layout.task_thumbnail_deprecated
+ }
?.inflate()
-
findViewById<ViewStub>(R.id.icon)
?.apply {
layoutResource =
@@ -917,7 +919,6 @@
listOf(
createTaskContainer(
task,
- R.id.task_content_view,
R.id.snapshot,
R.id.icon,
R.id.show_windows,
@@ -952,9 +953,9 @@
taskContainers.forEach { container ->
container.bind()
if (enableRefactorTaskThumbnail()) {
- container.taskContentView.cornerRadius =
+ container.thumbnailView.cornerRadius =
thumbnailFullscreenParams.currentCornerRadius
- container.taskContentView.doOnSizeChange { width, height ->
+ container.thumbnailView.doOnSizeChange { width, height ->
updateThumbnailValidity(container)
val thumbnailPosition = updateThumbnailMatrix(container, width, height)
container.refreshOverlay(thumbnailPosition)
@@ -981,7 +982,6 @@
protected fun createTaskContainer(
task: Task,
- @IdRes taskContentViewId: Int,
@IdRes thumbnailViewId: Int,
@IdRes iconViewId: Int,
@IdRes showWindowViewId: Int,
@@ -991,12 +991,10 @@
): TaskContainer =
traceSection("TaskView.createTaskContainer") {
val iconView = findViewById<View>(iconViewId) as TaskViewIcon
- val taskContentView = findViewById<TaskContentView>(taskContentViewId)
return TaskContainer(
this,
task,
- taskContentView,
- taskContentView.findViewById(thumbnailViewId),
+ findViewById(thumbnailViewId),
iconView,
TransformingTouchDelegate(iconView.asView()),
stagePosition,
@@ -1085,7 +1083,7 @@
protected open fun updateThumbnailSize() {
// TODO(b/271468547), we should default to setting translations only on the snapshot instead
// of a hybrid of both margins and translations
- firstTaskContainer?.taskContentView?.updateLayoutParams<LayoutParams> {
+ firstTaskContainer?.snapshotView?.updateLayoutParams<LayoutParams> {
topMargin = container.deviceProfile.overviewTaskThumbnailTopMarginPx
}
taskContainers.forEach { it.digitalWellBeingToast?.setupLayout() }
@@ -1099,11 +1097,11 @@
val thumbnailBounds = Rect()
if (relativeToDragLayer) {
container.dragLayer.getDescendantRectRelativeToSelf(
- it.taskContentView,
+ it.snapshotView,
thumbnailBounds,
)
} else {
- thumbnailBounds.set(it.taskContentView)
+ thumbnailBounds.set(it.snapshotView)
}
bounds.union(thumbnailBounds)
}
@@ -1817,7 +1815,7 @@
updateFullscreenParams(thumbnailFullscreenParams)
taskContainers.forEach {
if (enableRefactorTaskThumbnail()) {
- it.taskContentView.cornerRadius = thumbnailFullscreenParams.currentCornerRadius
+ it.thumbnailView.cornerRadius = thumbnailFullscreenParams.currentCornerRadius
} else {
it.thumbnailViewDeprecated.setFullscreenParams(thumbnailFullscreenParams)
}
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/SplashHelper.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/SplashHelper.kt
deleted file mode 100644
index 8cc09d4..0000000
--- a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/SplashHelper.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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.task.thumbnail
-
-import android.graphics.Bitmap
-import android.graphics.Canvas
-import android.graphics.Color
-import android.graphics.Paint
-
-object SplashHelper {
- private val BITMAP_RECT_COLORS = listOf(Color.GREEN, Color.RED, Color.BLUE, Color.CYAN)
-
- fun createSplash(): Bitmap = createBitmap(width = 20, height = 20, rectColorRotation = 1)
-
- fun createBitmap(width: Int, height: Int, rectColorRotation: Int = 0): Bitmap =
- Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).apply {
- Canvas(this).apply {
- val paint = Paint()
- paint.color = BITMAP_RECT_COLORS[rectColorRotation % 4]
- drawRect(0f, 0f, width / 2f, height / 2f, paint)
- paint.color = BITMAP_RECT_COLORS[(1 + rectColorRotation) % 4]
- drawRect(width / 2f, 0f, width.toFloat(), height / 2f, paint)
- paint.color = BITMAP_RECT_COLORS[(2 + rectColorRotation) % 4]
- drawRect(0f, height / 2f, width / 2f, height.toFloat(), paint)
- paint.color = BITMAP_RECT_COLORS[(3 + rectColorRotation) % 4]
- drawRect(width / 2f, height / 2f, width.toFloat(), height.toFloat(), paint)
- }
- }
-}
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskContentViewScreenshotTest.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskContentViewScreenshotTest.kt
deleted file mode 100644
index 7b1e445..0000000
--- a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskContentViewScreenshotTest.kt
+++ /dev/null
@@ -1,124 +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.task.thumbnail
-
-import android.content.Context
-import android.graphics.Color
-import android.graphics.drawable.BitmapDrawable
-import android.platform.test.flag.junit.SetFlagsRule
-import android.view.LayoutInflater
-import com.android.launcher3.Flags
-import com.android.launcher3.R
-import com.android.launcher3.util.rule.setFlags
-import com.android.quickstep.task.thumbnail.SplashHelper.createSplash
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
-import com.google.android.apps.nexuslauncher.imagecomparison.goldenpathmanager.ViewScreenshotGoldenPathManager
-import org.junit.Before
-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 [TaskContentView]. */
-@RunWith(ParameterizedAndroidJunit4::class)
-class TaskContentViewScreenshotTest(emulationSpec: DeviceEmulationSpec) {
-
- @get:Rule(order = 0) val setFlagsRule = SetFlagsRule()
-
- @get:Rule(order = 1)
- val screenshotRule =
- ViewScreenshotTestRule(
- emulationSpec,
- ViewScreenshotGoldenPathManager(getEmulatedDevicePathConfig(emulationSpec)),
- )
-
- @Before
- fun setUp() {
- setFlagsRule.setFlags(true, Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL)
- }
-
- @Test
- fun taskContentView_recyclesToUninitialized() {
- screenshotRule.screenshotTest("taskContentView_uninitialized") { activity ->
- activity.actionBar?.hide()
- val taskContentView = createTaskContentView(activity)
- taskContentView.setState(
- TaskHeaderUiState.HideHeader,
- BackgroundOnly(Color.YELLOW),
- null,
- )
- taskContentView.onRecycle()
- taskContentView
- }
- }
-
- @Test
- fun taskContentView_shows_thumbnail_and_header() {
- screenshotRule.screenshotTest("taskContentView_shows_thumbnail_and_header") { activity ->
- activity.actionBar?.hide()
- createTaskContentView(activity).apply {
- setState(
- TaskHeaderUiState.ShowHeader(
- TaskHeaderUiState.ThumbnailHeader(
- BitmapDrawable(activity.resources, createSplash()),
- "test",
- ) {}
- ),
- BackgroundOnly(Color.YELLOW),
- null,
- )
- }
- }
- }
-
- @Test
- fun taskContentView_scaled_roundRoundedCorners() {
- screenshotRule.screenshotTest("taskContentView_scaledRoundedCorners") { activity ->
- activity.actionBar?.hide()
- createTaskContentView(activity).apply {
- scaleX = 0.75f
- scaleY = 0.3f
- setState(TaskHeaderUiState.HideHeader, BackgroundOnly(Color.YELLOW), null)
- }
- }
- }
-
- private fun createTaskContentView(context: Context): TaskContentView {
- val taskContentView =
- LayoutInflater.from(context).inflate(R.layout.task_content_view, null, false)
- as TaskContentView
- taskContentView.cornerRadius = CORNER_RADIUS
- return taskContentView
- }
-
- companion object {
- @Parameters(name = "{0}")
- @JvmStatic
- fun getTestSpecs() =
- DeviceEmulationSpec.forDisplays(
- Displays.Phone,
- isDarkTheme = false,
- isLandscape = false,
- )
-
- const val CORNER_RADIUS = 56f
- }
-}
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskHeaderViewScreenshotTest.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskHeaderViewScreenshotTest.kt
deleted file mode 100644
index e30554e..0000000
--- a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskHeaderViewScreenshotTest.kt
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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.task.thumbnail
-
-import android.content.Context
-import android.graphics.drawable.BitmapDrawable
-import android.view.LayoutInflater
-import com.android.launcher3.R
-import com.android.quickstep.task.thumbnail.SplashHelper.createSplash
-import com.android.quickstep.views.TaskHeaderView
-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 [TaskHeaderView]. */
-@RunWith(ParameterizedAndroidJunit4::class)
-class TaskHeaderViewScreenshotTest(emulationSpec: DeviceEmulationSpec) {
- @get:Rule
- val screenshotRule =
- ViewScreenshotTestRule(
- emulationSpec,
- ViewScreenshotGoldenPathManager(getEmulatedDevicePathConfig(emulationSpec)),
- )
-
- @Test
- fun taskHeaderView_showHeader() {
- screenshotRule.screenshotTest("taskHeaderView_showHeader") { activity ->
- activity.actionBar?.hide()
- createTaskHeaderView(activity).apply {
- setState(
- TaskHeaderUiState.ShowHeader(
- TaskHeaderUiState.ThumbnailHeader(
- BitmapDrawable(activity.resources, createSplash()),
- "Example",
- ) {}
- )
- )
- }
- }
- }
-
- private fun createTaskHeaderView(context: Context): TaskHeaderView {
- val taskHeaderView =
- LayoutInflater.from(context).inflate(R.layout.task_header_view, null, false)
- as TaskHeaderView
- return taskHeaderView
- }
-
- companion object {
- @Parameters(name = "{0}")
- @JvmStatic
- fun getTestSpecs() =
- DeviceEmulationSpec.forDisplays(
- Displays.Tablet,
- isDarkTheme = false,
- isLandscape = true,
- )
- }
-}
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
index 45df735..80b2c16 100644
--- a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
+++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
@@ -16,14 +16,16 @@
package com.android.quickstep.task.thumbnail
import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Matrix
+import android.graphics.Paint
import android.graphics.drawable.BitmapDrawable
import android.view.LayoutInflater
import android.view.Surface.ROTATION_0
+import androidx.core.graphics.set
import com.android.launcher3.R
-import com.android.quickstep.task.thumbnail.SplashHelper.createBitmap
-import com.android.quickstep.task.thumbnail.SplashHelper.createSplash
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.SnapshotSplash
@@ -88,25 +90,23 @@
}
@Test
- fun taskThumbnailView_liveTile() {
+ fun taskThumbnailView_liveTile_withoutHeader() {
screenshotRule.screenshotTest("taskThumbnailView_liveTile") { activity ->
activity.actionBar?.hide()
- createTaskThumbnailView(activity).apply { setState(TaskThumbnailUiState.LiveTile) }
+ createTaskThumbnailView(activity).apply {
+ setState(TaskThumbnailUiState.LiveTile.WithoutHeader)
+ }
}
}
@Test
- fun taskThumbnailView_image() {
+ fun taskThumbnailView_image_withoutHeader() {
screenshotRule.screenshotTest("taskThumbnailView_image") { activity ->
activity.actionBar?.hide()
createTaskThumbnailView(activity).apply {
setState(
SnapshotSplash(
- Snapshot(
- createBitmap(VIEW_ENV_WIDTH, VIEW_ENV_HEIGHT),
- ROTATION_0,
- Color.DKGRAY,
- ),
+ Snapshot.WithoutHeader(createBitmap(), ROTATION_0, Color.DKGRAY),
null,
)
)
@@ -115,14 +115,14 @@
}
@Test
- fun taskThumbnailView_image_withImageMatrix() {
+ fun taskThumbnailView_image_withoutHeader_withImageMatrix() {
screenshotRule.screenshotTest("taskThumbnailView_image_withMatrix") { activity ->
activity.actionBar?.hide()
createTaskThumbnailView(activity).apply {
val lessThanHeightMatchingAspectRatio = (VIEW_ENV_HEIGHT / 2) - 200
setState(
SnapshotSplash(
- Snapshot(
+ Snapshot.WithoutHeader(
createBitmap(
width = VIEW_ENV_WIDTH / 2,
height = lessThanHeightMatchingAspectRatio,
@@ -139,17 +139,13 @@
}
@Test
- fun taskThumbnailView_splash() {
+ fun taskThumbnailView_splash_withoutHeader() {
screenshotRule.screenshotTest("taskThumbnailView_partial_splash") { activity ->
activity.actionBar?.hide()
createTaskThumbnailView(activity).apply {
setState(
SnapshotSplash(
- Snapshot(
- createBitmap(VIEW_ENV_WIDTH, VIEW_ENV_HEIGHT),
- ROTATION_0,
- Color.DKGRAY,
- ),
+ Snapshot.WithoutHeader(createBitmap(), ROTATION_0, Color.DKGRAY),
BitmapDrawable(activity.resources, createSplash()),
)
)
@@ -159,14 +155,14 @@
}
@Test
- fun taskThumbnailView_splash_withImageMatrix() {
+ fun taskThumbnailView_splash_withoutHeader_withImageMatrix() {
screenshotRule.screenshotTest("taskThumbnailView_partial_splash_withMatrix") { activity ->
activity.actionBar?.hide()
createTaskThumbnailView(activity).apply {
val lessThanHeightMatchingAspectRatio = (VIEW_ENV_HEIGHT / 2) - 200
setState(
SnapshotSplash(
- Snapshot(
+ Snapshot.WithoutHeader(
createBitmap(
width = VIEW_ENV_WIDTH / 2,
height = lessThanHeightMatchingAspectRatio,
@@ -233,9 +229,31 @@
val taskThumbnailView =
LayoutInflater.from(context).inflate(R.layout.task_thumbnail, null, false)
as TaskThumbnailView
+ taskThumbnailView.cornerRadius = CORNER_RADIUS
return taskThumbnailView
}
+ private fun createSplash() = createBitmap(width = 20, height = 20, rectColorRotation = 1)
+
+ private fun createBitmap(
+ width: Int = VIEW_ENV_WIDTH,
+ height: Int = VIEW_ENV_HEIGHT,
+ rectColorRotation: Int = 0,
+ ) =
+ Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).apply {
+ Canvas(this).apply {
+ val paint = Paint()
+ paint.color = BITMAP_RECT_COLORS[rectColorRotation % 4]
+ drawRect(0f, 0f, width / 2f, height / 2f, paint)
+ paint.color = BITMAP_RECT_COLORS[(1 + rectColorRotation) % 4]
+ drawRect(width / 2f, 0f, width.toFloat(), height / 2f, paint)
+ paint.color = BITMAP_RECT_COLORS[(2 + rectColorRotation) % 4]
+ drawRect(0f, height / 2f, width / 2f, height.toFloat(), paint)
+ paint.color = BITMAP_RECT_COLORS[(3 + rectColorRotation) % 4]
+ drawRect(width / 2f, height / 2f, width.toFloat(), height.toFloat(), paint)
+ }
+ }
+
companion object {
@Parameters(name = "{0}")
@JvmStatic
@@ -246,6 +264,8 @@
isLandscape = false,
)
+ const val CORNER_RADIUS = 56f
+ val BITMAP_RECT_COLORS = listOf(Color.GREEN, Color.RED, Color.BLUE, Color.CYAN)
const val VIEW_ENV_WIDTH = 1440
const val VIEW_ENV_HEIGHT = 3120
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
index d2abed8..42adfec 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
@@ -35,7 +35,6 @@
import com.android.quickstep.TaskOverlayFactory
import com.android.quickstep.TaskOverlayFactory.TaskOverlay
import com.android.quickstep.recents.di.RecentsDependencies
-import com.android.quickstep.task.thumbnail.TaskContentView
import com.android.quickstep.task.thumbnail.TaskThumbnailView
import com.android.quickstep.views.RecentsView
import com.android.quickstep.views.TaskContainer
@@ -199,7 +198,6 @@
return TaskContainer(
taskView,
task,
- mock<TaskContentView>(),
if (enableRefactorTaskThumbnail()) mock<TaskThumbnailView>()
else mock<TaskThumbnailViewDeprecated>(),
mock<TaskViewIcon>(),
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
index cfeade8..ee9505c 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
@@ -36,6 +36,7 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -105,11 +106,12 @@
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- when(mTopTaskTracker.getCachedTopTask(anyBoolean())).thenReturn(mTaskInfo);
+ when(mTopTaskTracker.getCachedTopTask(anyBoolean(), anyInt())).thenReturn(mTaskInfo);
when(mDeviceState.getSquaredTouchSlop()).thenReturn(SQUARED_TOUCH_SLOP);
when(mDelegate.allowInterceptByParent()).thenReturn(true);
mLongPressTriggered.set(false);
- when(mNavHandleLongPressHandler.getLongPressRunnable(any())).thenReturn(mLongPressRunnable);
+ when(mNavHandleLongPressHandler.getLongPressRunnable(any(), anyInt())).thenReturn(
+ mLongPressRunnable);
when(mStatsLogger.withPackageName(any())).thenReturn(mStatsLogger);
when(mStatsLatencyLogger.withInstanceId(any())).thenReturn(mStatsLatencyLogger);
when(mStatsLatencyLogger.withLatency(anyLong())).thenReturn(mStatsLatencyLogger);
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapperTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapperTest.kt
index 6c0d0ed..7ca194a 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapperTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapperTest.kt
@@ -19,120 +19,33 @@
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.drawable.ShapeDrawable
-import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
import android.view.Surface
import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.launcher3.Flags
import com.android.quickstep.recents.ui.viewmodel.TaskData
-import com.android.quickstep.task.thumbnail.TaskHeaderUiState
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.ThumbnailHeader
import com.android.systemui.shared.recents.model.ThumbnailData
import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class TaskUiStateMapperTest {
- @get:Rule val mSetFlagsRule = SetFlagsRule()
-
- /** TaskHeaderUiState */
@Test
- fun taskData_isNull_returns_HideHeader() {
+ fun taskData_isNull_returns_Uninitialized() {
val result =
- TaskUiStateMapper.toTaskHeaderState(
+ TaskUiStateMapper.toTaskThumbnailUiState(
taskData = null,
+ isLiveTile = false,
hasHeader = false,
clickCloseListener = null,
)
- assertThat(result).isEqualTo(TaskHeaderUiState.HideHeader)
- }
-
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
- @Test
- fun explodedFlagDisabled_returnsHideHeader() {
- val inputs =
- listOf(
- TASK_DATA,
- TASK_DATA.copy(thumbnailData = null),
- TASK_DATA.copy(isLocked = true),
- TASK_DATA.copy(title = null),
- )
- val closeCallback = View.OnClickListener {}
- val expected = TaskHeaderUiState.HideHeader
- inputs.forEach { taskData ->
- val result =
- TaskUiStateMapper.toTaskHeaderState(
- taskData = taskData,
- hasHeader = true,
- clickCloseListener = closeCallback,
- )
- assertThat(result).isEqualTo(expected)
- }
- }
-
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
- @Test
- fun taskData_hasHeader_and_taskData_returnsShowHeader() {
- val inputs =
- listOf(
- TASK_DATA,
- TASK_DATA.copy(thumbnailData = null),
- TASK_DATA.copy(isLocked = true),
- TASK_DATA.copy(title = null),
- )
- val closeCallback = View.OnClickListener {}
- val expected =
- TaskHeaderUiState.ShowHeader(
- header =
- TaskHeaderUiState.ThumbnailHeader(
- TASK_ICON,
- TASK_TITLE_DESCRIPTION,
- closeCallback,
- )
- )
- inputs.forEach { taskData ->
- val result =
- TaskUiStateMapper.toTaskHeaderState(
- taskData = taskData,
- hasHeader = true,
- clickCloseListener = closeCallback,
- )
- assertThat(result).isEqualTo(expected)
- }
- }
-
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
- @Test
- fun taskData_hasHeader_emptyTaskData_returns_HideHeader() {
- val inputs =
- listOf(
- TASK_DATA.copy(icon = null),
- TASK_DATA.copy(titleDescription = null),
- TASK_DATA.copy(icon = null, titleDescription = null),
- )
-
- inputs.forEach { taskData ->
- val result =
- TaskUiStateMapper.toTaskHeaderState(
- taskData = taskData,
- hasHeader = true,
- clickCloseListener = {},
- )
- assertThat(result).isEqualTo(TaskHeaderUiState.HideHeader)
- }
- }
-
- /** TaskThumbnailUiState */
- @Test
- fun taskData_isNull_returns_Uninitialized() {
- val result = TaskUiStateMapper.toTaskThumbnailUiState(taskData = null, isLiveTile = false)
assertThat(result).isEqualTo(TaskThumbnailUiState.Uninitialized)
}
@@ -142,20 +55,79 @@
listOf(TASK_DATA, TASK_DATA.copy(thumbnailData = null), TASK_DATA.copy(isLocked = true))
inputs.forEach { input ->
val result =
- TaskUiStateMapper.toTaskThumbnailUiState(taskData = input, isLiveTile = true)
- assertThat(result).isEqualTo(LiveTile)
+ TaskUiStateMapper.toTaskThumbnailUiState(
+ taskData = input,
+ isLiveTile = true,
+ hasHeader = false,
+ clickCloseListener = null,
+ )
+ assertThat(result).isEqualTo(LiveTile.WithoutHeader)
+ }
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
+ @Test
+ fun taskData_isLiveTileWithHeader_returns_LiveTileWithHeader() {
+ val inputs =
+ listOf(
+ TASK_DATA,
+ TASK_DATA.copy(thumbnailData = null),
+ TASK_DATA.copy(isLocked = true),
+ TASK_DATA.copy(title = null),
+ )
+ val closeCallback = View.OnClickListener {}
+ val expected =
+ LiveTile.WithHeader(
+ header = ThumbnailHeader(TASK_ICON, TASK_TITLE_DESCRIPTION, closeCallback)
+ )
+ inputs.forEach { taskData ->
+ val result =
+ TaskUiStateMapper.toTaskThumbnailUiState(
+ taskData = taskData,
+ isLiveTile = true,
+ hasHeader = true,
+ clickCloseListener = closeCallback,
+ )
+ assertThat(result).isEqualTo(expected)
+ }
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
+ @Test
+ fun taskData_isLiveTileWithHeader_missingHeaderData_returns_LiveTileWithoutHeader() {
+ val inputs =
+ listOf(
+ TASK_DATA.copy(icon = null),
+ TASK_DATA.copy(titleDescription = null),
+ TASK_DATA.copy(icon = null, titleDescription = null),
+ )
+
+ inputs.forEach { taskData ->
+ val result =
+ TaskUiStateMapper.toTaskThumbnailUiState(
+ taskData = taskData,
+ isLiveTile = true,
+ hasHeader = true,
+ clickCloseListener = {},
+ )
+ assertThat(result).isEqualTo(LiveTile.WithoutHeader)
}
}
@Test
fun taskData_isStaticTile_returns_SnapshotSplash() {
val result =
- TaskUiStateMapper.toTaskThumbnailUiState(taskData = TASK_DATA, isLiveTile = false)
+ TaskUiStateMapper.toTaskThumbnailUiState(
+ taskData = TASK_DATA,
+ isLiveTile = false,
+ hasHeader = false,
+ clickCloseListener = null,
+ )
val expected =
TaskThumbnailUiState.SnapshotSplash(
snapshot =
- Snapshot(
+ Snapshot.WithoutHeader(
backgroundColor = TASK_BACKGROUND_COLOR,
bitmap = TASK_THUMBNAIL,
thumbnailRotation = Surface.ROTATION_0,
@@ -166,12 +138,72 @@
assertThat(result).isEqualTo(expected)
}
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
+ @Test
+ fun taskData_isStaticTile_withHeader_returns_SnapshotSplashWithHeader() {
+ val inputs = listOf(TASK_DATA, TASK_DATA.copy(title = null))
+ val closeCallback = View.OnClickListener {}
+ val expected =
+ TaskThumbnailUiState.SnapshotSplash(
+ snapshot =
+ Snapshot.WithHeader(
+ backgroundColor = TASK_BACKGROUND_COLOR,
+ bitmap = TASK_THUMBNAIL,
+ thumbnailRotation = Surface.ROTATION_0,
+ header = ThumbnailHeader(TASK_ICON, TASK_TITLE_DESCRIPTION, closeCallback),
+ ),
+ splash = TASK_ICON,
+ )
+ inputs.forEach { taskData ->
+ val result =
+ TaskUiStateMapper.toTaskThumbnailUiState(
+ taskData = taskData,
+ isLiveTile = false,
+ hasHeader = true,
+ clickCloseListener = closeCallback,
+ )
+ assertThat(result).isEqualTo(expected)
+ }
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
+ @Test
+ fun taskData_isStaticTile_missingHeaderData_returns_SnapshotSplashWithoutHeader() {
+ val inputs =
+ listOf(
+ TASK_DATA.copy(titleDescription = null, icon = null),
+ TASK_DATA.copy(titleDescription = null),
+ TASK_DATA.copy(icon = null),
+ )
+ val expected =
+ Snapshot.WithoutHeader(
+ backgroundColor = TASK_BACKGROUND_COLOR,
+ thumbnailRotation = Surface.ROTATION_0,
+ bitmap = TASK_THUMBNAIL,
+ )
+ inputs.forEach { taskData ->
+ val result =
+ TaskUiStateMapper.toTaskThumbnailUiState(
+ taskData = taskData,
+ isLiveTile = false,
+ hasHeader = true,
+ clickCloseListener = {},
+ )
+
+ assertThat(result).isInstanceOf(TaskThumbnailUiState.SnapshotSplash::class.java)
+ result as TaskThumbnailUiState.SnapshotSplash
+ assertThat(result.snapshot).isEqualTo(expected)
+ }
+ }
+
@Test
fun taskData_thumbnailIsNull_returns_BackgroundOnly() {
val result =
TaskUiStateMapper.toTaskThumbnailUiState(
taskData = TASK_DATA.copy(thumbnailData = null),
isLiveTile = false,
+ hasHeader = false,
+ clickCloseListener = null,
)
val expected = TaskThumbnailUiState.BackgroundOnly(TASK_BACKGROUND_COLOR)
@@ -184,6 +216,8 @@
TaskUiStateMapper.toTaskThumbnailUiState(
taskData = TASK_DATA.copy(isLocked = true),
isLiveTile = false,
+ hasHeader = false,
+ clickCloseListener = null,
)
val expected = TaskThumbnailUiState.BackgroundOnly(TASK_BACKGROUND_COLOR)
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt
index 76d36d3..c325af4 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt
@@ -17,8 +17,9 @@
package com.android.quickstep.util
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
-import android.content.Context
import android.content.res.Resources
+import android.view.Display
+import android.view.Display.DEFAULT_DISPLAY
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.launcher3.apppairs.AppPairIcon
import com.android.launcher3.logging.StatsLogManager
@@ -26,6 +27,7 @@
import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT
+import com.android.launcher3.views.ActivityContext
import com.android.quickstep.TopTaskTracker
import com.android.quickstep.TopTaskTracker.CachedTaskInfo
import com.android.systemui.shared.recents.model.Task
@@ -56,7 +58,7 @@
@RunWith(AndroidJUnit4::class)
class AppPairsControllerTest {
- @Mock lateinit var context: Context
+ @Mock lateinit var context: ActivityContext
@Mock lateinit var resources: Resources
@Mock lateinit var splitSelectStateController: SplitSelectStateController
@Mock lateinit var statsLogManager: StatsLogManager
@@ -83,6 +85,7 @@
}
@Mock lateinit var mockAppPairIcon: AppPairIcon
+ @Mock lateinit var mockDisplay: Display
@Mock lateinit var mockTaskbarActivityContext: TaskbarActivityContext
@Mock lateinit var mockTopTaskTracker: TopTaskTracker
@Mock lateinit var mockCachedTaskInfo: CachedTaskInfo
@@ -105,8 +108,10 @@
// Stub methods on appPairsController so that they return mocks
spyAppPairsController = spy(appPairsController)
whenever(mockAppPairIcon.context).thenReturn(mockTaskbarActivityContext)
+ whenever(mockAppPairIcon.display).thenReturn(mockDisplay)
+ whenever(mockDisplay.displayId).thenReturn(DEFAULT_DISPLAY)
doReturn(mockTopTaskTracker).whenever(spyAppPairsController).topTaskTracker
- whenever(mockTopTaskTracker.getCachedTopTask(any())).thenReturn(mockCachedTaskInfo)
+ whenever(mockTopTaskTracker.getCachedTopTask(any(), any())).thenReturn(mockCachedTaskInfo)
whenever(mockTask1.getKey()).thenReturn(mockTaskKey1)
whenever(mockTask2.getKey()).thenReturn(mockTaskKey2)
doNothing().whenever(spyAppPairsController).launchAppPair(any(), any())
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
index 61a6975..8f26795 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
@@ -316,43 +316,6 @@
}
@Test
- fun animateBubbleInForStashed_showAnimationCanceled() {
- setUpBubbleBar()
-
- val handle = View(context)
- val handleAnimator = PhysicsAnimator.getInstance(handle)
- bubbleStashController.handleAnimator = handleAnimator
-
- val animator =
- BubbleBarViewAnimator(
- bubbleBarView,
- bubbleStashController,
- flyoutController,
- bubbleBarParentViewController,
- onExpanded = emptyRunnable,
- onBubbleBarVisible = emptyRunnable,
- animatorScheduler,
- )
-
- InstrumentationRegistry.getInstrumentation().runOnMainSync {
- animator.animateBubbleInForStashed(bubble, isExpanding = false)
- }
-
- // wait for the animation to start
- InstrumentationRegistry.getInstrumentation().runOnMainSync {}
- PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(handleAnimator) { true }
-
- handleAnimator.assertIsRunning()
- assertThat(animator.isAnimating).isTrue()
- assertThat(animatorScheduler.delayedBlock).isNotNull()
-
- handleAnimator.cancel()
- handleAnimator.assertIsNotRunning()
- assertThat(animator.isAnimating).isFalse()
- assertThat(animatorScheduler.delayedBlock).isNull()
- }
-
- @Test
fun animateBubbleInForStashed_autoExpanding() {
setUpBubbleBar()
diff --git a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
index 746f8bb..5f61ba2 100644
--- a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
@@ -37,7 +37,6 @@
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.launcher3.util.TransformingTouchDelegate
import com.android.quickstep.TaskOverlayFactory.TaskOverlay
-import com.android.quickstep.task.thumbnail.TaskContentView
import com.android.quickstep.task.thumbnail.TaskThumbnailView
import com.android.quickstep.views.LauncherRecentsView
import com.android.quickstep.views.RecentsViewContainer
@@ -254,7 +253,6 @@
TaskContainer(
taskView,
task,
- mock<TaskContentView>(),
if (enableRefactorTaskThumbnail()) mock<TaskThumbnailView>()
else mock<TaskThumbnailViewDeprecated>(),
mock<TaskViewIcon>(),
diff --git a/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt b/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt
index 5aaed7d..2db94f6 100644
--- a/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt
@@ -37,7 +37,6 @@
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.launcher3.util.TransformingTouchDelegate
import com.android.quickstep.TaskOverlayFactory.TaskOverlay
-import com.android.quickstep.task.thumbnail.TaskContentView
import com.android.quickstep.task.thumbnail.TaskThumbnailView
import com.android.quickstep.views.LauncherRecentsView
import com.android.quickstep.views.RecentsViewContainer
@@ -247,7 +246,6 @@
TaskContainer(
taskView,
task,
- mock<TaskContentView>(),
if (enableRefactorTaskThumbnail()) mock<TaskThumbnailView>()
else mock<TaskThumbnailViewDeprecated>(),
mock<TaskViewIcon>(),
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 090208a..e17f5b2 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -1950,13 +1950,18 @@
int remainingSpaceOnSide = (availableWidthPxForHotseat - hotseatPlusQSBWidth) / 2;
hotseatBarPadding.set(
- (remainingSpaceOnSide + qsbWidth) + mInsets.left + workspacePadding.left
+ remainingSpaceOnSide + mInsets.left + workspacePadding.left
+ cellLayoutPaddingPx.left,
hotseatBarSizePx - hotseatBarBottomPadding - hotseatCellHeightPx,
remainingSpaceOnSide + mInsets.right + workspacePadding.right
+ cellLayoutPaddingPx.right,
hotseatBarBottomPadding
);
+ if (Utilities.isRtl(context.getResources())) {
+ hotseatBarPadding.right += qsbWidth;
+ } else {
+ hotseatBarPadding.left += qsbWidth;
+ }
} else if (isTaskbarPresent) {
// Center the QSB vertically with hotseat
int hotseatBarBottomPadding = getHotseatBarBottomPadding();
@@ -2077,7 +2082,7 @@
* Returns the number of pixels the hotseat is translated from the bottom of the screen.
*/
private int getHotseatBarBottomPadding() {
- if (isTaskbarPresent) { // QSB on top or inline
+ if (isTaskbarPresent || isQsbInline) { // QSB on top or inline
return hotseatBarBottomSpacePx - (Math.abs(hotseatCellHeightPx - iconSizePx) / 2);
} else {
return hotseatBarSizePx - hotseatCellHeightPx;
diff --git a/src/com/android/launcher3/ModelCallbacks.kt b/src/com/android/launcher3/ModelCallbacks.kt
index 32b47d0..f38dc41 100644
--- a/src/com/android/launcher3/ModelCallbacks.kt
+++ b/src/com/android/launcher3/ModelCallbacks.kt
@@ -95,7 +95,7 @@
synchronouslyBoundPages = boundPages
pagesToBindSynchronously = LIntSet()
clearPendingBinds()
- if (!launcher.isInState(LauncherState.ALL_APPS)) {
+ if (!launcher.isInState(LauncherState.ALL_APPS) && !Flags.enableWorkspaceInflation()) {
launcher.appsView.appsStore.enableDeferUpdates(AllAppsStore.DEFER_UPDATES_NEXT_DRAW)
pendingTasks.add {
launcher.appsView.appsStore.disableDeferUpdates(
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index f60896e..3d8ebbc 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -575,6 +575,7 @@
}
protected void rebindAdapters(boolean force) {
+ Log.d(TAG, "rebindAdapters: force: " + force);
if (mSearchTransitionController.isRunning()) {
mRebindAdaptersAfterSearchAnimation = true;
return;
@@ -583,6 +584,7 @@
boolean showTabs = shouldShowTabs();
if (showTabs == mUsingTabs && !force) {
+ Log.d(TAG, "rebindAdapters: Not needed.");
return;
}
@@ -678,6 +680,7 @@
}
private void replaceAppsRVContainer(boolean showTabs) {
+ Log.d(TAG, "replaceAppsRVContainer: showTabs: " + showTabs);
for (int i = AdapterHolder.MAIN; i <= AdapterHolder.WORK; i++) {
AdapterHolder adapterHolder = mAH.get(i);
if (adapterHolder.mRecyclerView != null) {
@@ -711,7 +714,6 @@
mWorkManager.reset();
post(() -> mAH.get(AdapterHolder.WORK).applyPadding());
-
} else {
mWorkManager.detachWorkUtilityViews();
mViewPager = null;
@@ -1017,6 +1019,7 @@
@VisibleForTesting
public void onAppsUpdated() {
+ Log.d(TAG, "onAppsUpdated; number of apps: " + mAllAppsStore.getApps().length);
mHasWorkApps = Stream.of(mAllAppsStore.getApps())
.anyMatch(mWorkManager.getItemInfoMatcher());
mHasPrivateApps = Stream.of(mAllAppsStore.getApps())
diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java
index d5a4022..821027e 100644
--- a/src/com/android/launcher3/allapps/AllAppsStore.java
+++ b/src/com/android/launcher3/allapps/AllAppsStore.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.os.UserHandle;
+import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -53,6 +54,7 @@
*/
public class AllAppsStore<T extends Context & ActivityContext> {
+ private static final String TAG = "AllAppsStore";
// Defer updates flag used to defer all apps updates to the next draw.
public static final int DEFER_UPDATES_NEXT_DRAW = 1 << 0;
// Defer updates flag used to defer all apps updates by a test's request.
@@ -102,6 +104,7 @@
public void setApps(@Nullable AppInfo[] apps, int flags, Map<PackageUserKey, Integer> map,
boolean shouldPreinflate) {
mApps = apps == null ? EMPTY_ARRAY : apps;
+ Log.d(TAG, "setApps: apps.length=" + mApps.length);
mModelFlags = flags;
notifyUpdate();
mPackageUserKeytoUidMap = map;
@@ -159,10 +162,12 @@
public void enableDeferUpdates(int flag) {
mDeferUpdatesFlags |= flag;
+ Log.d(TAG, "enableDeferUpdates: " + flag + " mDeferUpdatesFlags=" + mDeferUpdatesFlags);
}
public void disableDeferUpdates(int flag) {
mDeferUpdatesFlags &= ~flag;
+ Log.d(TAG, "disableDeferUpdates: " + flag + " mDeferUpdatesFlags=" + mDeferUpdatesFlags);
if (mDeferUpdatesFlags == 0 && mUpdatePending) {
notifyUpdate();
mUpdatePending = false;
@@ -171,6 +176,9 @@
public void disableDeferUpdatesSilently(int flag) {
mDeferUpdatesFlags &= ~flag;
+ Log.d(TAG, "disableDeferUpdatesSilently: " + flag
+ + " mDeferUpdatesFlags=" + mDeferUpdatesFlags);
+
}
public int getDeferUpdatesFlags() {
@@ -179,9 +187,11 @@
private void notifyUpdate() {
if (mDeferUpdatesFlags != 0) {
+ Log.d(TAG, "notifyUpdate: deferring update");
mUpdatePending = true;
return;
}
+ Log.d(TAG, "notifyUpdate: notifying listeners");
for (OnUpdateListener listener : mUpdateListeners) {
listener.onAppsUpdated();
}
diff --git a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
index 929e52e..813ed3e 100644
--- a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
+++ b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
@@ -38,7 +38,6 @@
import androidx.annotation.UiThread;
import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.folder.PreviewBackground;
import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.views.ActivityContext;
@@ -143,7 +142,6 @@
icon.getPreviewBounds(sTmpRect);
final int previewSize = sTmpRect.width();
- PreviewBackground bg = icon.getFolderBackground();
final int margin = (size - previewSize) / 2;
final float previewShiftX = -sTmpRect.left + margin;
final float previewShiftY = -sTmpRect.top + margin;
@@ -162,11 +160,10 @@
foregroundCanvas.restore();
// Draw background
- Paint backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- backgroundPaint.setColor(bg.getBgColor());
- bg.drawShadow(backgroundCanvas);
- backgroundCanvas.drawPaint(backgroundPaint);
- bg.drawBackgroundStroke(backgroundCanvas);
+ backgroundCanvas.save();
+ backgroundCanvas.translate(previewShiftX, previewShiftY);
+ icon.getFolderBackground().drawBackground(backgroundCanvas);
+ backgroundCanvas.restore();
}
@Override
diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java
index c4bbae4..1d19a17 100644
--- a/src/com/android/launcher3/model/BaseLauncherBinder.java
+++ b/src/com/android/launcher3/model/BaseLauncherBinder.java
@@ -323,8 +323,10 @@
Executor pendingExecutor = pendingTasks::add;
RunnableList onCompleteSignal = new RunnableList();
+ onCompleteSignal.add(() -> Log.d(TAG, "Calling onCompleteSignal"));
if (enableWorkspaceInflation() && inflater != null) {
+ Log.d(TAG, "Starting async inflation");
MODEL_EXECUTOR.execute(() -> {
inflateAsyncAndBind(otherWorkspaceItems, inflater, pendingExecutor);
inflateAsyncAndBind(otherAppWidgets, inflater, pendingExecutor);
@@ -335,6 +337,7 @@
MAIN_EXECUTOR.execute(onCompleteSignal::executeAllAndDestroy);
});
} else {
+ Log.d(TAG, "Starting sync inflation");
bindItemsInChunks(otherWorkspaceItems, ITEMS_CHUNK, pendingExecutor);
bindItemsInChunks(otherAppWidgets, 1, pendingExecutor);
setupPendingBind(currentScreenIds, pendingExecutor);
diff --git a/src/com/android/launcher3/util/ViewOnDrawExecutor.java b/src/com/android/launcher3/util/ViewOnDrawExecutor.java
index 26bfd36..dad7629 100644
--- a/src/com/android/launcher3/util/ViewOnDrawExecutor.java
+++ b/src/com/android/launcher3/util/ViewOnDrawExecutor.java
@@ -16,6 +16,7 @@
package com.android.launcher3.util;
+import android.util.Log;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.ViewTreeObserver.OnDrawListener;
@@ -32,6 +33,7 @@
public class ViewOnDrawExecutor implements OnDrawListener, Runnable,
OnAttachStateChangeListener {
+ private static final String TAG = "ViewOnDrawExecutor";
private final RunnableList mTasks;
private final Consumer<ViewOnDrawExecutor> mOnClearCallback;
private View mAttachedView;
@@ -88,7 +90,10 @@
* Executes all tasks immediately
*/
public void markCompleted() {
- if (!mCancelled) {
+ if (mCancelled) {
+ Log.d(TAG, "markCompleted ignored: cancelled");
+ } else {
+ Log.d(TAG, "markCompleted: executing tasks");
mTasks.executeAllAndDestroy();
}
mCompleted = true;
@@ -101,6 +106,7 @@
}
public void cancel() {
+ Log.d(TAG, "Cancelling tasks");
mCancelled = true;
markCompleted();
}
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 02c6630..8fbb5e3 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -73,18 +73,16 @@
return getCombinedSplitTaskHeight();
}
- if (isDesktop()) {
- return getTaskSnapshot(DESKTOP).getVisibleBounds().height();
- }
- return getTaskSnapshot(DEFAULT).getVisibleBounds().height();
+ UiObject2 taskSnapshot1 = findObjectInTask((isDesktop() ? DESKTOP : DEFAULT).snapshotRes);
+ return taskSnapshot1.getVisibleBounds().height();
}
/**
* Calculates the visible height for split tasks, containing 2 snapshot tiles and a divider.
*/
private int getCombinedSplitTaskHeight() {
- UiObject2 taskSnapshot1 = getTaskSnapshot(SPLIT_TOP_OR_LEFT);
- UiObject2 taskSnapshot2 = getTaskSnapshot(SPLIT_BOTTOM_OR_RIGHT);
+ UiObject2 taskSnapshot1 = findObjectInTask(SPLIT_TOP_OR_LEFT.snapshotRes);
+ UiObject2 taskSnapshot2 = findObjectInTask(SPLIT_BOTTOM_OR_RIGHT.snapshotRes);
// If the split task is partly off screen, taskSnapshot1 can be invisible.
if (taskSnapshot1 == null) {
@@ -99,6 +97,34 @@
return bottom - top;
}
+ /**
+ * Returns the width of the visible task, or the combined width of two tasks in split with a
+ * divider between.
+ */
+ int getVisibleWidth() {
+ if (isGrouped()) {
+ return getCombinedSplitTaskWidth();
+ }
+
+ UiObject2 taskSnapshot1 = findObjectInTask(DEFAULT.snapshotRes);
+ return taskSnapshot1.getVisibleBounds().width();
+ }
+
+ /**
+ * Calculates the visible width for split tasks, containing 2 snapshot tiles and a divider.
+ */
+ private int getCombinedSplitTaskWidth() {
+ UiObject2 taskSnapshot1 = findObjectInTask(SPLIT_TOP_OR_LEFT.snapshotRes);
+ UiObject2 taskSnapshot2 = findObjectInTask(SPLIT_BOTTOM_OR_RIGHT.snapshotRes);
+
+ int left = Math.min(
+ taskSnapshot1.getVisibleBounds().left, taskSnapshot2.getVisibleBounds().left);
+ int right = Math.max(
+ taskSnapshot1.getVisibleBounds().right, taskSnapshot2.getVisibleBounds().right);
+
+ return right - left;
+ }
+
public int getTaskCenterX() {
return mTask.getVisibleCenter().x;
}
@@ -116,22 +142,6 @@
}
/**
- * Returns the task snapshot (thumbnail) for the given `OverviewTaskContainer`.
- *
- * For some reason `BySelector` does not work with `hasChild` or `hasParent` so instead we
- * grab all the views matching the id: "snapshot" and filter for the correct parent.
- */
- private UiObject2 getTaskSnapshot(OverviewTaskContainer overviewTaskContainer) {
- BySelector snapshotSelector = mLauncher.getOverviewObjectSelector("snapshot");
- List<UiObject2> snapshots = mTask.findObjects(snapshotSelector);
- return snapshots.stream()
- .filter(snapshot -> snapshot.getParent().getResourceName()
- .contains(overviewTaskContainer.taskContentViewRes))
- .findFirst()
- .orElse(snapshots.getFirst());
- }
-
- /**
* Dismisses the task by swiping up.
*/
public void dismiss() {
@@ -294,13 +304,17 @@
}
}
+ private UiObject2 findObjectInTask(String resName) {
+ return mTask.findObject(mLauncher.getOverviewObjectSelector(resName));
+ }
+
/**
* Returns whether the given String is contained in this Task's contentDescription. Also returns
* true if both Strings are null.
*/
public boolean containsContentDescription(String expected,
OverviewTaskContainer overviewTaskContainer) {
- String actual = getTaskSnapshot(overviewTaskContainer).getContentDescription();
+ String actual = findObjectInTask(overviewTaskContainer.snapshotRes).getContentDescription();
if (actual == null && expected == null) {
return true;
}
@@ -346,19 +360,19 @@
*/
public enum OverviewTaskContainer {
// The main task when the task is not split.
- DEFAULT("task_content_view", "icon"),
+ DEFAULT("snapshot", "icon"),
// The first task in split task.
- SPLIT_TOP_OR_LEFT("task_content_view", "icon"),
+ SPLIT_TOP_OR_LEFT("snapshot", "icon"),
// The second task in split task.
- SPLIT_BOTTOM_OR_RIGHT("bottomright_task_content_view", "bottomRight_icon"),
+ SPLIT_BOTTOM_OR_RIGHT("bottomright_snapshot", "bottomRight_icon"),
// The desktop task.
DESKTOP("background", "icon");
- public final String taskContentViewRes;
+ public final String snapshotRes;
public final String iconAppRes;
- OverviewTaskContainer(String taskContentViewRes, String iconAppRes) {
- this.taskContentViewRes = taskContentViewRes;
+ OverviewTaskContainer(String snapshotRes, String iconAppRes) {
+ this.snapshotRes = snapshotRes;
this.iconAppRes = iconAppRes;
}
}