diff --git a/quickstep/res/layout/split_instructions_view.xml b/quickstep/res/layout/split_instructions_view.xml index 797ea45be4..a11974c858 100644 --- a/quickstep/res/layout/split_instructions_view.xml +++ b/quickstep/res/layout/split_instructions_view.xml @@ -41,5 +41,6 @@ android:textColor="?androidprv:attr/textColorOnAccent" android:layout_marginStart="@dimen/split_instructions_start_margin_cancel" android:text="@string/toast_split_select_app_cancel" + android:textStyle="bold" android:visibility="gone"/> \ No newline at end of file diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml index ac1a50a5d8..34193d34c6 100644 --- a/quickstep/res/layout/task.xml +++ b/quickstep/res/layout/task.xml @@ -19,7 +19,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" xmlns:launcher="http://schemas.android.com/apk/res-auto" - android:id="@+id/task" + android:id="@+id/task_view_single" android:layout_width="match_parent" android:layout_height="match_parent" android:clipChildren="false" diff --git a/quickstep/res/layout/task_desktop.xml b/quickstep/res/layout/task_desktop.xml index 64aa7e14ed..8c7090e41a 100644 --- a/quickstep/res/layout/task_desktop.xml +++ b/quickstep/res/layout/task_desktop.xml @@ -19,7 +19,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" xmlns:launcher="http://schemas.android.com/apk/res-auto" - android:id="@+id/task" + android:id="@+id/task_view_desktop" android:layout_width="match_parent" android:layout_height="match_parent" android:clipChildren="true" diff --git a/quickstep/res/layout/task_grouped.xml b/quickstep/res/layout/task_grouped.xml index da2b29f9c6..cb4b98fd4c 100644 --- a/quickstep/res/layout/task_grouped.xml +++ b/quickstep/res/layout/task_grouped.xml @@ -24,7 +24,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" xmlns:launcher="http://schemas.android.com/apk/res-auto" - android:id="@+id/task" + android:id="@+id/task_view_grouped" android:layout_width="match_parent" android:layout_height="match_parent" android:clipChildren="false" diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml index 8bcbb33b85..037a0f6d72 100644 --- a/quickstep/res/values/strings.xml +++ b/quickstep/res/values/strings.xml @@ -237,7 +237,7 @@ Tap another app to use split screen Choose another app to use split screen - Cancel + Cancel Exit split screen selection Choose another app to use split screen diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java index 2cdc0ced86..83123b507b 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java @@ -303,7 +303,8 @@ public class BubbleBarViewController { /** Whether a new bubble is animating. */ public boolean isAnimatingNewBubble() { - return mBarView.isAnimatingNewBubble(); + return mBarView.isAnimatingNewBubble() + || (mBubbleBarViewAnimator != null && mBubbleBarViewAnimator.hasAnimatingBubble()); } /** The horizontal margin of the bubble bar from the edge of the screen. */ @@ -575,14 +576,14 @@ public class BubbleBarViewController { } boolean persistentTaskbarOrOnHome = mBubbleStashController.isBubblesShowingOnHome() || !mBubbleStashController.isTransientTaskBar(); - if (persistentTaskbarOrOnHome && !isExpanding && !isExpanded()) { - mBubbleBarViewAnimator.animateBubbleBarForCollapsed(bubble); + if (persistentTaskbarOrOnHome && !isExpanded()) { + mBubbleBarViewAnimator.animateBubbleBarForCollapsed(bubble, isExpanding); return; } // only animate the new bubble if we're in an app, have handle view and not auto expanding - if (isInApp && !isExpanding && mBubbleStashController.getHasHandleView() && !isExpanded()) { - mBubbleBarViewAnimator.animateBubbleInForStashed(bubble); + if (isInApp && mBubbleStashController.getHasHandleView() && !isExpanded()) { + mBubbleBarViewAnimator.animateBubbleInForStashed(bubble, isExpanding); } } @@ -626,6 +627,10 @@ public class BubbleBarViewController { * from SystemUI. */ public void setExpandedFromSysui(boolean isExpanded) { + if (isAnimatingNewBubble() && isExpanded) { + mBubbleBarViewAnimator.expandedWhileAnimating(); + return; + } if (!isExpanded) { mBubbleStashController.stashBubbleBar(); } else { diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java index 52f5a29401..8158fe79b4 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java @@ -245,6 +245,11 @@ public class BubbleStashedHandleViewController { mStashedHandleView.setTranslationY(transY); } + /** Returns the translation of the stashed handle. */ + public float getTranslationY() { + return mStashedHandleView.getTranslationY(); + } + /** * Used by {@link BubbleStashController} to animate the handle when stashing or un stashing. */ diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt index 0a0cfd007b..b745193e2b 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt @@ -43,6 +43,8 @@ constructor( private val bubbleBarBounceDistanceInPx = bubbleBarView.resources.getDimensionPixelSize(R.dimen.bubblebar_bounce_distance) + fun hasAnimatingBubble() = animatingBubble != null + private companion object { /** The time to show the flyout. */ const val FLYOUT_DELAY_MS: Long = 2500 @@ -58,8 +60,33 @@ constructor( private data class AnimatingBubble( val bubbleView: BubbleView, val showAnimation: Runnable, - val hideAnimation: Runnable - ) + val hideAnimation: Runnable, + val expand: Boolean, + val state: State = State.CREATED + ) { + + /** + * The state of the animation. + * + * The animation is initially created but will be scheduled later using the [Scheduler]. + * + * The normal uninterrupted cycle is for the bubble notification to animate in, then be in a + * transient state and eventually to animate out. + * + * However different events, such as touch and external signals, may cause the animation to + * end earlier. + */ + enum class State { + /** The animation is created but not started yet. */ + CREATED, + /** The bubble notification is animating in. */ + ANIMATING_IN, + /** The bubble notification is now fully showing and waiting to be hidden. */ + IN, + /** The bubble notification is animating out. */ + ANIMATING_OUT + } + } /** An interface for scheduling jobs. */ interface Scheduler { @@ -97,15 +124,18 @@ constructor( ) /** Animates a bubble for the state where the bubble bar is stashed. */ - fun animateBubbleInForStashed(b: BubbleBarBubble) { + fun animateBubbleInForStashed(b: BubbleBarBubble, isExpanding: Boolean) { + // TODO b/346400677: handle animations for the same bubble interrupting each other + if (animatingBubble?.bubbleView?.bubble?.key == b.key) return val bubbleView = b.view val animator = PhysicsAnimator.getInstance(bubbleView) if (animator.isRunning()) animator.cancel() // the animation of a new bubble is divided into 2 parts. The first part shows the bubble // and the second part hides it after a delay. val showAnimation = buildHandleToBubbleBarAnimation() - val hideAnimation = buildBubbleBarToHandleAnimation() - animatingBubble = AnimatingBubble(bubbleView, showAnimation, hideAnimation) + val hideAnimation = if (isExpanding) Runnable {} else buildBubbleBarToHandleAnimation() + animatingBubble = + AnimatingBubble(bubbleView, showAnimation, hideAnimation, expand = isExpanding) scheduler.post(showAnimation) scheduler.postDelayed(FLYOUT_DELAY_MS, hideAnimation) } @@ -125,6 +155,7 @@ constructor( * visible which helps avoiding further updates when we re-enter the second part. */ private fun buildHandleToBubbleBarAnimation() = Runnable { + moveToState(AnimatingBubble.State.ANIMATING_IN) // prepare the bubble bar for the animation bubbleBarView.onAnimatingBubbleStarted() bubbleBarView.visibility = VISIBLE @@ -138,9 +169,12 @@ constructor( // handle. when the handle becomes invisible and we start animating in the bubble bar, // the translation y is offset by this value to make the transition from the handle to the // bar smooth. - val offset: Float = bubbleStashController.getDiffBetweenHandleAndBarCenters() - val stashedHandleTranslationY: Float = + val offset = bubbleStashController.getDiffBetweenHandleAndBarCenters() + val stashedHandleTranslationYForAnimation = bubbleStashController.getStashedHandleTranslationForNewBubbleAnimation() + val stashedHandleTranslationY = + bubbleStashController.getHandleTranslationY() ?: return@Runnable + val translationTracker = TranslationTracker(stashedHandleTranslationY) // this is the total distance that both the stashed handle and the bubble will be traveling // at the end of the animation the bubble bar will be positioned in the same place when it @@ -150,15 +184,14 @@ constructor( animator.setDefaultSpringConfig(springConfig) animator.spring(DynamicAnimation.TRANSLATION_Y, totalTranslationY) animator.addUpdateListener { handle, values -> - val ty: Float = - values[DynamicAnimation.TRANSLATION_Y]?.value ?: return@addUpdateListener + val ty = values[DynamicAnimation.TRANSLATION_Y]?.value ?: return@addUpdateListener when { - ty >= stashedHandleTranslationY -> { + ty >= stashedHandleTranslationYForAnimation -> { // we're in the first leg of the animation. only animate the handle. the bubble // bar remains hidden during this part of the animation // map the path [0, stashedHandleTranslationY] to [0,1] - val fraction = ty / stashedHandleTranslationY + val fraction = ty / stashedHandleTranslationYForAnimation handle.alpha = 1 - fraction } ty >= totalTranslationY -> { @@ -172,8 +205,8 @@ constructor( if (bubbleBarView.alpha != 1f) { // map the path [stashedHandleTranslationY, totalTranslationY] to [0, 1] val fraction = - (ty - stashedHandleTranslationY) / - (totalTranslationY - stashedHandleTranslationY) + (ty - stashedHandleTranslationYForAnimation) / + (totalTranslationY - stashedHandleTranslationYForAnimation) bubbleBarView.alpha = fraction bubbleBarView.scaleY = BUBBLE_ANIMATION_INITIAL_SCALE_Y + @@ -193,18 +226,16 @@ constructor( bubbleStashController.updateTaskbarTouchRegion() } } + translationTracker.updateTyAndExpandIfNeeded(ty) } animator.addEndListener { _, _, _, canceled, _, _, _ -> // if the show animation was canceled, also cancel the hide animation. this is typically // canceled in this class, but could potentially be canceled elsewhere. - if (canceled) { - val hideAnimation = animatingBubble?.hideAnimation ?: return@addEndListener - scheduler.cancel(hideAnimation) - animatingBubble = null - bubbleBarView.onAnimatingBubbleCompleted() - bubbleBarView.relativePivotY = 1f + if (canceled || animatingBubble?.expand == true) { + cancelHideAnimation() return@addEndListener } + moveToState(AnimatingBubble.State.IN) // the bubble bar is now fully settled in. update taskbar touch region so it's touchable bubbleStashController.updateTaskbarTouchRegion() } @@ -227,7 +258,8 @@ constructor( */ private fun buildBubbleBarToHandleAnimation() = Runnable { if (animatingBubble == null) return@Runnable - val offset = bubbleStashController.getStashedHandleTranslationForNewBubbleAnimation() + moveToState(AnimatingBubble.State.ANIMATING_OUT) + val offset = bubbleStashController.getDiffBetweenHandleAndBarCenters() val stashedHandleTranslationY = bubbleStashController.getStashedHandleTranslationForNewBubbleAnimation() // this is the total distance that both the stashed handle and the bar will be traveling @@ -281,6 +313,8 @@ constructor( /** Animates to the initial state of the bubble bar, when there are no previous bubbles. */ fun animateToInitialState(b: BubbleBarBubble, isInApp: Boolean, isExpanding: Boolean) { + // TODO b/346400677: handle animations for the same bubble interrupting each other + if (animatingBubble?.bubbleView?.bubble?.key == b.key) return val bubbleView = b.view val animator = PhysicsAnimator.getInstance(bubbleView) if (animator.isRunning()) animator.cancel() @@ -300,12 +334,14 @@ constructor( bubbleStashController.updateTaskbarTouchRegion() } } - animatingBubble = AnimatingBubble(bubbleView, showAnimation, hideAnimation) + animatingBubble = + AnimatingBubble(bubbleView, showAnimation, hideAnimation, expand = isExpanding) scheduler.post(showAnimation) scheduler.postDelayed(FLYOUT_DELAY_MS, hideAnimation) } private fun buildBubbleBarSpringInAnimation() = Runnable { + moveToState(AnimatingBubble.State.ANIMATING_IN) // prepare the bubble bar for the animation bubbleBarView.onAnimatingBubbleStarted() bubbleBarView.translationY = bubbleBarView.height.toFloat() @@ -314,18 +350,31 @@ constructor( bubbleBarView.scaleX = 1f bubbleBarView.scaleY = 1f + val translationTracker = TranslationTracker(bubbleBarView.translationY) + val animator = PhysicsAnimator.getInstance(bubbleBarView) animator.setDefaultSpringConfig(springConfig) animator.spring(DynamicAnimation.TRANSLATION_Y, bubbleStashController.bubbleBarTranslationY) - animator.addUpdateListener { _, _ -> bubbleStashController.updateTaskbarTouchRegion() } + animator.addUpdateListener { _, values -> + val ty = values[DynamicAnimation.TRANSLATION_Y]?.value ?: return@addUpdateListener + translationTracker.updateTyAndExpandIfNeeded(ty) + bubbleStashController.updateTaskbarTouchRegion() + } animator.addEndListener { _, _, _, _, _, _, _ -> + if (animatingBubble?.expand == true) { + cancelHideAnimation() + } else { + moveToState(AnimatingBubble.State.IN) + } // the bubble bar is now fully settled in. update taskbar touch region so it's touchable bubbleStashController.updateTaskbarTouchRegion() } animator.start() } - fun animateBubbleBarForCollapsed(b: BubbleBarBubble) { + fun animateBubbleBarForCollapsed(b: BubbleBarBubble, isExpanding: Boolean) { + // TODO b/346400677: handle animations for the same bubble interrupting each other + if (animatingBubble?.bubbleView?.bubble?.key == b.key) return val bubbleView = b.view val animator = PhysicsAnimator.getInstance(bubbleView) if (animator.isRunning()) animator.cancel() @@ -336,7 +385,8 @@ constructor( bubbleBarView.onAnimatingBubbleCompleted() bubbleStashController.updateTaskbarTouchRegion() } - animatingBubble = AnimatingBubble(bubbleView, showAnimation, hideAnimation) + animatingBubble = + AnimatingBubble(bubbleView, showAnimation, hideAnimation, expand = isExpanding) scheduler.post(showAnimation) scheduler.postDelayed(FLYOUT_DELAY_MS, hideAnimation) } @@ -347,17 +397,29 @@ constructor( * the bubble bar moves back to its initial position with a spring animation. */ private fun buildBubbleBarBounceAnimation() = Runnable { + moveToState(AnimatingBubble.State.ANIMATING_IN) bubbleBarView.onAnimatingBubbleStarted() val ty = bubbleStashController.bubbleBarTranslationY val springBackAnimation = PhysicsAnimator.getInstance(bubbleBarView) springBackAnimation.setDefaultSpringConfig(springConfig) springBackAnimation.spring(DynamicAnimation.TRANSLATION_Y, ty) + springBackAnimation.addEndListener { _, _, _, _, _, _, _ -> + if (animatingBubble?.expand == true) { + bubbleBarView.isExpanded = true + cancelHideAnimation() + } else { + moveToState(AnimatingBubble.State.IN) + } + } // animate the bubble bar up and start the spring back down animation when it ends. ObjectAnimator.ofFloat(bubbleBarView, View.TRANSLATION_Y, ty - bubbleBarBounceDistanceInPx) .withDuration(BUBBLE_BAR_BOUNCE_ANIMATION_DURATION_MS) - .withEndAction { springBackAnimation.start() } + .withEndAction { + if (animatingBubble?.expand == true) bubbleBarView.isExpanded = true + springBackAnimation.start() + } .start() } @@ -386,6 +448,25 @@ constructor( ) } + fun expandedWhileAnimating() { + val animatingBubble = animatingBubble ?: return + this.animatingBubble = animatingBubble.copy(expand = true) + // if we're fully in and waiting to hide, cancel the hide animation and clean up + if (animatingBubble.state == AnimatingBubble.State.IN) { + bubbleBarView.isExpanded = true + cancelHideAnimation() + } + } + + private fun cancelHideAnimation() { + val hideAnimation = animatingBubble?.hideAnimation ?: return + scheduler.cancel(hideAnimation) + animatingBubble = null + bubbleBarView.onAnimatingBubbleCompleted() + bubbleBarView.relativePivotY = 1f + bubbleStashController.showBubbleBarImmediate() + } + private fun PhysicsAnimator?.cancelIfRunning() { if (this?.isRunning() == true) cancel() } @@ -405,4 +486,37 @@ constructor( ) return this } + + private fun moveToState(state: AnimatingBubble.State) { + val animatingBubble = this.animatingBubble ?: return + this.animatingBubble = animatingBubble.copy(state = state) + } + + /** + * Tracks the translation Y of the bubble bar during the animation. When the bubble bar expands + * as part of the animation, the expansion should start after the bubble bar reaches the peak + * position. + */ + private inner class TranslationTracker(initialTy: Float) { + private var previousTy = initialTy + private var startedExpanding = false + private var reachedPeak = false + + fun updateTyAndExpandIfNeeded(ty: Float) { + if (!reachedPeak) { + // the bubble bar is positioned at the bottom of the screen and moves up using + // negative ty values. the peak is reached the first time we see a value that is + // greater than the previous. + if (ty > previousTy) { + reachedPeak = true + } + } + val expand = animatingBubble?.expand ?: false + if (reachedPeak && expand && !startedExpanding) { + bubbleBarView.isExpanded = true + startedExpanding = true + } + previousTy = ty + } + } } 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 0f437449bb..48eb7dea15 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt @@ -143,6 +143,9 @@ interface BubbleStashController { /** Set the translation Y for the stashed handle. */ fun setHandleTranslationY(translationY: Float) + /** Returns the translation of the handle. */ + fun getHandleTranslationY(): Float? + /** * Returns bubble bar Y position according to [isBubblesShowingOnHome] and * [isBubblesShowingOnOverview] values. Default implementation only analyse diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashController.kt index 62fe221c70..1b650195ef 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashController.kt +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashController.kt @@ -198,6 +198,8 @@ class PersistentBubbleStashController( // no op since does not have a handle view } + override fun getHandleTranslationY(): Float? = null + private fun updateExpandedState(expand: Boolean) { if (bubbleBarViewController.isHiddenForNoBubbles) { // If there are no bubbles the bar is invisible, nothing to do here. 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 23e009b3a1..1a4b9823ef 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt @@ -242,6 +242,8 @@ class TransientBubbleStashController( bubbleStashedHandleViewController?.setTranslationYForSwipe(translationY) } + override fun getHandleTranslationY(): Float? = bubbleStashedHandleViewController?.translationY + private fun getStashTranslation(): Float { return (bubbleBarViewController.bubbleBarCollapsedHeight - stashedHeight) / 2f } diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt index 9ce2277b14..384945b34c 100644 --- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt +++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt @@ -30,6 +30,8 @@ import android.view.ViewGroup import androidx.core.view.updateLayoutParams import com.android.launcher3.Flags.enableRefactorTaskThumbnail import com.android.launcher3.R +import com.android.launcher3.testing.TestLogging +import com.android.launcher3.testing.shared.TestProtocol import com.android.launcher3.util.RunnableList import com.android.launcher3.util.SplitConfigurationOptions import com.android.launcher3.util.TransformingTouchDelegate @@ -213,7 +215,15 @@ class DesktopTaskView @JvmOverloads constructor(context: Context, attrs: Attribu } override fun needsUpdate(dataChange: Int, flag: Int) = - if (flag == FLAG_UPDATE_THUMBNAIL) super.needsUpdate(dataChange, flag) else false + if (flag == FLAG_UPDATE_CORNER_RADIUS) false else super.needsUpdate(dataChange, flag) + + override fun onIconLoaded(taskContainer: TaskContainer) { + // Update contentDescription of snapshotView only, individual task icon is unused. + taskContainer.snapshotView.contentDescription = taskContainer.task.titleDescription + } + + // Ignoring [onIconUnloaded] as all tasks shares the same Desktop icon + override fun onIconUnloaded(taskContainer: TaskContainer) {} // thumbnailView is laid out differently and is handled in onMeasure override fun updateThumbnailSize() {} @@ -228,6 +238,11 @@ class DesktopTaskView @JvmOverloads constructor(context: Context, attrs: Attribu override fun launchTaskAnimated(): RunnableList? { val recentsView = recentsView ?: return null + TestLogging.recordEvent( + TestProtocol.SEQUENCE_MAIN, + "launchDesktopFromRecents", + taskIds.contentToString() + ) val endCallback = RunnableList() val desktopController = recentsView.desktopRecentsController checkNotNull(desktopController) { "recentsController is null" } diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt index d87e05ff50..176a538d48 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.kt +++ b/quickstep/src/com/android/quickstep/views/TaskView.kt @@ -901,18 +901,11 @@ constructor( it.task.icon = icon it.task.titleDescription = contentDescription it.task.title = title - setIcon(it.iconView, icon) - if (enableOverviewIconMenu()) { - setText(it.iconView, title) - } - it.digitalWellBeingToast?.initialize(it.task) + onIconLoaded(it) } ?.also { request -> pendingIconLoadRequests.add(request) } } else { - setIcon(it.iconView, null) - if (enableOverviewIconMenu()) { - setText(it.iconView, null) - } + onIconUnloaded(it) } } } @@ -931,6 +924,21 @@ constructor( pendingIconLoadRequests.clear() } + protected open fun onIconLoaded(taskContainer: TaskContainer) { + setIcon(taskContainer.iconView, taskContainer.task.icon) + if (enableOverviewIconMenu()) { + setText(taskContainer.iconView, taskContainer.task.title) + } + taskContainer.digitalWellBeingToast?.initialize(taskContainer.task) + } + + protected open fun onIconUnloaded(taskContainer: TaskContainer) { + setIcon(taskContainer.iconView, null) + if (enableOverviewIconMenu()) { + setText(taskContainer.iconView, null) + } + } + protected fun setIcon(iconView: TaskViewIcon, icon: Drawable?) { with(iconView) { if (icon != null) { @@ -1152,6 +1160,11 @@ constructor( isClickableAsLiveTile = true return runnableList } + TestLogging.recordEvent( + TestProtocol.SEQUENCE_MAIN, + "composeRecentsLaunchAnimator", + taskIds.contentToString() + ) val runnableList = RunnableList() with(AnimatorSet()) { TaskViewUtils.composeRecentsLaunchAnimator( diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt index 619ce1c860..21eb3e0f7d 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt @@ -84,7 +84,7 @@ class BubbleBarViewAnimatorTest { BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler) InstrumentationRegistry.getInstrumentation().runOnMainSync { - animator.animateBubbleInForStashed(bubble) + animator.animateBubbleInForStashed(bubble, isExpanding = false) } // let the animation start and wait for it to complete @@ -128,7 +128,7 @@ class BubbleBarViewAnimatorTest { BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler) InstrumentationRegistry.getInstrumentation().runOnMainSync { - animator.animateBubbleInForStashed(bubble) + animator.animateBubbleInForStashed(bubble, isExpanding = false) } // let the animation start and wait for it to complete @@ -171,7 +171,7 @@ class BubbleBarViewAnimatorTest { BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler) InstrumentationRegistry.getInstrumentation().runOnMainSync { - animator.animateBubbleInForStashed(bubble) + animator.animateBubbleInForStashed(bubble, isExpanding = false) } // wait for the animation to start @@ -211,7 +211,7 @@ class BubbleBarViewAnimatorTest { BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler) InstrumentationRegistry.getInstrumentation().runOnMainSync { - animator.animateBubbleInForStashed(bubble) + animator.animateBubbleInForStashed(bubble, isExpanding = false) } // let the animation start and wait for it to complete @@ -252,7 +252,7 @@ class BubbleBarViewAnimatorTest { BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler) InstrumentationRegistry.getInstrumentation().runOnMainSync { - animator.animateBubbleInForStashed(bubble) + animator.animateBubbleInForStashed(bubble, isExpanding = false) } // wait for the animation to start @@ -269,6 +269,123 @@ class BubbleBarViewAnimatorTest { assertThat(animatorScheduler.delayedBlock).isNull() } + @Test + fun animateBubbleInForStashed_autoExpanding() { + setUpBubbleBar() + setUpBubbleStashController() + + val handle = View(context) + val handleAnimator = PhysicsAnimator.getInstance(handle) + whenever(bubbleStashController.getStashedHandlePhysicsAnimator()).thenReturn(handleAnimator) + + val animator = + BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateBubbleInForStashed(bubble, isExpanding = true) + } + + // wait for the animation to start + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + assertThat(handle.alpha).isEqualTo(0) + assertThat(handle.translationY) + .isEqualTo(DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS + BAR_TRANSLATION_Y_FOR_TASKBAR) + assertThat(bubbleBarView.visibility).isEqualTo(VISIBLE) + assertThat(bubbleBarView.scaleX).isEqualTo(1) + assertThat(bubbleBarView.scaleY).isEqualTo(1) + assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR) + assertThat(bubbleBarView.isAnimatingNewBubble).isFalse() + assertThat(bubbleBarView.isExpanded).isTrue() + + // verify there is no hide animation + assertThat(animatorScheduler.delayedBlock).isNull() + + verify(bubbleStashController).showBubbleBarImmediate() + } + + @Test + fun animateBubbleInForStashed_expandedWhileAnimatingIn() { + setUpBubbleBar() + setUpBubbleStashController() + + val handle = View(context) + val handleAnimator = PhysicsAnimator.getInstance(handle) + whenever(bubbleStashController.getStashedHandlePhysicsAnimator()).thenReturn(handleAnimator) + + val animator = + BubbleBarViewAnimator(bubbleBarView, bubbleStashController, 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(bubbleBarView.isAnimatingNewBubble).isTrue() + // verify the hide bubble animation is pending + assertThat(animatorScheduler.delayedBlock).isNotNull() + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.expandedWhileAnimating() + } + + // let the animation finish + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + // verify that the hide animation was canceled + assertThat(animatorScheduler.delayedBlock).isNull() + + assertThat(handle.alpha).isEqualTo(0) + assertThat(handle.translationY) + .isEqualTo(DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS + BAR_TRANSLATION_Y_FOR_TASKBAR) + verifyBubbleBarIsExpandedWithTranslation(BAR_TRANSLATION_Y_FOR_TASKBAR) + assertThat(bubbleBarView.isAnimatingNewBubble).isFalse() + } + + @Test + fun animateBubbleInForStashed_expandedWhileFullyIn() { + setUpBubbleBar() + setUpBubbleStashController() + + val handle = View(context) + val handleAnimator = PhysicsAnimator.getInstance(handle) + whenever(bubbleStashController.getStashedHandlePhysicsAnimator()).thenReturn(handleAnimator) + + val animator = + BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateBubbleInForStashed(bubble, isExpanding = false) + } + + // wait for the animation to start + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + // wait for the animation to end + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + assertThat(bubbleBarView.isAnimatingNewBubble).isTrue() + // verify the hide bubble animation is pending + assertThat(animatorScheduler.delayedBlock).isNotNull() + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.expandedWhileAnimating() + } + + // verify that the hide animation was canceled + assertThat(animatorScheduler.delayedBlock).isNull() + + assertThat(handle.alpha).isEqualTo(0) + assertThat(handle.translationY) + .isEqualTo(DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS + BAR_TRANSLATION_Y_FOR_TASKBAR) + verifyBubbleBarIsExpandedWithTranslation(BAR_TRANSLATION_Y_FOR_TASKBAR) + assertThat(bubbleBarView.isAnimatingNewBubble).isFalse() + } + @Test fun animateToInitialState_inApp() { setUpBubbleBar() @@ -336,17 +453,11 @@ class BubbleBarViewAnimatorTest { PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) barAnimator.assertIsNotRunning() - assertThat(bubbleBarView.isAnimatingNewBubble).isTrue() - assertThat(bubbleBarView.alpha).isEqualTo(1) - assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR) - - assertThat(animatorScheduler.delayedBlock).isNotNull() - InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!) - assertThat(bubbleBarView.isAnimatingNewBubble).isFalse() assertThat(bubbleBarView.alpha).isEqualTo(1) assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR) + assertThat(animatorScheduler.delayedBlock).isNull() verify(bubbleStashController).showBubbleBarImmediate() } @@ -384,6 +495,79 @@ class BubbleBarViewAnimatorTest { verify(bubbleStashController).showBubbleBarImmediate() } + @Test + fun animateToInitialState_expandedWhileAnimatingIn() { + setUpBubbleBar() + setUpBubbleStashController() + whenever(bubbleStashController.bubbleBarTranslationY) + .thenReturn(BAR_TRANSLATION_Y_FOR_HOTSEAT) + + val animator = + BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateToInitialState(bubble, isInApp = false, isExpanding = false) + } + + val bubbleBarAnimator = PhysicsAnimator.getInstance(bubbleBarView) + + // wait for the animation to start + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(bubbleBarAnimator) { true } + + bubbleBarAnimator.assertIsRunning() + assertThat(bubbleBarView.isAnimatingNewBubble).isTrue() + // verify the hide bubble animation is pending + assertThat(animatorScheduler.delayedBlock).isNotNull() + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.expandedWhileAnimating() + } + + // let the animation finish + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + // verify that the hide animation was canceled + assertThat(animatorScheduler.delayedBlock).isNull() + + verifyBubbleBarIsExpandedWithTranslation(BAR_TRANSLATION_Y_FOR_HOTSEAT) + assertThat(bubbleBarView.isAnimatingNewBubble).isFalse() + verify(bubbleStashController).showBubbleBarImmediate() + } + + @Test + fun animateToInitialState_expandedWhileFullyIn() { + setUpBubbleBar() + setUpBubbleStashController() + whenever(bubbleStashController.bubbleBarTranslationY) + .thenReturn(BAR_TRANSLATION_Y_FOR_HOTSEAT) + + val animator = + BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateToInitialState(bubble, isInApp = false, isExpanding = false) + } + + // wait for the animation to start + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + assertThat(bubbleBarView.isAnimatingNewBubble).isTrue() + // verify the hide bubble animation is pending + assertThat(animatorScheduler.delayedBlock).isNotNull() + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.expandedWhileAnimating() + } + + // verify that the hide animation was canceled + assertThat(animatorScheduler.delayedBlock).isNull() + + verifyBubbleBarIsExpandedWithTranslation(BAR_TRANSLATION_Y_FOR_HOTSEAT) + assertThat(bubbleBarView.isAnimatingNewBubble).isFalse() + } + @Test fun animateBubbleBarForCollapsed() { setUpBubbleBar() @@ -397,7 +581,7 @@ class BubbleBarViewAnimatorTest { BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler) InstrumentationRegistry.getInstrumentation().runOnMainSync { - animator.animateBubbleBarForCollapsed(bubble) + animator.animateBubbleBarForCollapsed(bubble, isExpanding = false) } InstrumentationRegistry.getInstrumentation().runOnMainSync {} @@ -424,6 +608,142 @@ class BubbleBarViewAnimatorTest { verify(bubbleStashController).showBubbleBarImmediate() } + @Test + fun animateBubbleBarForCollapsed_autoExpanding() { + setUpBubbleBar() + setUpBubbleStashController() + whenever(bubbleStashController.bubbleBarTranslationY) + .thenReturn(BAR_TRANSLATION_Y_FOR_HOTSEAT) + + val barAnimator = PhysicsAnimator.getInstance(bubbleBarView) + + val animator = + BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateBubbleBarForCollapsed(bubble, isExpanding = true) + } + + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + // verify we started animating + assertThat(bubbleBarView.isAnimatingNewBubble).isTrue() + + // advance the animation handler by the duration of the initial lift + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animatorTestRule.advanceTimeBy(250) + } + + // the lift animation is complete; the spring back animation should start now + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + barAnimator.assertIsRunning() + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + // verify there is no hide animation + assertThat(animatorScheduler.delayedBlock).isNull() + + assertThat(bubbleBarView.isAnimatingNewBubble).isFalse() + assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_HOTSEAT) + assertThat(bubbleBarView.isExpanded).isTrue() + verify(bubbleStashController).showBubbleBarImmediate() + } + + @Test + fun animateBubbleBarForCollapsed_expandingWhileAnimatingIn() { + setUpBubbleBar() + setUpBubbleStashController() + whenever(bubbleStashController.bubbleBarTranslationY) + .thenReturn(BAR_TRANSLATION_Y_FOR_HOTSEAT) + + val barAnimator = PhysicsAnimator.getInstance(bubbleBarView) + + val animator = + BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateBubbleBarForCollapsed(bubble, isExpanding = false) + } + + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + // verify we started animating + assertThat(bubbleBarView.isAnimatingNewBubble).isTrue() + + // advance the animation handler by the duration of the initial lift + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animatorTestRule.advanceTimeBy(250) + } + + // the lift animation is complete; the spring back animation should start now + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + barAnimator.assertIsRunning() + PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(barAnimator) { true } + + // verify there is a pending hide animation + assertThat(animatorScheduler.delayedBlock).isNotNull() + assertThat(bubbleBarView.isAnimatingNewBubble).isTrue() + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.expandedWhileAnimating() + } + + // let the animation finish + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + // verify that the hide animation was canceled + assertThat(animatorScheduler.delayedBlock).isNull() + + assertThat(bubbleBarView.isAnimatingNewBubble).isFalse() + assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_HOTSEAT) + assertThat(bubbleBarView.isExpanded).isTrue() + verify(bubbleStashController).showBubbleBarImmediate() + } + + @Test + fun animateBubbleBarForCollapsed_expandingWhileFullyIn() { + setUpBubbleBar() + setUpBubbleStashController() + whenever(bubbleStashController.bubbleBarTranslationY) + .thenReturn(BAR_TRANSLATION_Y_FOR_HOTSEAT) + + val barAnimator = PhysicsAnimator.getInstance(bubbleBarView) + + val animator = + BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler) + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.animateBubbleBarForCollapsed(bubble, isExpanding = false) + } + + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + // verify we started animating + assertThat(bubbleBarView.isAnimatingNewBubble).isTrue() + + // advance the animation handler by the duration of the initial lift + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animatorTestRule.advanceTimeBy(250) + } + + // the lift animation is complete; the spring back animation should start now + InstrumentationRegistry.getInstrumentation().runOnMainSync {} + barAnimator.assertIsRunning() + PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y) + + // verify there is a pending hide animation + assertThat(animatorScheduler.delayedBlock).isNotNull() + assertThat(bubbleBarView.isAnimatingNewBubble).isTrue() + + InstrumentationRegistry.getInstrumentation().runOnMainSync { + animator.expandedWhileAnimating() + } + + // verify that the hide animation was canceled + assertThat(animatorScheduler.delayedBlock).isNull() + + assertThat(bubbleBarView.isAnimatingNewBubble).isFalse() + assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_HOTSEAT) + assertThat(bubbleBarView.isExpanded).isTrue() + verify(bubbleStashController).showBubbleBarImmediate() + } + private fun setUpBubbleBar() { bubbleBarView = BubbleBarView(context) InstrumentationRegistry.getInstrumentation().runOnMainSync { @@ -459,6 +779,14 @@ class BubbleBarViewAnimatorTest { .thenReturn(BAR_TRANSLATION_Y_FOR_TASKBAR) } + private fun verifyBubbleBarIsExpandedWithTranslation(ty: Float) { + assertThat(bubbleBarView.visibility).isEqualTo(VISIBLE) + assertThat(bubbleBarView.scaleX).isEqualTo(1) + assertThat(bubbleBarView.scaleY).isEqualTo(1) + assertThat(bubbleBarView.translationY).isEqualTo(ty) + assertThat(bubbleBarView.isExpanded).isTrue() + } + private fun PhysicsAnimator.assertIsRunning() { InstrumentationRegistry.getInstrumentation().runOnMainSync { assertThat(isRunning()).isTrue() diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRuleTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRuleTest.kt index f75e5423be..f7e457613a 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRuleTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRuleTest.kt @@ -24,6 +24,7 @@ import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.TRANSIENT import com.android.launcher3.taskbar.rules.TaskbarModeRule.TaskbarMode import com.android.launcher3.util.DisplayController import com.android.launcher3.util.LauncherMultivalentJUnit +import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices import com.android.launcher3.util.NavigationMode import com.google.common.truth.Truth.assertThat import org.junit.Rule @@ -31,6 +32,7 @@ import org.junit.Test import org.junit.runner.RunWith @RunWith(LauncherMultivalentJUnit::class) +@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"]) class TaskbarModeRuleTest { private val context = TaskbarWindowSandboxContext.create(getInstrumentation().targetContext) diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPinningPreferenceRuleTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPinningPreferenceRuleTest.kt index a709133b34..a5154058fd 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPinningPreferenceRuleTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPinningPreferenceRuleTest.kt @@ -19,6 +19,7 @@ package com.android.launcher3.taskbar.rules import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.android.launcher3.util.DisplayController import com.android.launcher3.util.LauncherMultivalentJUnit +import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices import com.android.launcher3.util.window.WindowManagerProxy import com.google.android.apps.nexuslauncher.deviceemulator.TestWindowManagerProxy import com.google.common.truth.Truth.assertThat @@ -28,6 +29,7 @@ import org.junit.runner.RunWith import org.junit.runners.model.Statement @RunWith(LauncherMultivalentJUnit::class) +@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"]) class TaskbarPinningPreferenceRuleTest { private val context = TaskbarWindowSandboxContext.create(getInstrumentation().targetContext) diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPreferenceRuleTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPreferenceRuleTest.kt index 22d2079821..46817d2bbd 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPreferenceRuleTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarPreferenceRuleTest.kt @@ -19,6 +19,7 @@ package com.android.launcher3.taskbar.rules import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING import com.android.launcher3.util.LauncherMultivalentJUnit +import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.Description @@ -26,6 +27,7 @@ import org.junit.runner.RunWith import org.junit.runners.model.Statement @RunWith(LauncherMultivalentJUnit::class) +@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"]) class TaskbarPreferenceRuleTest { private val context = TaskbarWindowSandboxContext.create(getInstrumentation().targetContext) diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRuleTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRuleTest.kt index 234e4991c6..5d4fdc5418 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRuleTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRuleTest.kt @@ -34,7 +34,7 @@ import org.junit.runner.RunWith import org.junit.runners.model.Statement @RunWith(LauncherMultivalentJUnit::class) -@EmulatedDevices(["pixelFoldable2023"]) +@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"]) class TaskbarUnitTestRuleTest { private val context = TaskbarWindowSandboxContext.create(getInstrumentation().targetContext) diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContextTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContextTest.kt index ad4b4de2ea..4834d48973 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContextTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContextTest.kt @@ -25,6 +25,7 @@ import org.junit.Test import org.junit.runner.RunWith @RunWith(LauncherMultivalentJUnit::class) +@LauncherMultivalentJUnit.EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"]) class TaskbarWindowSandboxContextTest { private val context = TaskbarWindowSandboxContext.create(getInstrumentation().targetContext) diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsOverviewDesktop.kt b/quickstep/tests/src/com/android/quickstep/TaplTestsOverviewDesktop.kt new file mode 100644 index 0000000000..694a3822fe --- /dev/null +++ b/quickstep/tests/src/com/android/quickstep/TaplTestsOverviewDesktop.kt @@ -0,0 +1,105 @@ +/* + * 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 + +import android.platform.test.rule.AllowedDevices +import android.platform.test.rule.DeviceProduct +import android.platform.test.rule.IgnoreLimit +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Until +import com.android.launcher3.BuildConfig +import com.android.launcher3.ui.AbstractLauncherUiTest +import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape +import com.android.launcher3.uioverrides.QuickstepLauncher +import com.google.common.truth.Truth.assertWithMessage +import org.junit.Before +import org.junit.Test + +/** Test Desktop windowing in Overview. */ +@AllowedDevices(allowed = [DeviceProduct.CF_TABLET, DeviceProduct.TANGORPRO]) +@IgnoreLimit(ignoreLimit = BuildConfig.IS_STUDIO_BUILD) +class TaplTestsOverviewDesktop : AbstractLauncherUiTest() { + @Before + fun setup() { + val overview = mLauncher.goHome().switchToOverview() + if (overview.hasTasks()) { + overview.dismissAllTasks() + } + startTestAppsWithCheck() + mLauncher.goHome() + } + + @Test + @PortraitLandscape + fun enterDesktopViaOverviewMenu() { + // Move last launched TEST_ACTIVITY_2 into Desktop + mLauncher.workspace + .switchToOverview() + .getTestActivityTask(TEST_ACTIVITY_2) + .tapMenu() + .tapDesktopMenuItem() + assertTestAppLaunched(TEST_ACTIVITY_2) + + // Scroll back to TEST_ACTIVITY_1, then move it into Desktop + mLauncher + .goHome() + .switchToOverview() + .apply { flingForward() } + .getTestActivityTask(TEST_ACTIVITY_1) + .tapMenu() + .tapDesktopMenuItem() + TEST_ACTIVITIES.forEach { assertTestAppLaunched(it) } + + // Launch static DesktopTaskView + val desktop = + mLauncher.goHome().switchToOverview().getTestActivityTask(TEST_ACTIVITIES).open() + TEST_ACTIVITIES.forEach { assertTestAppLaunched(it) } + + // Launch live-tile DesktopTaskView + desktop.switchToOverview().getTestActivityTask(TEST_ACTIVITIES).open() + TEST_ACTIVITIES.forEach { assertTestAppLaunched(it) } + } + + private fun startTestAppsWithCheck() { + TEST_ACTIVITIES.forEach { + startTestActivity(it) + executeOnLauncher { launcher -> + assertWithMessage( + "Launcher activity is the top activity; expecting TestActivity$it" + ) + .that(isInLaunchedApp(launcher)) + .isTrue() + } + } + } + + private fun assertTestAppLaunched(index: Int) { + assertWithMessage("TestActivity$index not opened in Desktop") + .that( + mDevice.wait( + Until.hasObject(By.pkg(getAppPackageName()).text("TestActivity$index")), + DEFAULT_UI_TIMEOUT + ) + ) + .isTrue() + } + + companion object { + const val TEST_ACTIVITY_1 = 2 + const val TEST_ACTIVITY_2 = 3 + val TEST_ACTIVITIES = listOf(TEST_ACTIVITY_1, TEST_ACTIVITY_2) + } +} diff --git a/res/values/dimens.xml b/res/values/dimens.xml index af91b5a4ff..5e1d8a5ad3 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -306,6 +306,7 @@ 2dp 4dp 2dp + 1dp 8dp diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 2eb50340e3..1eccbff00a 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -873,7 +873,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, if (drawable == null) { setText(text); Log.w(TAG, "setTextWithStartIcon: start icon Drawable not found from resources" - + ", will just set text instead. text=" + text); + + ", will just set text instead."); return; } drawable.setTint(getCurrentTextColor()); diff --git a/src/com/android/launcher3/FastScrollRecyclerView.java b/src/com/android/launcher3/FastScrollRecyclerView.java index 960d77a7d9..de1748b911 100644 --- a/src/com/android/launcher3/FastScrollRecyclerView.java +++ b/src/com/android/launcher3/FastScrollRecyclerView.java @@ -20,7 +20,6 @@ import static com.android.launcher3.testing.shared.TestProtocol.SCROLL_FINISHED_ import android.content.Context; import android.util.AttributeSet; -import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.accessibility.AccessibilityNodeInfo; @@ -29,7 +28,6 @@ import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; import com.android.launcher3.compat.AccessibilityManagerCompat; -import com.android.launcher3.testing.shared.TestProtocol; import com.android.launcher3.views.RecyclerViewFastScroller; @@ -189,10 +187,6 @@ public abstract class FastScrollRecyclerView extends RecyclerView { * Scrolls this recycler view to the top. */ public void scrollToTop() { - if (TestProtocol.sDebugTracing) { - Log.d(TestProtocol.PRIVATE_SPACE_SCROLL_FAILURE, "FastScrollRecyclerView#scrollToTop", - new Exception()); - } if (mScrollbar != null) { mScrollbar.reattachThumbToScroll(); } diff --git a/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java b/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java index bc66a33b0c..ef66ffe091 100644 --- a/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java +++ b/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java @@ -19,11 +19,19 @@ package com.android.launcher3.views; import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound; import android.content.Context; -import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.style.ImageSpan; import android.util.AttributeSet; -import android.widget.TextView; +import android.util.Log; + +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; import com.android.launcher3.BubbleTextView; import com.android.launcher3.R; @@ -45,22 +53,65 @@ public class DoubleShadowBubbleTextView extends BubbleTextView { public DoubleShadowBubbleTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - mShadowInfo = new ShadowInfo(context, attrs, defStyle); - setShadowLayer(mShadowInfo.ambientShadowBlur, 0, 0, mShadowInfo.ambientShadowColor); + mShadowInfo = ShadowInfo.Companion.fromContext(context, attrs, defStyle); + setShadowLayer( + mShadowInfo.getAmbientShadowBlur(), + 0, + 0, + mShadowInfo.getAmbientShadowColor() + ); + } + + @Override + public void setTextWithStartIcon(CharSequence text, @DrawableRes int drawableId) { + Drawable drawable = getContext().getDrawable(drawableId); + if (drawable == null) { + setText(text); + Log.w(TAG, "setTextWithStartIcon: start icon Drawable not found from resources" + + ", will just set text instead."); + return; + } + drawable.setTint(getCurrentTextColor()); + int textSize = Math.round(getTextSize()); + ImageSpan imageSpan; + if (!skipDoubleShadow() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + drawable = getDoubleShadowDrawable(drawable, textSize); + } + drawable.setBounds(0, 0, textSize, textSize); + imageSpan = new ImageSpan(drawable, ImageSpan.ALIGN_CENTER); + // First space will be replaced with Drawable, second space is for space before text. + SpannableString spannable = new SpannableString(" " + text); + spannable.setSpan(imageSpan, 0, 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + setText(spannable); + } + + @RequiresApi(Build.VERSION_CODES.S) + private DoubleShadowIconDrawable getDoubleShadowDrawable( + @NonNull Drawable drawable, int textSize + ) { + // add some padding via inset to avoid shadow clipping + int iconInsetSize = getContext().getResources() + .getDimensionPixelSize(R.dimen.app_title_icon_shadow_inset); + return new DoubleShadowIconDrawable( + mShadowInfo, + drawable, + textSize, + iconInsetSize + ); } @Override public void onDraw(Canvas canvas) { // If text is transparent or shadow alpha is 0, don't draw any shadow - if (mShadowInfo.skipDoubleShadow(this)) { + if (skipDoubleShadow()) { super.onDraw(canvas); return; } int alpha = Color.alpha(getCurrentTextColor()); // We enhance the shadow by drawing the shadow twice - getPaint().setShadowLayer(mShadowInfo.ambientShadowBlur, 0, 0, - getTextShadowColor(mShadowInfo.ambientShadowColor, alpha)); + getPaint().setShadowLayer(mShadowInfo.getAmbientShadowBlur(), 0, 0, + getTextShadowColor(mShadowInfo.getAmbientShadowColor(), alpha)); drawWithoutDot(canvas); canvas.save(); @@ -69,10 +120,10 @@ public class DoubleShadowBubbleTextView extends BubbleTextView { getScrollY() + getHeight()); getPaint().setShadowLayer( - mShadowInfo.keyShadowBlur, - mShadowInfo.keyShadowOffsetX, - mShadowInfo.keyShadowOffsetY, - getTextShadowColor(mShadowInfo.keyShadowColor, alpha)); + mShadowInfo.getKeyShadowBlur(), + mShadowInfo.getKeyShadowOffsetX(), + mShadowInfo.getKeyShadowOffsetY(), + getTextShadowColor(mShadowInfo.getKeyShadowColor(), alpha)); drawWithoutDot(canvas); canvas.restore(); @@ -80,55 +131,30 @@ public class DoubleShadowBubbleTextView extends BubbleTextView { drawRunningAppIndicatorIfNecessary(canvas); } - public static class ShadowInfo { - public final float ambientShadowBlur; - public final int ambientShadowColor; - - public final float keyShadowBlur; - public final float keyShadowOffsetX; - public final float keyShadowOffsetY; - public final int keyShadowColor; - - public ShadowInfo(Context c, AttributeSet attrs, int defStyle) { - - TypedArray a = c.obtainStyledAttributes( - attrs, R.styleable.ShadowInfo, defStyle, 0); - - ambientShadowBlur = a.getDimensionPixelSize( - R.styleable.ShadowInfo_ambientShadowBlur, 0); - ambientShadowColor = a.getColor(R.styleable.ShadowInfo_ambientShadowColor, 0); - - keyShadowBlur = a.getDimensionPixelSize(R.styleable.ShadowInfo_keyShadowBlur, 0); - keyShadowOffsetX = a.getDimensionPixelSize(R.styleable.ShadowInfo_keyShadowOffsetX, 0); - keyShadowOffsetY = a.getDimensionPixelSize(R.styleable.ShadowInfo_keyShadowOffsetY, 0); - keyShadowColor = a.getColor(R.styleable.ShadowInfo_keyShadowColor, 0); - a.recycle(); - } - - public boolean skipDoubleShadow(TextView textView) { - int textAlpha = Color.alpha(textView.getCurrentTextColor()); - int keyShadowAlpha = Color.alpha(keyShadowColor); - int ambientShadowAlpha = Color.alpha(ambientShadowColor); - if (textAlpha == 0 || (keyShadowAlpha == 0 && ambientShadowAlpha == 0)) { - textView.getPaint().clearShadowLayer(); - return true; - } else if (ambientShadowAlpha > 0 && keyShadowAlpha == 0) { - textView.getPaint().setShadowLayer(ambientShadowBlur, 0, 0, - getTextShadowColor(ambientShadowColor, textAlpha)); - return true; - } else if (keyShadowAlpha > 0 && ambientShadowAlpha == 0) { - textView.getPaint().setShadowLayer( - keyShadowBlur, - keyShadowOffsetX, - keyShadowOffsetY, - getTextShadowColor(keyShadowColor, textAlpha)); - return true; - } else { - return false; - } + private boolean skipDoubleShadow() { + int textAlpha = Color.alpha(getCurrentTextColor()); + int keyShadowAlpha = Color.alpha(mShadowInfo.getKeyShadowColor()); + int ambientShadowAlpha = Color.alpha(mShadowInfo.getAmbientShadowColor()); + if (textAlpha == 0 || (keyShadowAlpha == 0 && ambientShadowAlpha == 0)) { + getPaint().clearShadowLayer(); + return true; + } else if (ambientShadowAlpha > 0 && keyShadowAlpha == 0) { + getPaint().setShadowLayer(mShadowInfo.getAmbientShadowBlur(), 0, 0, + getTextShadowColor(mShadowInfo.getAmbientShadowColor(), textAlpha)); + return true; + } else if (keyShadowAlpha > 0 && ambientShadowAlpha == 0) { + getPaint().setShadowLayer( + mShadowInfo.getKeyShadowBlur(), + mShadowInfo.getKeyShadowOffsetX(), + mShadowInfo.getKeyShadowOffsetY(), + getTextShadowColor(mShadowInfo.getKeyShadowColor(), textAlpha)); + return true; + } else { + return false; } } + // Multiplies the alpha of shadowColor by textAlpha. private static int getTextShadowColor(int shadowColor, int textAlpha) { return setColorAlphaBound(shadowColor, diff --git a/src/com/android/launcher3/views/DoubleShadowIconDrawable.kt b/src/com/android/launcher3/views/DoubleShadowIconDrawable.kt new file mode 100644 index 0000000000..7ac7c94ae4 --- /dev/null +++ b/src/com/android/launcher3/views/DoubleShadowIconDrawable.kt @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.views + +import android.content.res.ColorStateList +import android.graphics.BlendMode +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.ColorFilter +import android.graphics.PixelFormat +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter +import android.graphics.RenderEffect +import android.graphics.RenderNode +import android.graphics.Shader +import android.graphics.drawable.Drawable +import android.graphics.drawable.InsetDrawable +import android.os.Build.VERSION_CODES +import androidx.annotation.RequiresApi +import androidx.annotation.VisibleForTesting + +/** + * Launcher wrapper for Drawables to provide a double shadow effect. Currently for use with + * [DoubleShadowBubbleTextView] to provide a similar shadow to inline icons. + */ +@RequiresApi(VERSION_CODES.S) +class DoubleShadowIconDrawable( + private val shadowInfo: ShadowInfo, + iconDrawable: Drawable, + private val iconSize: Int, + iconInsetSize: Int +) : Drawable() { + private val mIconDrawable: InsetDrawable + private val mDoubleShadowNode: RenderNode? + + init { + mIconDrawable = InsetDrawable(iconDrawable, iconInsetSize) + mIconDrawable.setBounds(0, 0, iconSize, iconSize) + mDoubleShadowNode = createShadowRenderNode() + } + + @VisibleForTesting + fun createShadowRenderNode(): RenderNode { + val renderNode = RenderNode("DoubleShadowNode") + renderNode.setPosition(0, 0, iconSize, iconSize) + // Create render effects + val ambientShadow = + createShadowRenderEffect( + shadowInfo.ambientShadowBlur, + 0f, + 0f, + Color.alpha(shadowInfo.ambientShadowColor).toFloat() + ) + val keyShadow = + createShadowRenderEffect( + shadowInfo.keyShadowBlur, + shadowInfo.keyShadowOffsetX, + shadowInfo.keyShadowOffsetY, + Color.alpha(shadowInfo.keyShadowColor).toFloat() + ) + val blend = RenderEffect.createBlendModeEffect(ambientShadow, keyShadow, BlendMode.DST_ATOP) + renderNode.setRenderEffect(blend) + return renderNode + } + + @VisibleForTesting + fun createShadowRenderEffect( + radius: Float, + offsetX: Float, + offsetY: Float, + alpha: Float + ): RenderEffect { + return RenderEffect.createColorFilterEffect( + PorterDuffColorFilter(Color.argb(alpha, 0f, 0f, 0f), PorterDuff.Mode.MULTIPLY), + RenderEffect.createOffsetEffect( + offsetX, + offsetY, + RenderEffect.createBlurEffect(radius, radius, Shader.TileMode.CLAMP) + ) + ) + } + + override fun draw(canvas: Canvas) { + if (canvas.isHardwareAccelerated && mDoubleShadowNode != null) { + if (!mDoubleShadowNode.hasDisplayList()) { + // Record render node if its display list is not recorded or discarded + // (which happens when it's no longer drawn by anything). + val recordingCanvas = mDoubleShadowNode.beginRecording() + mIconDrawable.draw(recordingCanvas) + mDoubleShadowNode.endRecording() + } + canvas.drawRenderNode(mDoubleShadowNode) + } + mIconDrawable.draw(canvas) + } + + override fun getIntrinsicHeight() = iconSize + + override fun getIntrinsicWidth() = iconSize + + override fun getOpacity() = PixelFormat.TRANSPARENT + + override fun setAlpha(alpha: Int) { + mIconDrawable.alpha = alpha + } + + override fun setColorFilter(colorFilter: ColorFilter?) { + mIconDrawable.colorFilter = colorFilter + } + + override fun setTint(color: Int) { + mIconDrawable.setTint(color) + } + + override fun setTintList(tint: ColorStateList?) { + mIconDrawable.setTintList(tint) + } +} diff --git a/src/com/android/launcher3/views/ShadowInfo.kt b/src/com/android/launcher3/views/ShadowInfo.kt new file mode 100644 index 0000000000..4f626ec520 --- /dev/null +++ b/src/com/android/launcher3/views/ShadowInfo.kt @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.views + +import android.content.Context +import android.util.AttributeSet +import com.android.launcher3.R + +/** + * Launcher data holder for classes such as [DoubleShadowBubbleTextView] to model shadows for + * "double shadow" effect. + */ +data class ShadowInfo( + val ambientShadowBlur: Float, + val ambientShadowColor: Int, + val keyShadowBlur: Float, + val keyShadowOffsetX: Float, + val keyShadowOffsetY: Float, + val keyShadowColor: Int +) { + + companion object { + /** Constructs instance of ShadowInfo from Context and given attribute set. */ + @JvmStatic + fun fromContext(context: Context, attrs: AttributeSet?, defStyle: Int): ShadowInfo { + val styledAttrs = + context.obtainStyledAttributes(attrs, R.styleable.ShadowInfo, defStyle, 0) + val shadowInfo = + ShadowInfo( + ambientShadowBlur = + styledAttrs + .getDimensionPixelSize(R.styleable.ShadowInfo_ambientShadowBlur, 0) + .toFloat(), + ambientShadowColor = + styledAttrs.getColor(R.styleable.ShadowInfo_ambientShadowColor, 0), + keyShadowBlur = + styledAttrs + .getDimensionPixelSize(R.styleable.ShadowInfo_keyShadowBlur, 0) + .toFloat(), + keyShadowOffsetX = + styledAttrs + .getDimensionPixelSize(R.styleable.ShadowInfo_keyShadowOffsetX, 0) + .toFloat(), + keyShadowOffsetY = + styledAttrs + .getDimensionPixelSize(R.styleable.ShadowInfo_keyShadowOffsetY, 0) + .toFloat(), + keyShadowColor = styledAttrs.getColor(R.styleable.ShadowInfo_keyShadowColor, 0) + ) + styledAttrs.recycle() + return shadowInfo + } + } +} diff --git a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java index dc3b3213d2..3f4a73aec6 100644 --- a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java +++ b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java @@ -173,8 +173,6 @@ public final class TestProtocol { public static final String TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE = "b/326908466"; public static final String WIDGET_CONFIG_NULL_EXTRA_INTENT = "b/324419890"; public static final String OVERVIEW_SELECT_TOOLTIP_MISALIGNED = "b/332485341"; - public static final String PRIVATE_SPACE_SCROLL_FAILURE = "b/339737008"; - public static final String REQUEST_FLAG_ENABLE_GRID_ONLY_OVERVIEW = "enable-grid-only-overview"; public static final String REQUEST_FLAG_ENABLE_APP_PAIRS = "enable-app-pairs"; diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index 1e2744c3aa..749a75aaa1 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -43,6 +43,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.platform.test.flag.junit.SetFlagsRule; +import android.platform.test.rule.LimitDevicesRule; import android.system.OsConstants; import android.util.Log; @@ -222,6 +223,9 @@ public abstract class AbstractLauncherUiTest { @Rule public ExtendedLongPressTimeoutRule mLongPressTimeoutRule = new ExtendedLongPressTimeoutRule(); + @Rule + public LimitDevicesRule mlimitDevicesRule = new LimitDevicesRule(); + public static void initialize(AbstractLauncherUiTest test) throws Exception { test.reinitializeLauncherData(); test.mDevice.pressHome(); diff --git a/tests/src/com/android/launcher3/ui/DoubleShadowIconDrawableTest.kt b/tests/src/com/android/launcher3/ui/DoubleShadowIconDrawableTest.kt new file mode 100644 index 0000000000..1cee71c219 --- /dev/null +++ b/tests/src/com/android/launcher3/ui/DoubleShadowIconDrawableTest.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.ui + +import android.graphics.Color +import android.graphics.drawable.Drawable +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.launcher3.views.DoubleShadowIconDrawable +import com.android.launcher3.views.ShadowInfo +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.verify + +@SmallTest +@RunWith(AndroidJUnit4::class) +class DoubleShadowIconDrawableTest { + + @Test + fun `DoubleShadowIconDrawable is setup correctly from given ShadowInfo`() { + // Given + val shadowInfo: ShadowInfo = mock() + val originalDrawable: Drawable = mock() + val iconSize = 2 + val iconInsetSize = 1 + // When + val drawableUnderTest = + DoubleShadowIconDrawable(shadowInfo, originalDrawable, iconSize, iconInsetSize) + // Then + assertThat(drawableUnderTest.intrinsicHeight).isEqualTo(iconSize) + assertThat(drawableUnderTest.intrinsicWidth).isEqualTo(iconSize) + } + + @Test + fun `createShadowRenderNode creates RenderNode for shadow effects`() { + // Given + val shadowInfo = + ShadowInfo( + ambientShadowBlur = 1f, + ambientShadowColor = 2, + keyShadowBlur = 3f, + keyShadowOffsetX = 4f, + keyShadowOffsetY = 5f, + keyShadowColor = 6 + ) + val originalDrawable: Drawable = mock() + val iconSize = 2 + val iconInsetSize = 1 + // When + val shadowDrawableUnderTest = + spy(DoubleShadowIconDrawable(shadowInfo, originalDrawable, iconSize, iconInsetSize)) + shadowDrawableUnderTest.createShadowRenderNode() + // Then + verify(shadowDrawableUnderTest) + .createShadowRenderEffect( + shadowInfo.ambientShadowBlur, + 0f, + 0f, + Color.alpha(shadowInfo.ambientShadowColor).toFloat() + ) + verify(shadowDrawableUnderTest) + .createShadowRenderEffect( + shadowInfo.keyShadowBlur, + shadowInfo.keyShadowOffsetX, + shadowInfo.keyShadowOffsetY, + Color.alpha(shadowInfo.keyShadowColor).toFloat() + ) + } +} diff --git a/tests/src/com/android/launcher3/ui/ShadowInfoTest.kt b/tests/src/com/android/launcher3/ui/ShadowInfoTest.kt new file mode 100644 index 0000000000..ef4dc1a8e7 --- /dev/null +++ b/tests/src/com/android/launcher3/ui/ShadowInfoTest.kt @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.ui + +import android.content.Context +import android.content.res.TypedArray +import android.util.AttributeSet +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.launcher3.R +import com.android.launcher3.views.ShadowInfo +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +class ShadowInfoTest { + + @Test + fun `ShadowInfo is created correctly from context`() { + // Given + val mockContext: Context = mock() + val mockAttrs: AttributeSet = mock() + val styledAttrs: TypedArray = mock() + val expectedShadowInfo = + ShadowInfo( + ambientShadowBlur = 1f, + ambientShadowColor = 2, + keyShadowBlur = 3f, + keyShadowOffsetX = 4f, + keyShadowOffsetY = 5f, + keyShadowColor = 6 + ) + doReturn(styledAttrs) + .whenever(mockContext) + .obtainStyledAttributes(mockAttrs, R.styleable.ShadowInfo, 0, 0) + doReturn(1) + .whenever(styledAttrs) + .getDimensionPixelSize(R.styleable.ShadowInfo_ambientShadowBlur, 0) + doReturn(2).whenever(styledAttrs).getColor(R.styleable.ShadowInfo_ambientShadowColor, 0) + doReturn(3) + .whenever(styledAttrs) + .getDimensionPixelSize(R.styleable.ShadowInfo_keyShadowBlur, 0) + doReturn(4) + .whenever(styledAttrs) + .getDimensionPixelSize(R.styleable.ShadowInfo_keyShadowOffsetX, 0) + doReturn(5) + .whenever(styledAttrs) + .getDimensionPixelSize(R.styleable.ShadowInfo_keyShadowOffsetY, 0) + doReturn(6).whenever(styledAttrs).getColor(R.styleable.ShadowInfo_keyShadowColor, 0) + // When + val actualShadowInfo = ShadowInfo.fromContext(mockContext, mockAttrs, 0) + // Then + assertThat(actualShadowInfo.ambientShadowBlur).isEqualTo(1) + assertThat(actualShadowInfo.ambientShadowColor).isEqualTo(2) + assertThat(actualShadowInfo.keyShadowBlur).isEqualTo(3) + assertThat(actualShadowInfo.keyShadowOffsetX).isEqualTo(4) + assertThat(actualShadowInfo.keyShadowOffsetY).isEqualTo(5) + assertThat(actualShadowInfo.keyShadowColor).isEqualTo(6) + assertThat(actualShadowInfo).isEqualTo(expectedShadowInfo) + } +} diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java index 988aa94c28..b7ebfcd0d6 100644 --- a/tests/tapl/com/android/launcher3/tapl/Background.java +++ b/tests/tapl/com/android/launcher3/tapl/Background.java @@ -16,7 +16,7 @@ package com.android.launcher3.tapl; -import static com.android.launcher3.tapl.BaseOverview.TASK_RES_ID; +import static com.android.launcher3.tapl.BaseOverview.TASK_SELECTOR; import static com.android.launcher3.tapl.OverviewTask.TASK_START_EVENT; import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL; @@ -117,10 +117,10 @@ public abstract class Background extends LauncherInstrumentation.VisibleContaine // non-tablet overview, snapshots can be on either side of the swiped // task, but we still check that they become visible after swiping and // pausing. - mLauncher.waitForOverviewObject(TASK_RES_ID); + mLauncher.waitForObjectBySelector(TASK_SELECTOR); if (mLauncher.isTablet()) { List tasks = mLauncher.getDevice().findObjects( - mLauncher.getOverviewObjectSelector(TASK_RES_ID)); + TASK_SELECTOR); final int centerX = mLauncher.getDevice().getDisplayWidth() / 2; mLauncher.assertTrue( "All tasks not to the left of the swiped task", diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java index 567a8bd1c0..e71b49fcad 100644 --- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java +++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java @@ -19,7 +19,9 @@ package com.android.launcher3.tapl; import static android.view.KeyEvent.KEYCODE_ESCAPE; import static com.android.launcher3.tapl.LauncherInstrumentation.TASKBAR_RES_ID; +import static com.android.launcher3.tapl.LauncherInstrumentation.log; import static com.android.launcher3.tapl.OverviewTask.TASK_START_EVENT; +import static com.android.launcher3.tapl.TestHelpers.getOverviewPackageName; import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL; import android.graphics.Rect; @@ -35,9 +37,11 @@ import androidx.test.uiautomator.UiObject2; import com.android.launcher3.testing.shared.TestProtocol; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.Optional; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -46,7 +50,9 @@ import java.util.stream.Collectors; */ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { private static final String TAG = "BaseOverview"; - protected static final String TASK_RES_ID = "task"; + protected static final BySelector TASK_SELECTOR = By.res(Pattern.compile( + getOverviewPackageName() + + ":id/(task_view_single|task_view_grouped|task_view_desktop)")); private static final Pattern EVENT_ALT_ESC_UP = Pattern.compile( "Key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_ESCAPE.*?metaState=0"); private static final Pattern EVENT_ENTER_DOWN = Pattern.compile( @@ -56,10 +62,22 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { private static final int FLINGS_FOR_DISMISS_LIMIT = 40; + private final @Nullable UiObject2 mLiveTileTask; + + BaseOverview(LauncherInstrumentation launcher) { + this(launcher, /*launchedFromApp=*/false); + } + + BaseOverview(LauncherInstrumentation launcher, boolean launchedFromApp) { super(launcher); verifyActiveContainer(); verifyActionsViewVisibility(); + if (launchedFromApp) { + mLiveTileTask = getCurrentTaskUnchecked(); + } else { + mLiveTileTask = null; + } } @Override @@ -79,7 +97,7 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { private void flingForwardImpl() { try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer("want to fling forward in overview")) { - LauncherInstrumentation.log("Overview.flingForward before fling"); + log("Overview.flingForward before fling"); final UiObject2 overview = verifyActiveContainer(); final int leftMargin = mLauncher.getTargetInsets().left + mLauncher.getEdgeSensitivityWidth(); @@ -105,7 +123,7 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { private void flingBackwardImpl() { try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer("want to fling backward in overview")) { - LauncherInstrumentation.log("Overview.flingBackward before fling"); + log("Overview.flingBackward before fling"); final UiObject2 overview = verifyActiveContainer(); final int rightMargin = mLauncher.getTargetInsets().right + mLauncher.getEdgeSensitivityWidth(); @@ -276,37 +294,56 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { */ @NonNull public OverviewTask getCurrentTask() { + UiObject2 currentTask = getCurrentTaskUnchecked(); + mLauncher.assertNotNull("Unable to find a task", currentTask); + return new OverviewTask(mLauncher, currentTask, this); + } + + @Nullable + private UiObject2 getCurrentTaskUnchecked() { final List taskViews = getTasks(); - mLauncher.assertNotEquals("Unable to find a task", 0, taskViews.size()); + if (taskViews.isEmpty()) { + return null; + } // The widest, and most top-right task should be the current task - UiObject2 currentTask = Collections.max(taskViews, + return Collections.max(taskViews, Comparator.comparingInt((UiObject2 t) -> t.getVisibleBounds().width()) .thenComparingInt((UiObject2 t) -> t.getVisibleCenter().x) .thenComparing(Comparator.comparing( (UiObject2 t) -> t.getVisibleCenter().y).reversed())); - return new OverviewTask(mLauncher, currentTask, this); } - /** Returns an overview task matching TestActivity {@param activityNumber}. */ + /** + * Returns an overview task that contains the specified test activity in its thumbnails. + * + * @param activityIndex index of TestActivity to match against + */ @NonNull - public OverviewTask getTestActivityTask(int activityNumber) { + public OverviewTask getTestActivityTask(int activityIndex) { + return getTestActivityTask(Collections.singleton(activityIndex)); + } + + /** + * Returns an overview task that contains all the specified test activities in its thumbnails. + * + * @param activityNumbers collection of indices of TestActivity to match against + */ + @NonNull + public OverviewTask getTestActivityTask(Collection activityNumbers) { final List taskViews = getTasks(); mLauncher.assertNotEquals("Unable to find a task", 0, taskViews.size()); - final String activityName = "TestActivity" + activityNumber; - UiObject2 task = null; - for (UiObject2 taskView : taskViews) { - // TODO(b/239452415): Use equals instead of descEndsWith - if (taskView.getParent().hasObject(By.descEndsWith(activityName))) { - task = taskView; - break; - } - } - mLauncher.assertNotNull( - "Unable to find a task with " + activityName + " from the task list", task); + Optional task = taskViews.stream().filter( + taskView -> activityNumbers.stream().allMatch(activityNumber -> + // TODO(b/239452415): Use equals instead of descEndsWith + taskView.hasObject(By.descEndsWith("TestActivity" + activityNumber)) + )).findFirst(); - return new OverviewTask(mLauncher, task, this); + mLauncher.assertTrue("Unable to find a task with test activities " + activityNumbers + + " from the task list", task.isPresent()); + + return new OverviewTask(mLauncher, task.get(), this); } /** @@ -328,8 +365,7 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( "want to get overview tasks")) { verifyActiveContainer(); - return mLauncher.getDevice().findObjects( - mLauncher.getOverviewObjectSelector(TASK_RES_ID)); + return mLauncher.getDevice().findObjects(TASK_SELECTOR); } } @@ -506,4 +542,10 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { } return null; } + + protected boolean isLiveTile(UiObject2 task) { + // UiObject2.equals returns false even when mLiveTileTask and task have the same node, hence + // compare only hashCode as a workaround. + return mLiveTileTask != null && mLiveTileTask.hashCode() == task.hashCode(); + } } diff --git a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java index 200f2fff17..b3ad930659 100644 --- a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java +++ b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java @@ -33,6 +33,7 @@ import android.view.InputDevice; import android.view.MotionEvent; import android.view.ViewConfiguration; +import androidx.annotation.NonNull; import androidx.test.uiautomator.Condition; import androidx.test.uiautomator.UiDevice; @@ -75,6 +76,20 @@ public final class LaunchedAppState extends Background { return false; } + @NonNull + @Override + public BaseOverview switchToOverview() { + try (LauncherInstrumentation.Closable ignored = mLauncher.eventsCheck(); + LauncherInstrumentation.Closable ignored1 = mLauncher.addContextLayer( + "want to switch from background to overview")) { + verifyActiveContainer(); + goToOverviewUnchecked(); + return mLauncher.is3PLauncher() + ? new BaseOverview(mLauncher, /*launchedFromApp=*/true) + : new Overview(mLauncher, /*launchedFromApp=*/true); + } + } + /** * Returns the taskbar. * diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index 75c1b24e23..a87406286b 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -1585,7 +1585,7 @@ public final class LauncherInstrumentation { return objects; } - private UiObject2 waitForObjectBySelector(BySelector selector) { + UiObject2 waitForObjectBySelector(BySelector selector) { Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE, "LauncherInstrumentation.waitForObjectBySelector"); final UiObject2 object = mDevice.wait(Until.findObject(selector), WAIT_TIME_MS); diff --git a/tests/tapl/com/android/launcher3/tapl/Overview.java b/tests/tapl/com/android/launcher3/tapl/Overview.java index 50c21368e0..deb27e4f87 100644 --- a/tests/tapl/com/android/launcher3/tapl/Overview.java +++ b/tests/tapl/com/android/launcher3/tapl/Overview.java @@ -22,9 +22,12 @@ import com.android.launcher3.tapl.LauncherInstrumentation.ContainerType; * Overview pane. */ public class Overview extends BaseOverview { - Overview(LauncherInstrumentation launcher) { - super(launcher); + this(launcher, /*launchedFromApp=*/false); + } + + Overview(LauncherInstrumentation launcher, boolean launchedFromApp) { + super(launcher, launchedFromApp); } @Override diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java index 6f420afe78..7a8ab492b5 100644 --- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java +++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java @@ -40,16 +40,23 @@ import java.util.stream.Collectors; public final class OverviewTask { private static final String SYSTEMUI_PACKAGE = "com.android.systemui"; static final Pattern TASK_START_EVENT = Pattern.compile("startActivityFromRecentsAsync"); + static final Pattern TASK_START_EVENT_DESKTOP = Pattern.compile("launchDesktopFromRecents"); + static final Pattern TASK_START_EVENT_LIVE_TILE = Pattern.compile( + "composeRecentsLaunchAnimator"); static final Pattern SPLIT_SELECT_EVENT = Pattern.compile("enterSplitSelect"); static final Pattern SPLIT_START_EVENT = Pattern.compile("launchSplitTasks"); private final LauncherInstrumentation mLauncher; + @NonNull private final UiObject2 mTask; + private final TaskViewType mType; private final BaseOverview mOverview; - OverviewTask(LauncherInstrumentation launcher, UiObject2 task, BaseOverview overview) { + OverviewTask(LauncherInstrumentation launcher, @NonNull UiObject2 task, BaseOverview overview) { mLauncher = launcher; + mLauncher.assertNotNull("task must not be null", task); mTask = task; mOverview = overview; + mType = getType(); verifyActiveContainer(); } @@ -220,7 +227,22 @@ public final class OverviewTask { return new LaunchedAppState(mLauncher); } } else { - mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT); + final Pattern event; + if (mOverview.isLiveTile(mTask)) { + event = TASK_START_EVENT_LIVE_TILE; + } else if (mType == TaskViewType.DESKTOP) { + event = TASK_START_EVENT_DESKTOP; + } else { + event = TASK_START_EVENT; + } + mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, event); + + if (mType == TaskViewType.DESKTOP) { + try (LauncherInstrumentation.Closable ignored = mLauncher.addContextLayer( + "launched desktop")) { + mLauncher.waitForSystemUiObject("desktop_mode_caption"); + } + } return new LaunchedAppState(mLauncher); } } @@ -273,6 +295,17 @@ public final class OverviewTask { return actual.contains(expected); } + private TaskViewType getType() { + String resourceName = mTask.getResourceName(); + if (resourceName.endsWith("task_view_grouped")) { + return TaskViewType.GROUPED; + } else if (resourceName.endsWith("task_view_desktop")) { + return TaskViewType.DESKTOP; + } else { + return TaskViewType.SINGLE; + } + } + /** * Enum used to specify which task is retrieved when it is a split task. */ @@ -292,4 +325,10 @@ public final class OverviewTask { this.iconAppRes = iconAppRes; } } + + private enum TaskViewType { + SINGLE, + GROUPED, + DESKTOP + } } diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java b/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java index 902ad5b568..90d32f3249 100644 --- a/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java +++ b/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java @@ -97,20 +97,35 @@ public class OverviewTaskMenu { } } + /** + * Taps the Desktop item from the overview task menu and returns the LaunchedAppState + * representing the Desktop. + */ + @NonNull + public LaunchedAppState tapDesktopMenuItem() { + try (LauncherInstrumentation.Closable ignored = mLauncher.eventsCheck(); + LauncherInstrumentation.Closable ignored1 = mLauncher.addContextLayer( + "before tapping the desktop menu item")) { + mLauncher.executeAndWaitForLauncherStop( + () -> mLauncher.clickLauncherObject( + mLauncher.findObjectInContainer(mMenu, By.text("Desktop"))), + "tapped desktop menu item"); + + try (LauncherInstrumentation.Closable ignored2 = mLauncher.addContextLayer( + "tapped desktop menu item")) { + mLauncher.waitUntilSystemLauncherObjectGone("overview_panel"); + mLauncher.waitForSystemUiObject("desktop_mode_caption"); + return new LaunchedAppState(mLauncher); + } + } + } + /** Returns true if an item matching the given string is present in the menu. */ public boolean hasMenuItem(String expectedMenuItemText) { UiObject2 menuItem = mLauncher.findObjectInContainer(mMenu, By.text(expectedMenuItemText)); return menuItem != null; } - /** - * Returns the menu item specified by name if present. - */ - public OverviewTaskMenuItem getMenuItemByName(String menuItemName) { - return new OverviewTaskMenuItem(mLauncher, - mLauncher.waitForObjectInContainer(mMenu, By.text(menuItemName))); - } - /** * Taps outside task menu to dismiss it. */ diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenuItem.java b/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenuItem.java deleted file mode 100644 index e3035bf71b..0000000000 --- a/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenuItem.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2023 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.tapl; - -import android.graphics.Rect; - -import androidx.test.uiautomator.UiObject2; - -/** Represents an item in the overview task menu. */ -public class OverviewTaskMenuItem { - - private final LauncherInstrumentation mLauncher; - private final UiObject2 mMenuItem; - - OverviewTaskMenuItem(LauncherInstrumentation launcher, UiObject2 menuItem) { - mLauncher = launcher; - mMenuItem = menuItem; - } - - /** - * Returns this menu item's visible bounds. - */ - public Rect getVisibleBounds() { - return mMenuItem.getVisibleBounds(); - } -}