Merge "Support switch to screenshot for TTV" into main
diff --git a/quickstep/AndroidManifest-launcher.xml b/quickstep/AndroidManifest-launcher.xml
index 80d8154..c6e2d8c 100644
--- a/quickstep/AndroidManifest-launcher.xml
+++ b/quickstep/AndroidManifest-launcher.xml
@@ -48,7 +48,7 @@
android:stateNotNeeded="true"
android:windowSoftInputMode="adjustPan"
android:screenOrientation="unspecified"
- android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
+ android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
android:resizeableActivity="true"
android:resumeWhilePausing="true"
android:taskAffinity=""
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 4abf6e1..bf198b6 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -80,7 +80,7 @@
android:stateNotNeeded="true"
android:theme="@style/LauncherTheme"
android:screenOrientation="behind"
- android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
+ android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
android:resizeableActivity="true"
android:resumeWhilePausing="true"
android:enableOnBackInvokedCallback="false"
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
index d57c483..06376d3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
@@ -33,6 +33,7 @@
import android.widget.TextView
import androidx.annotation.IntDef
import androidx.annotation.LayoutRes
+import androidx.annotation.VisibleForTesting
import androidx.core.text.HtmlCompat
import androidx.core.view.updateLayoutParams
import com.airbnb.lottie.LottieAnimationView
@@ -87,7 +88,7 @@
!activityContext.isTinyTaskbar
}
- private val isOpen: Boolean
+ val isTooltipOpen: Boolean
get() = tooltip?.isOpen ?: false
val isBeforeTooltipFeaturesStep: Boolean
@@ -96,7 +97,8 @@
private lateinit var controllers: TaskbarControllers
// Keep track of whether the user has seen the Search Edu
- private var userHasSeenSearchEdu: Boolean
+ @VisibleForTesting
+ var userHasSeenSearchEdu: Boolean
get() {
return TASKBAR_SEARCH_EDU_SEEN.get(activityContext)
}
@@ -409,7 +411,7 @@
override fun dumpLogs(prefix: String?, pw: PrintWriter?) {
pw?.println(prefix + "TaskbarEduTooltipController:")
pw?.println("$prefix\tisTooltipEnabled=$isTooltipEnabled")
- pw?.println("$prefix\tisOpen=$isOpen")
+ pw?.println("$prefix\tisOpen=$isTooltipOpen")
pw?.println("$prefix\ttooltipStep=$tooltipStep")
}
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 e42b6d6..59fc76c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt
@@ -81,12 +81,7 @@
controllersAfterInitAction: ControllersAfterInitAction
)
- /** Sets stashed and expanded state of the bubble bar */
- fun updateStashedAndExpandedState(stash: Boolean = false, expand: Boolean = false)
-
- /**
- * Shows the bubble bar at [getBubbleBarTranslationY] position immediately without animation.
- */
+ /** Shows the bubble bar at [bubbleBarTranslationY] position immediately without animation. */
fun showBubbleBarImmediate()
/** Shows the bubble bar at [bubbleBarTranslationY] position immediately without animation. */
@@ -120,21 +115,17 @@
* Stashes the bubble bar (transform to the handle view), or just shrink width of the expanded
* bubble bar based on the controller implementation.
*/
- fun stashBubbleBar() {
- updateStashedAndExpandedState(stash = true, expand = false)
- }
+ fun stashBubbleBar()
/** Shows the bubble bar, and expands bubbles depending on [expandBubbles]. */
- fun showBubbleBar(expandBubbles: Boolean) {
- updateStashedAndExpandedState(stash = false, expandBubbles)
- }
+ fun showBubbleBar(expandBubbles: Boolean)
// TODO(b/354218264): Move to BubbleBarViewAnimator
/**
* The difference on the Y axis between the center of the handle and the center of the bubble
* bar.
*/
- fun getSlideInAnimationDistanceY(): Float
+ fun getDiffBetweenHandleAndBarCenters(): Float
// TODO(b/354218264): Move to BubbleBarViewAnimator
/** The distance the handle moves as part of the new bubble animation. */
@@ -190,13 +181,5 @@
/** The scale bubble bar animates to when being stashed. */
const val STASHED_BAR_SCALE = 0.5f
-
- /** Creates new instance of [BubbleStashController] */
- @JvmStatic
- fun newInstance(
- taskbarHotseatDimensionsProvider: TaskbarHotseatDimensionsProvider
- ): BubbleStashController {
- return PersistentTaskbarStashController(taskbarHotseatDimensionsProvider)
- }
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentTaskbarStashController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashController.kt
similarity index 94%
rename from quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentTaskbarStashController.kt
rename to quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashController.kt
index 4a05a5e..62fe221 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentTaskbarStashController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashController.kt
@@ -33,7 +33,7 @@
import com.android.wm.shell.common.bubbles.BubbleBarLocation
import com.android.wm.shell.shared.animation.PhysicsAnimator
-class PersistentTaskbarStashController(
+class PersistentBubbleStashController(
private val taskbarHotseatDimensionsProvider: TaskbarHotseatDimensionsProvider,
) : BubbleStashController {
@@ -54,7 +54,7 @@
}
if (onHome) {
// When transition to home we should show collapse the bubble bar
- updateStashedAndExpandedState(stash = false, expand = false)
+ updateExpandedState(expand = false)
}
animateBubbleBarY()
bubbleBarViewController.onBubbleBarConfigurationChanged(/* animate= */ true)
@@ -66,7 +66,7 @@
field = onOverview
if (!onOverview) {
// When transition from overview we should show collapse the bubble bar
- updateStashedAndExpandedState(stash = false, expand = false)
+ updateExpandedState(expand = false)
}
bubbleBarViewController.onBubbleBarConfigurationChanged(/* animate= */ true)
}
@@ -114,7 +114,8 @@
this.bubbleBarViewController = bubbleBarViewController
this.controllersAfterInitAction = controllersAfterInitAction
bubbleBarTranslationYAnimator = bubbleBarViewController.bubbleBarTranslationY
- bubbleBarAlphaAnimator = bubbleBarViewController.bubbleBarAlpha.get(0)
+ // bubble bar has only alpha property, getting it at index 0
+ bubbleBarAlphaAnimator = bubbleBarViewController.bubbleBarAlpha.get(/* index= */ 0)
bubbleBarScaleAnimator = bubbleBarViewController.bubbleBarScale
}
@@ -131,16 +132,6 @@
animatorSet.setDuration(BAR_STASH_DURATION).start()
}
- override fun updateStashedAndExpandedState(stash: Boolean, expand: Boolean) {
- if (bubbleBarViewController.isHiddenForNoBubbles) {
- // If there are no bubbles the bar is invisible, nothing to do here.
- return
- }
- if (bubbleBarViewController.isExpanded != expand) {
- bubbleBarViewController.isExpanded = expand
- }
- }
-
override fun showBubbleBarImmediate() = showBubbleBarImmediate(bubbleBarTranslationY)
override fun showBubbleBarImmediate(bubbleBarTranslationY: Float) {
@@ -154,6 +145,14 @@
// operation is performed.
}
+ override fun stashBubbleBar() {
+ updateExpandedState(expand = false)
+ }
+
+ override fun showBubbleBar(expandBubbles: Boolean) {
+ updateExpandedState(expandBubbles)
+ }
+
override fun stashBubbleBarImmediate() {
// When the bubble bar is shown for the persistent task bar, there is no handle view, so no
// operation is performed.
@@ -176,7 +175,7 @@
override fun isEventOverBubbleBarViews(ev: MotionEvent): Boolean =
bubbleBarViewController.isEventOverAnyItem(ev)
- override fun getSlideInAnimationDistanceY(): Float {
+ override fun getDiffBetweenHandleAndBarCenters(): Float {
// distance from the bottom of the screen and the bubble bar center.
return -bubbleBarViewController.bubbleBarCollapsedHeight / 2f
}
@@ -199,6 +198,16 @@
// no op since does not have a handle view
}
+ private fun updateExpandedState(expand: Boolean) {
+ if (bubbleBarViewController.isHiddenForNoBubbles) {
+ // If there are no bubbles the bar is invisible, nothing to do here.
+ return
+ }
+ if (bubbleBarViewController.isExpanded != expand) {
+ bubbleBarViewController.isExpanded = expand
+ }
+ }
+
/** Animates bubble bar Y accordingly to the showing mode */
private fun animateBubbleBarY() {
val animator =
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
new file mode 100644
index 0000000..23e009b
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.bubbles.stashing
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
+import android.content.res.Resources
+import android.view.MotionEvent
+import android.view.View
+import androidx.annotation.VisibleForTesting
+import com.android.launcher3.R
+import com.android.launcher3.anim.AnimatedFloat
+import com.android.launcher3.taskbar.StashedHandleViewController
+import com.android.launcher3.taskbar.TaskbarInsetsController
+import com.android.launcher3.taskbar.bubbles.BubbleBarViewController
+import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController
+import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.BAR_STASH_DURATION
+import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.BAR_TRANSLATION_DURATION
+import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.STASHED_BAR_SCALE
+import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.ControllersAfterInitAction
+import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.TaskbarHotseatDimensionsProvider
+import com.android.launcher3.util.MultiPropertyFactory
+import com.android.wm.shell.common.bubbles.BubbleBarLocation
+import com.android.wm.shell.shared.animation.PhysicsAnimator
+
+class TransientBubbleStashController(
+ private val taskbarHotseatDimensionsProvider: TaskbarHotseatDimensionsProvider,
+ resources: Resources
+) : BubbleStashController {
+
+ private lateinit var bubbleBarViewController: BubbleBarViewController
+ private lateinit var taskbarInsetsController: TaskbarInsetsController
+ private lateinit var controllersAfterInitAction: ControllersAfterInitAction
+
+ // stash view properties
+ private var bubbleStashedHandleViewController: BubbleStashedHandleViewController? = null
+ private var stashHandleViewAlpha: MultiPropertyFactory<View>.MultiProperty? = null
+ private var stashedHeight: Int = 0
+
+ // bubble bar properties
+ private lateinit var bubbleBarAlpha: MultiPropertyFactory<View>.MultiProperty
+ private lateinit var bubbleBarTranslationYAnimator: AnimatedFloat
+ private lateinit var bubbleBarScale: AnimatedFloat
+ private val mHandleCenterFromScreenBottom =
+ resources.getDimensionPixelSize(R.dimen.bubblebar_stashed_size) / 2f
+
+ private var animator: AnimatorSet? = null
+
+ override var isStashed: Boolean = false
+ @VisibleForTesting set
+
+ override var isBubblesShowingOnHome: Boolean = false
+ set(onHome) {
+ if (field == onHome) return
+ field = onHome
+ if (!bubbleBarViewController.hasBubbles()) {
+ // if there are no bubbles, there's nothing to show, so just return.
+ return
+ }
+ if (onHome) {
+ updateStashedAndExpandedState(stash = false, expand = false)
+ // When transitioning from app to home we need to animate the bubble bar
+ // here to align with hotseat center.
+ animateBubbleBarYToHotseat()
+ } else if (!bubbleBarViewController.isExpanded) {
+ updateStashedAndExpandedState(stash = true, expand = false)
+ }
+ bubbleBarViewController.onBubbleBarConfigurationChanged(/* animate= */ true)
+ }
+
+ override var isBubblesShowingOnOverview: Boolean = false
+ set(onOverview) {
+ if (field == onOverview) return
+ field = onOverview
+ if (onOverview) {
+ // When transitioning to overview we need to animate the bubble bar to align with
+ // the taskbar bottom.
+ animateBubbleBarYToTaskbar()
+ } else {
+ updateStashedAndExpandedState(stash = true, expand = false)
+ }
+ bubbleBarViewController.onBubbleBarConfigurationChanged(/* animate= */ true)
+ }
+
+ override var isSysuiLocked: Boolean = false
+ set(isLocked) {
+ if (field == isLocked) return
+ field = isLocked
+ if (!isLocked && bubbleBarViewController.hasBubbles()) {
+ animateAfterUnlock()
+ }
+ }
+
+ override val isTransientTaskBar: Boolean = true
+
+ override val bubbleBarTranslationYForHotseat: Float
+ get() {
+ val hotseatBottomSpace = taskbarHotseatDimensionsProvider.getHotseatBottomSpace()
+ val hotseatCellHeight = taskbarHotseatDimensionsProvider.getHotseatHeight()
+ val bubbleBarHeight: Float = bubbleBarViewController.bubbleBarCollapsedHeight
+ return -hotseatBottomSpace - (hotseatCellHeight - bubbleBarHeight) / 2
+ }
+
+ override val bubbleBarTranslationYForTaskbar: Float =
+ -taskbarHotseatDimensionsProvider.getTaskbarBottomSpace().toFloat()
+
+ /** Check if we have handle view controller */
+ override val hasHandleView: Boolean
+ get() = bubbleStashedHandleViewController != null
+
+ override fun init(
+ taskbarInsetsController: TaskbarInsetsController,
+ bubbleBarViewController: BubbleBarViewController,
+ bubbleStashedHandleViewController: BubbleStashedHandleViewController?,
+ controllersAfterInitAction: ControllersAfterInitAction
+ ) {
+ this.taskbarInsetsController = taskbarInsetsController
+ this.bubbleBarViewController = bubbleBarViewController
+ this.bubbleStashedHandleViewController = bubbleStashedHandleViewController
+ this.controllersAfterInitAction = controllersAfterInitAction
+ bubbleBarTranslationYAnimator = bubbleBarViewController.bubbleBarTranslationY
+ // bubble bar has only alpha property, getting it at index 0
+ bubbleBarAlpha = bubbleBarViewController.bubbleBarAlpha.get(/* index= */ 0)
+ bubbleBarScale = bubbleBarViewController.bubbleBarScale
+ stashedHeight = bubbleStashedHandleViewController?.stashedHeight ?: 0
+ stashHandleViewAlpha =
+ bubbleStashedHandleViewController
+ ?.stashedHandleAlpha
+ ?.get(StashedHandleViewController.ALPHA_INDEX_STASHED)
+ }
+
+ private fun animateAfterUnlock() {
+ val animatorSet = AnimatorSet()
+ if (isBubblesShowingOnHome || isBubblesShowingOnOverview) {
+ isStashed = false
+ animatorSet.playTogether(
+ bubbleBarScale.animateToValue(1f),
+ bubbleBarTranslationYAnimator.animateToValue(bubbleBarTranslationY),
+ bubbleBarAlpha.animateToValue(1f)
+ )
+ } else {
+ isStashed = true
+ stashHandleViewAlpha?.let { animatorSet.playTogether(it.animateToValue(1f)) }
+ }
+ animatorSet.updateTouchRegionOnAnimationEnd().setDuration(BAR_STASH_DURATION).start()
+ }
+
+ override fun showBubbleBarImmediate() {
+ showBubbleBarImmediate(bubbleBarTranslationY)
+ }
+
+ override fun showBubbleBarImmediate(bubbleBarTranslationY: Float) {
+ bubbleStashedHandleViewController?.setTranslationYForSwipe(0f)
+ stashHandleViewAlpha?.value = 0f
+ this.bubbleBarTranslationYAnimator.updateValue(bubbleBarTranslationY)
+ bubbleBarAlpha.setValue(1f)
+ bubbleBarScale.updateValue(1f)
+ isStashed = false
+ onIsStashedChanged()
+ }
+
+ override fun stashBubbleBarImmediate() {
+ bubbleStashedHandleViewController?.setTranslationYForSwipe(0f)
+ stashHandleViewAlpha?.value = 1f
+ this.bubbleBarTranslationYAnimator.updateValue(getStashTranslation())
+ bubbleBarAlpha.setValue(0f)
+ bubbleBarScale.updateValue(STASHED_BAR_SCALE)
+ isStashed = true
+ onIsStashedChanged()
+ }
+
+ override fun getTouchableHeight(): Int =
+ when {
+ isStashed -> stashedHeight
+ isBubbleBarVisible() -> bubbleBarViewController.bubbleBarCollapsedHeight.toInt()
+ else -> 0
+ }
+
+ override fun isBubbleBarVisible(): Boolean = bubbleBarViewController.hasBubbles() && !isStashed
+
+ override fun onNewBubbleAnimationInterrupted(isStashed: Boolean, bubbleBarTranslationY: Float) =
+ if (isStashed) {
+ stashBubbleBarImmediate()
+ } else {
+ showBubbleBarImmediate(bubbleBarTranslationY)
+ }
+
+ /** Check if [ev] belongs to the stash handle or the bubble bar views. */
+ override fun isEventOverBubbleBarViews(ev: MotionEvent): Boolean {
+ val isOverHandle = bubbleStashedHandleViewController?.isEventOverHandle(ev) ?: false
+ return isOverHandle || bubbleBarViewController.isEventOverAnyItem(ev)
+ }
+
+ /** Set the bubble bar stash handle location . */
+ override fun setBubbleBarLocation(bubbleBarLocation: BubbleBarLocation) {
+ bubbleStashedHandleViewController?.setBubbleBarLocation(bubbleBarLocation)
+ }
+
+ override fun stashBubbleBar() {
+ updateStashedAndExpandedState(stash = true, expand = false)
+ }
+
+ override fun showBubbleBar(expandBubbles: Boolean) {
+ updateStashedAndExpandedState(stash = false, expandBubbles)
+ }
+
+ 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.
+ val barCenter: Float = bubbleBarViewController.bubbleBarCollapsedHeight / 2f
+ return mHandleCenterFromScreenBottom - barCenter
+ }
+
+ override fun getStashedHandleTranslationForNewBubbleAnimation(): Float {
+ return -mHandleCenterFromScreenBottom
+ }
+
+ override fun getStashedHandlePhysicsAnimator(): PhysicsAnimator<View>? {
+ return bubbleStashedHandleViewController?.physicsAnimator
+ }
+
+ override fun updateTaskbarTouchRegion() {
+ taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
+ }
+
+ override fun setHandleTranslationY(translationY: Float) {
+ bubbleStashedHandleViewController?.setTranslationYForSwipe(translationY)
+ }
+
+ private fun getStashTranslation(): Float {
+ return (bubbleBarViewController.bubbleBarCollapsedHeight - stashedHeight) / 2f
+ }
+
+ /**
+ * Create a stash animation.
+ *
+ * @param isStashed whether it's a stash animation or an unstash animation
+ * @param duration duration of the animation
+ * @return the animation
+ */
+ @Suppress("SameParameterValue")
+ private fun createStashAnimator(isStashed: Boolean, duration: Long): AnimatorSet {
+ val animatorSet = AnimatorSet()
+ val fullLengthAnimatorSet = AnimatorSet()
+ // Not exactly half and may overlap. See [first|second]HalfDurationScale below.
+ val firstHalfAnimatorSet = AnimatorSet()
+ val secondHalfAnimatorSet = AnimatorSet()
+ val firstHalfDurationScale: Float
+ val secondHalfDurationScale: Float
+ val stashHandleAlphaValue: Float
+ if (isStashed) {
+ firstHalfDurationScale = 0.75f
+ secondHalfDurationScale = 0.5f
+ stashHandleAlphaValue = 1f
+ fullLengthAnimatorSet.play(
+ bubbleBarTranslationYAnimator.animateToValue(getStashTranslation())
+ )
+ firstHalfAnimatorSet.playTogether(
+ bubbleBarAlpha.animateToValue(0f),
+ bubbleBarScale.animateToValue(STASHED_BAR_SCALE)
+ )
+ } else {
+ firstHalfDurationScale = 0.5f
+ secondHalfDurationScale = 0.75f
+ stashHandleAlphaValue = 0f
+ fullLengthAnimatorSet.playTogether(
+ bubbleBarScale.animateToValue(1f),
+ bubbleBarTranslationYAnimator.animateToValue(bubbleBarTranslationY)
+ )
+ secondHalfAnimatorSet.playTogether(bubbleBarAlpha.animateToValue(1f))
+ }
+ stashHandleViewAlpha?.let {
+ secondHalfAnimatorSet.playTogether(it.animateToValue(stashHandleAlphaValue))
+ }
+ bubbleStashedHandleViewController?.createRevealAnimToIsStashed(isStashed)?.let {
+ fullLengthAnimatorSet.play(it)
+ }
+ fullLengthAnimatorSet.setDuration(duration)
+ firstHalfAnimatorSet.setDuration((duration * firstHalfDurationScale).toLong())
+ secondHalfAnimatorSet.setDuration((duration * secondHalfDurationScale).toLong())
+ secondHalfAnimatorSet.startDelay = (duration * (1 - secondHalfDurationScale)).toLong()
+ animatorSet.playTogether(fullLengthAnimatorSet, firstHalfAnimatorSet, secondHalfAnimatorSet)
+ animatorSet.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ animator = null
+ controllersAfterInitAction.runAfterInit {
+ if (isStashed) {
+ bubbleBarViewController.isExpanded = false
+ }
+ taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
+ }
+ }
+ }
+ )
+ return animatorSet
+ }
+
+ private fun onIsStashedChanged() {
+ controllersAfterInitAction.runAfterInit {
+ taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
+ bubbleStashedHandleViewController?.onIsStashedChanged()
+ }
+ }
+
+ private fun animateBubbleBarYToHotseat() {
+ translateBubbleBarYUpdateTouchRegionOnCompletion(bubbleBarTranslationYForHotseat)
+ }
+
+ private fun animateBubbleBarYToTaskbar() {
+ translateBubbleBarYUpdateTouchRegionOnCompletion(bubbleBarTranslationYForTaskbar)
+ }
+
+ private fun translateBubbleBarYUpdateTouchRegionOnCompletion(toY: Float) {
+ bubbleBarViewController.bubbleBarTranslationY
+ .animateToValue(toY)
+ .updateTouchRegionOnAnimationEnd()
+ .setDuration(BAR_TRANSLATION_DURATION)
+ .start()
+ }
+
+ @VisibleForTesting
+ fun updateStashedAndExpandedState(stash: Boolean, expand: Boolean) {
+ if (bubbleBarViewController.isHiddenForNoBubbles) {
+ // If there are no bubbles the bar and handle are invisible, nothing to do here.
+ return
+ }
+ val isStashed = stash && !isBubblesShowingOnHome && !isBubblesShowingOnOverview
+ if (this.isStashed != isStashed) {
+ this.isStashed = isStashed
+ // notify the view controller that the stash state is about to change so that it can
+ // cancel an ongoing animation if there is one.
+ // note that this has to be called before updating mIsStashed with the new value,
+ // otherwise interrupting an ongoing animation may update it again with the wrong state
+ bubbleBarViewController.onStashStateChanging()
+ animator?.cancel()
+ animator =
+ createStashAnimator(isStashed, BAR_STASH_DURATION).apply {
+ updateTouchRegionOnAnimationEnd()
+ start()
+ }
+ }
+ if (bubbleBarViewController.isExpanded != expand) {
+ bubbleBarViewController.isExpanded = expand
+ }
+ }
+
+ private fun Animator.updateTouchRegionOnAnimationEnd(): Animator {
+ this.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ onIsStashedChanged()
+ }
+ }
+ )
+ return this
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index bbc6b42..d678c46 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -258,6 +258,8 @@
private boolean mCanShowAllAppsEducationView;
+ private boolean mIsOverlayVisible;
+
public static QuickstepLauncher getLauncher(Context context) {
return fromContext(context);
}
@@ -495,7 +497,8 @@
(getActivityFlags() & ACTIVITY_STATE_USER_WILL_BE_ACTIVE) != 0;
boolean visible = (state == NORMAL || state == OVERVIEW)
&& (willUserBeActive || isUserActive())
- && !profile.isVerticalBarLayout();
+ && !profile.isVerticalBarLayout()
+ && !mIsOverlayVisible;
SystemUiProxy.INSTANCE.get(this)
.setLauncherKeepClearAreaHeight(visible, profile.hotseatBarSizePx);
}
@@ -505,6 +508,12 @@
}
@Override
+ public void onOverlayVisibilityChanged(boolean visible) {
+ super.onOverlayVisibilityChanged(visible);
+ mIsOverlayVisible = visible;
+ }
+
+ @Override
public void bindExtraContainerItems(FixedContainerItems item) {
if (item.containerId == Favorites.CONTAINER_PREDICTION) {
mAllAppsPredictions = item;
diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
index a922e37..af32ba2 100644
--- a/quickstep/src/com/android/quickstep/views/TaskContainer.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
@@ -116,7 +116,6 @@
taskContainerViewModel.shouldShowThumbnailSplash(task.key.id)
else thumbnailViewDeprecated.shouldShowSplashView()
- // TODO(b/350743460) Support sysUiStatusNavFlags for new TTV.
val sysUiStatusNavFlags: Int
get() =
if (enableRefactorTaskThumbnail())
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarControllerTestUtil.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarControllerTestUtil.kt
new file mode 100644
index 0000000..a57fb70
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarControllerTestUtil.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar
+
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+
+object TaskbarControllerTestUtil {
+ inline fun runOnMainSync(crossinline runTest: () -> Unit) {
+ getInstrumentation().runOnMainSync { runTest() }
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarEduTooltipControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarEduTooltipControllerTest.kt
new file mode 100644
index 0000000..e583f63
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarEduTooltipControllerTest.kt
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar
+
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.Utilities
+import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync
+import com.android.launcher3.taskbar.rules.TaskbarModeRule
+import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.PINNED
+import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.THREE_BUTTONS
+import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.TRANSIENT
+import com.android.launcher3.taskbar.rules.TaskbarModeRule.TaskbarMode
+import com.android.launcher3.taskbar.rules.TaskbarPinningPreferenceRule
+import com.android.launcher3.taskbar.rules.TaskbarPreferenceRule
+import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule
+import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController
+import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
+import com.android.launcher3.util.OnboardingPrefs
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(LauncherMultivalentJUnit::class)
+@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"])
+class TaskbarEduTooltipControllerTest {
+
+ private val context =
+ TaskbarWindowSandboxContext.create(
+ InstrumentationRegistry.getInstrumentation().targetContext
+ )
+
+ @get:Rule
+ val tooltipStepPreferenceRule =
+ TaskbarPreferenceRule(
+ context,
+ OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP.prefItem,
+ )
+
+ @get:Rule
+ val searchEduPreferenceRule =
+ TaskbarPreferenceRule(
+ context,
+ OnboardingPrefs.TASKBAR_SEARCH_EDU_SEEN,
+ )
+
+ @get:Rule val taskbarPinningPreferenceRule = TaskbarPinningPreferenceRule(context)
+
+ @get:Rule val taskbarModeRule = TaskbarModeRule(context)
+
+ @get:Rule val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
+
+ @InjectController lateinit var taskbarEduTooltipController: TaskbarEduTooltipController
+
+ private val taskbarContext: TaskbarActivityContext
+ get() = taskbarUnitTestRule.activityContext
+
+ private val wasInTestHarness = Utilities.isRunningInTestHarness()
+
+ @Before
+ fun setUp() {
+ Utilities.disableRunningInTestHarnessForTests()
+ }
+
+ @After
+ fun tearDown() {
+ if (wasInTestHarness) {
+ Utilities.enableRunningInTestHarnessForTests()
+ }
+ }
+
+ @Test
+ @TaskbarMode(THREE_BUTTONS)
+ fun testMaybeShowSwipeEdu_whenTaskbarIsInThreeButtonMode_doesNotShowSwipeEdu() {
+ tooltipStepPreferenceRule.value = TOOLTIP_STEP_SWIPE
+ assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_SWIPE)
+ runOnMainSync { taskbarEduTooltipController.maybeShowSwipeEdu() }
+ assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_SWIPE)
+ assertThat(taskbarEduTooltipController.isTooltipOpen).isFalse()
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testMaybeShowSwipeEdu_whenSwipeEduAlreadyShown_doesNotShowSwipeEdu() {
+ tooltipStepPreferenceRule.value = TOOLTIP_STEP_FEATURES
+ assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_FEATURES)
+ runOnMainSync { taskbarEduTooltipController.maybeShowSwipeEdu() }
+ assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_FEATURES)
+ assertThat(taskbarEduTooltipController.isTooltipOpen).isFalse()
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testMaybeShowSwipeEdu_whenUserHasNotSeen_doesShowSwipeEdu() {
+ tooltipStepPreferenceRule.value = TOOLTIP_STEP_SWIPE
+ assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_SWIPE)
+ runOnMainSync { taskbarEduTooltipController.maybeShowSwipeEdu() }
+ assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_FEATURES)
+ assertThat(taskbarEduTooltipController.isTooltipOpen).isTrue()
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testMaybeShowFeaturesEdu_whenFeatureEduAlreadyShown_doesNotShowFeatureEdu() {
+ tooltipStepPreferenceRule.value = TOOLTIP_STEP_NONE
+ assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_NONE)
+ runOnMainSync { taskbarEduTooltipController.maybeShowFeaturesEdu() }
+ assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_NONE)
+ assertThat(taskbarEduTooltipController.isTooltipOpen).isFalse()
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testMaybeShowFeaturesEdu_whenUserHasNotSeen_doesShowFeatureEdu() {
+ tooltipStepPreferenceRule.value = TOOLTIP_STEP_FEATURES
+ assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_FEATURES)
+ runOnMainSync { taskbarEduTooltipController.maybeShowFeaturesEdu() }
+ assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_NONE)
+ assertThat(taskbarEduTooltipController.isTooltipOpen).isTrue()
+ }
+
+ @Test
+ @TaskbarMode(THREE_BUTTONS)
+ fun testMaybeShowPinningEdu_whenTaskbarIsInThreeButtonMode_doesNotShowPinningEdu() {
+ tooltipStepPreferenceRule.value = TOOLTIP_STEP_PINNING
+ assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_PINNING)
+ runOnMainSync { taskbarEduTooltipController.maybeShowFeaturesEdu() }
+ assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_PINNING)
+ assertThat(taskbarEduTooltipController.isTooltipOpen).isFalse()
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testMaybeShowPinningEdu_whenUserHasNotSeen_doesShowPinningEdu() {
+ // Test standalone pinning edu, where user has seen taskbar edu before, but not pinning edu.
+ tooltipStepPreferenceRule.value = TOOLTIP_STEP_PINNING
+ assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_PINNING)
+ runOnMainSync { taskbarEduTooltipController.maybeShowFeaturesEdu() }
+ assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_NONE)
+ assertThat(taskbarEduTooltipController.isTooltipOpen).isTrue()
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testIsBeforeTooltipFeaturesStep_whenUserHasNotSeenFeatureEdu_shouldReturnTrue() {
+ tooltipStepPreferenceRule.value = TOOLTIP_STEP_SWIPE
+ assertThat(taskbarEduTooltipController.isBeforeTooltipFeaturesStep).isTrue()
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testIsBeforeTooltipFeaturesStep_whenUserHasSeenFeatureEdu_shouldReturnFalse() {
+ tooltipStepPreferenceRule.value = TOOLTIP_STEP_NONE
+ assertThat(taskbarEduTooltipController.isBeforeTooltipFeaturesStep).isFalse()
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testHide_whenTooltipIsOpen_shouldCloseTooltip() {
+ tooltipStepPreferenceRule.value = TOOLTIP_STEP_SWIPE
+ assertThat(taskbarEduTooltipController.tooltipStep).isEqualTo(TOOLTIP_STEP_SWIPE)
+ assertThat(taskbarEduTooltipController.isTooltipOpen).isFalse()
+ runOnMainSync { taskbarEduTooltipController.maybeShowSwipeEdu() }
+ assertThat(taskbarEduTooltipController.isTooltipOpen).isTrue()
+ runOnMainSync { taskbarEduTooltipController.hide() }
+ assertThat(taskbarEduTooltipController.isTooltipOpen).isFalse()
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testMaybeShowSearchEdu_whenTaskbarIsTransient_shouldNotShowSearchEdu() {
+ assertThat(taskbarEduTooltipController.isTooltipOpen).isFalse()
+ runOnMainSync { taskbarEduTooltipController.init(taskbarContext.controllers) }
+ assertThat(taskbarEduTooltipController.isTooltipOpen).isFalse()
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testMaybeShowSearchEdu_whenTaskbarIsPinnedAndUserHasSeenSearchEdu_shouldNotShowSearchEdu() {
+ searchEduPreferenceRule.value = true
+ assertThat(taskbarEduTooltipController.userHasSeenSearchEdu).isTrue()
+ runOnMainSync { taskbarEduTooltipController.hide() }
+ assertThat(taskbarEduTooltipController.isTooltipOpen).isFalse()
+ runOnMainSync { taskbarEduTooltipController.init(taskbarContext.controllers) }
+ assertThat(taskbarEduTooltipController.isTooltipOpen).isFalse()
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt
index 2f0b446..43d924a 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt
@@ -26,6 +26,7 @@
import com.android.launcher3.model.data.AppInfo
import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.notification.NotificationKeyData
+import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync
import com.android.launcher3.taskbar.overlay.TaskbarOverlayController
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController
@@ -56,13 +57,13 @@
@Test
fun testToggle_once_showsAllApps() {
- getInstrumentation().runOnMainSync { allAppsController.toggle() }
+ runOnMainSync { allAppsController.toggle() }
assertThat(allAppsController.isOpen).isTrue()
}
@Test
fun testToggle_twice_closesAllApps() {
- getInstrumentation().runOnMainSync {
+ runOnMainSync {
allAppsController.toggle()
allAppsController.toggle()
}
@@ -71,7 +72,7 @@
@Test
fun testToggle_taskbarRecreated_allAppsReopened() {
- getInstrumentation().runOnMainSync { allAppsController.toggle() }
+ runOnMainSync { allAppsController.toggle() }
taskbarUnitTestRule.recreateTaskbar()
assertThat(allAppsController.isOpen).isTrue()
}
@@ -138,7 +139,7 @@
@Test
fun testUpdateNotificationDots_appInfo_hasDot() {
- getInstrumentation().runOnMainSync {
+ runOnMainSync {
allAppsController.setApps(TEST_APPS, 0, emptyMap())
allAppsController.toggle()
taskbarUnitTestRule.activityContext.popupDataProvider.onNotificationPosted(
@@ -162,7 +163,7 @@
@Test
fun testUpdateNotificationDots_predictedApp_hasDot() {
- getInstrumentation().runOnMainSync {
+ runOnMainSync {
allAppsController.setPredictedApps(TEST_PREDICTED_APPS)
allAppsController.toggle()
taskbarUnitTestRule.activityContext.popupDataProvider.onNotificationPosted(
@@ -185,12 +186,12 @@
@Test
fun testToggleSearch_searchEditTextFocused() {
- getInstrumentation().runOnMainSync { allAppsController.toggleSearch() }
- getInstrumentation().runOnMainSync {
+ runOnMainSync { allAppsController.toggleSearch() }
+ runOnMainSync {
// All Apps is now attached to window. Open animation is posted but not started.
}
- getInstrumentation().runOnMainSync {
+ runOnMainSync {
// Animation has started. Advance to end of animation.
animatorTestRule.advanceTimeBy(overlayController.openDuration.toLong())
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentTaskbarStashControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashControllerTest.kt
similarity index 97%
rename from quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentTaskbarStashControllerTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashControllerTest.kt
index c46c08d..c0a5dfa 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentTaskbarStashControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/PersistentBubbleStashControllerTest.kt
@@ -40,10 +40,10 @@
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
-/** Unit tests for [PersistentTaskbarStashController]. */
+/** Unit tests for [PersistentBubbleStashController]. */
@SmallTest
@RunWith(AndroidJUnit4::class)
-class PersistentTaskbarStashControllerTest {
+class PersistentBubbleStashControllerTest {
companion object {
const val BUBBLE_BAR_HEIGHT = 100f
@@ -52,15 +52,17 @@
}
@get:Rule val animatorTestRule: AnimatorTestRule = AnimatorTestRule(this)
+
@get:Rule val rule: MockitoRule = MockitoJUnit.rule()
private val context = ApplicationProvider.getApplicationContext<Context>()
private lateinit var bubbleBarView: BubbleBarView
@Mock lateinit var bubbleBarViewController: BubbleBarViewController
+
@Mock lateinit var taskbarInsetsController: TaskbarInsetsController
- private lateinit var persistentTaskBarStashController: PersistentTaskbarStashController
+ private lateinit var persistentTaskBarStashController: PersistentBubbleStashController
private lateinit var translationY: AnimatedFloat
private lateinit var scale: AnimatedFloat
private lateinit var alpha: MultiValueAlpha
@@ -68,7 +70,7 @@
@Before
fun setUp() {
persistentTaskBarStashController =
- PersistentTaskbarStashController(DefaultDimensionsProvider())
+ PersistentBubbleStashController(DefaultDimensionsProvider())
setUpBubbleBarView()
setUpBubbleBarController()
persistentTaskBarStashController.init(
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/StashingTestUtils.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/StashingTestUtils.kt
index 5dc9440..00ad3b7 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/StashingTestUtils.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/StashingTestUtils.kt
@@ -20,14 +20,19 @@
override fun runAfterInit(action: () -> Unit) = action.invoke()
}
-class DefaultDimensionsProvider : BubbleStashController.TaskbarHotseatDimensionsProvider {
- override fun getTaskbarBottomSpace(): Int = TASKBAR_BOTTOM_SPACE
+class DefaultDimensionsProvider(
+ private val taskBarBottomSpace: Int = TASKBAR_BOTTOM_SPACE,
+ private val taskBarHeight: Int = TASKBAR_HEIGHT,
+ private val hotseatBottomSpace: Int = HOTSEAT_BOTTOM_SPACE,
+ private val hotseatHeight: Int = HOTSEAT_HEIGHT
+) : BubbleStashController.TaskbarHotseatDimensionsProvider {
+ override fun getTaskbarBottomSpace(): Int = taskBarBottomSpace
- override fun getTaskbarHeight(): Int = TASKBAR_HEIGHT
+ override fun getTaskbarHeight(): Int = taskBarHeight
- override fun getHotseatBottomSpace(): Int = HOTSEAT_BOTTOM_SPACE
+ override fun getHotseatBottomSpace(): Int = hotseatBottomSpace
- override fun getHotseatHeight(): Int = HOTSEAT_HEIGHT
+ override fun getHotseatHeight(): Int = hotseatHeight
companion object {
const val TASKBAR_BOTTOM_SPACE = 0
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt
new file mode 100644
index 0000000..b5809c2
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.bubbles.stashing
+
+import android.animation.AnimatorTestRule
+import android.content.Context
+import android.view.View
+import android.widget.FrameLayout
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.anim.AnimatedFloat
+import com.android.launcher3.taskbar.StashedHandleView
+import com.android.launcher3.taskbar.TaskbarInsetsController
+import com.android.launcher3.taskbar.bubbles.BubbleBarView
+import com.android.launcher3.taskbar.bubbles.BubbleBarViewController
+import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController
+import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.STASHED_BAR_SCALE
+import com.android.launcher3.util.MultiValueAlpha
+import com.android.wm.shell.shared.animation.PhysicsAnimator
+import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.any
+import org.mockito.kotlin.atLeastOnce
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+/** Unit tests for [TransientBubbleStashController]. */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TransientBubbleStashControllerTest {
+
+ companion object {
+ const val TASKBAR_BOTTOM_SPACE = 5
+ const val BUBBLE_BAR_HEIGHT = 100f
+ const val HOTSEAT_TRANSLATION_Y = -45f
+ const val TASK_BAR_TRANSLATION_Y = -TASKBAR_BOTTOM_SPACE
+ const val HANDLE_VIEW_HEIGHT = 4
+ const val BUBBLE_BAR_STASHED_TRANSLATION_Y = 48
+ }
+
+ @get:Rule val animatorTestRule: AnimatorTestRule = AnimatorTestRule(this)
+
+ @get:Rule val rule: MockitoRule = MockitoJUnit.rule()
+
+ @Mock lateinit var bubbleStashedHandleViewController: BubbleStashedHandleViewController
+
+ @Mock lateinit var bubbleBarViewController: BubbleBarViewController
+
+ @Mock lateinit var taskbarInsetsController: TaskbarInsetsController
+
+ private val context = ApplicationProvider.getApplicationContext<Context>()
+ private lateinit var bubbleBarView: BubbleBarView
+ private lateinit var stashedHandleView: StashedHandleView
+ private lateinit var barTranslationY: AnimatedFloat
+ private lateinit var barScale: AnimatedFloat
+ private lateinit var barAlpha: MultiValueAlpha
+ private lateinit var stashedHandleAlpha: MultiValueAlpha
+ private lateinit var stashedHandleScale: AnimatedFloat
+ private lateinit var stashedHandleTranslationY: AnimatedFloat
+ private lateinit var stashPhysicsAnimator: PhysicsAnimator<View>
+
+ private lateinit var mTransientBubbleStashController: TransientBubbleStashController
+
+ @Before
+ fun setUp() {
+ val taskbarHotseatDimensionsProvider =
+ DefaultDimensionsProvider(taskBarBottomSpace = TASKBAR_BOTTOM_SPACE)
+ mTransientBubbleStashController =
+ TransientBubbleStashController(taskbarHotseatDimensionsProvider, context.resources)
+ setUpBubbleBarView()
+ setUpBubbleBarController()
+ setUpStashedHandleView()
+ setUpBubbleStashedHandleViewController()
+ PhysicsAnimatorTestUtils.prepareForTest()
+ mTransientBubbleStashController.init(
+ taskbarInsetsController,
+ bubbleBarViewController,
+ bubbleStashedHandleViewController,
+ ImmediateAction()
+ )
+ }
+
+ @Test
+ fun setBubblesShowingOnHomeUpdatedToTrue_barPositionYUpdated_controllersNotified() {
+ // Given bubble bar is on home and has bubbles
+ whenever(bubbleBarViewController.hasBubbles()).thenReturn(true)
+
+ // When switch out of the home screen
+ getInstrumentation().runOnMainSync {
+ mTransientBubbleStashController.isBubblesShowingOnHome = true
+ }
+
+ // Then BubbleBarView is animating, BubbleBarViewController controller is notified
+ assertThat(barTranslationY.isAnimating).isTrue()
+ verify(bubbleBarViewController).onBubbleBarConfigurationChanged(/* animate= */ true)
+
+ // Wait until animation ends
+ advanceTimeBy(BubbleStashController.BAR_TRANSLATION_DURATION)
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
+ // Then translation Y is correct and the insets controller is notified
+ assertThat(barTranslationY.isAnimating).isFalse()
+ verify(taskbarInsetsController).onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
+ assertThat(bubbleBarView.translationY).isEqualTo(HOTSEAT_TRANSLATION_Y)
+ }
+
+ @Test
+ fun setBubblesShowingOnOverviewUpdatedToTrue_barPositionYUpdated_controllersNotified() {
+ // Given bubble bar is on home and has bubbles
+ whenever(bubbleBarViewController.hasBubbles()).thenReturn(true)
+
+ // When switch out of the home screen
+ getInstrumentation().runOnMainSync {
+ mTransientBubbleStashController.isBubblesShowingOnOverview = true
+ }
+
+ // Then BubbleBarView is animating, BubbleBarViewController controller is notified
+ assertThat(barTranslationY.isAnimating).isTrue()
+ verify(bubbleBarViewController).onBubbleBarConfigurationChanged(/* animate= */ true)
+
+ // Wait until animation ends
+ advanceTimeBy(BubbleStashController.BAR_TRANSLATION_DURATION)
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
+ // Then translation Y is correct and the insets controller is notified
+ assertThat(barTranslationY.isAnimating).isFalse()
+ verify(taskbarInsetsController).onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
+ assertThat(bubbleBarView.translationY).isEqualTo(TASK_BAR_TRANSLATION_Y)
+ }
+
+ @Test
+ fun updateStashedAndExpandedState_stashAndCollapse_bubbleBarHidden_stashedHandleShown() {
+ // Given bubble bar has bubbles and not stashed
+ mTransientBubbleStashController.isStashed = false
+ whenever(bubbleBarViewController.isHiddenForNoBubbles).thenReturn(false)
+
+ // When stash
+ getInstrumentation().runOnMainSync {
+ mTransientBubbleStashController.updateStashedAndExpandedState(
+ stash = true,
+ expand = false
+ )
+ }
+
+ // Wait until animations ends
+ advanceTimeBy(BubbleStashController.BAR_STASH_DURATION)
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
+
+ // Then check BubbleBarController is notified
+ verify(bubbleBarViewController).onStashStateChanging()
+ // Bubble bar is stashed
+ assertThat(mTransientBubbleStashController.isStashed).isTrue()
+ assertThat(bubbleBarView.translationY).isEqualTo(BUBBLE_BAR_STASHED_TRANSLATION_Y)
+ assertThat(bubbleBarView.alpha).isEqualTo(0f)
+ assertThat(bubbleBarView.scaleX).isEqualTo(STASHED_BAR_SCALE)
+ assertThat(bubbleBarView.scaleY).isEqualTo(STASHED_BAR_SCALE)
+ // Handle view is visible
+ assertThat(stashedHandleView.translationY).isEqualTo(0)
+ assertThat(stashedHandleView.alpha).isEqualTo(1)
+ }
+
+ @Test
+ fun isSysuiLockedSwitchedToFalseForOverview_unlockAnimationIsShown() {
+ // Given screen is locked and bubble bar has bubbles
+ getInstrumentation().runOnMainSync {
+ mTransientBubbleStashController.isSysuiLocked = true
+ mTransientBubbleStashController.isBubblesShowingOnOverview = true
+ whenever(bubbleBarViewController.hasBubbles()).thenReturn(true)
+ }
+ advanceTimeBy(BubbleStashController.BAR_TRANSLATION_DURATION)
+
+ // When switch to the overview screen
+ getInstrumentation().runOnMainSync { mTransientBubbleStashController.isSysuiLocked = false }
+
+ // Then
+ assertThat(barTranslationY.isAnimating).isTrue()
+ assertThat(barScale.isAnimating).isTrue()
+ // Wait until animation ends
+ advanceTimeBy(BubbleStashController.BAR_STASH_DURATION)
+
+ // Then bubble bar is fully visible at the correct location
+ assertThat(bubbleBarView.scaleX).isEqualTo(1f)
+ assertThat(bubbleBarView.scaleY).isEqualTo(1f)
+ assertThat(bubbleBarView.translationY)
+ .isEqualTo(PersistentBubbleStashControllerTest.TASK_BAR_TRANSLATION_Y)
+ assertThat(bubbleBarView.alpha).isEqualTo(1f)
+ // Insets controller is notified
+ verify(taskbarInsetsController, atLeastOnce())
+ .onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
+ }
+
+ @Test
+ fun showBubbleBarImmediateToY() {
+ // Given bubble bar is fully transparent and scaled to 0 at 0 y position
+ val targetY = 341f
+ bubbleBarView.alpha = 0f
+ bubbleBarView.scaleX = 0f
+ bubbleBarView.scaleY = 0f
+ bubbleBarView.translationY = 0f
+ stashedHandleView.translationY = targetY
+
+ // When
+ mTransientBubbleStashController.showBubbleBarImmediate(targetY)
+
+ // Then all property values are updated
+ assertThat(bubbleBarView.translationY).isEqualTo(targetY)
+ assertThat(bubbleBarView.alpha).isEqualTo(1f)
+ assertThat(bubbleBarView.scaleX).isEqualTo(1f)
+ assertThat(bubbleBarView.scaleY).isEqualTo(1f)
+ // Handle is transparent
+ assertThat(stashedHandleView.alpha).isEqualTo(0)
+ // Insets controller is notified
+ verify(taskbarInsetsController).onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
+ }
+
+ @Test
+ fun stashBubbleBarImmediate() {
+ // When
+ mTransientBubbleStashController.stashBubbleBarImmediate()
+
+ // Then all property values are updated
+ assertThat(bubbleBarView.translationY).isEqualTo(BUBBLE_BAR_STASHED_TRANSLATION_Y)
+ assertThat(bubbleBarView.alpha).isEqualTo(0)
+ assertThat(bubbleBarView.scaleX).isEqualTo(STASHED_BAR_SCALE)
+ assertThat(bubbleBarView.scaleY).isEqualTo(STASHED_BAR_SCALE)
+ // Handle is visible at correct Y position
+ assertThat(stashedHandleView.alpha).isEqualTo(1)
+ assertThat(stashedHandleView.translationY).isEqualTo(0)
+ // Insets controller is notified
+ verify(taskbarInsetsController).onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
+ }
+
+ @Test
+ fun getTouchableHeight_stashed_stashHeightReturned() {
+ // When
+ mTransientBubbleStashController.isStashed = true
+ val height = mTransientBubbleStashController.getTouchableHeight()
+
+ // Then
+ assertThat(height).isEqualTo(HANDLE_VIEW_HEIGHT)
+ }
+
+ @Test
+ fun getTouchableHeight_unstashed_barHeightReturned() {
+ // When BubbleBar is not stashed
+ mTransientBubbleStashController.isStashed = false
+ val height = mTransientBubbleStashController.getTouchableHeight()
+
+ // Then bubble bar height is returned
+ assertThat(height).isEqualTo(BUBBLE_BAR_HEIGHT.toInt())
+ }
+
+ private fun advanceTimeBy(advanceMs: Long) {
+ // Advance animator for on-device tests
+ getInstrumentation().runOnMainSync { animatorTestRule.advanceTimeBy(advanceMs) }
+ }
+
+ private fun setUpBubbleBarView() {
+ getInstrumentation().runOnMainSync {
+ bubbleBarView = BubbleBarView(context)
+ bubbleBarView.layoutParams = FrameLayout.LayoutParams(0, 0)
+ }
+ }
+
+ private fun setUpStashedHandleView() {
+ getInstrumentation().runOnMainSync {
+ stashedHandleView = StashedHandleView(context)
+ stashedHandleView.layoutParams = FrameLayout.LayoutParams(0, 0)
+ }
+ }
+
+ private fun setUpBubbleBarController() {
+ barTranslationY =
+ AnimatedFloat(Runnable { bubbleBarView.translationY = barTranslationY.value })
+ barScale =
+ AnimatedFloat(
+ Runnable {
+ val scale: Float = barScale.value
+ bubbleBarView.scaleX = scale
+ bubbleBarView.scaleY = scale
+ }
+ )
+ barAlpha = MultiValueAlpha(bubbleBarView, 1 /* num alpha channels */)
+
+ whenever(bubbleBarViewController.hasBubbles()).thenReturn(true)
+ whenever(bubbleBarViewController.bubbleBarTranslationY).thenReturn(barTranslationY)
+ whenever(bubbleBarViewController.bubbleBarScale).thenReturn(barScale)
+ whenever(bubbleBarViewController.bubbleBarAlpha).thenReturn(barAlpha)
+ whenever(bubbleBarViewController.bubbleBarCollapsedHeight).thenReturn(BUBBLE_BAR_HEIGHT)
+ }
+
+ private fun setUpBubbleStashedHandleViewController() {
+ stashedHandleTranslationY =
+ AnimatedFloat(Runnable { stashedHandleView.translationY = barTranslationY.value })
+ stashedHandleScale =
+ AnimatedFloat(
+ Runnable {
+ val scale: Float = barScale.value
+ bubbleBarView.scaleX = scale
+ bubbleBarView.scaleY = scale
+ }
+ )
+ stashedHandleAlpha = MultiValueAlpha(stashedHandleView, 1 /* num alpha channels */)
+ stashPhysicsAnimator = PhysicsAnimator.getInstance(stashedHandleView)
+ whenever(bubbleStashedHandleViewController.stashedHandleAlpha)
+ .thenReturn(stashedHandleAlpha)
+ whenever(bubbleStashedHandleViewController.physicsAnimator).thenReturn(stashPhysicsAnimator)
+ whenever(bubbleStashedHandleViewController.stashedHeight).thenReturn(HANDLE_VIEW_HEIGHT)
+ whenever(bubbleStashedHandleViewController.setTranslationYForSwipe(any())).thenAnswer {
+ invocation ->
+ (invocation.arguments[0] as Float).also { stashedHandleView.translationY = it }
+ }
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt
index f946d4d..4fa821d 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt
@@ -25,6 +25,7 @@
import com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_OVERLAY_PROXY
import com.android.launcher3.AbstractFloatingView.hasOpenView
import com.android.launcher3.taskbar.TaskbarActivityContext
+import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController
import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext
@@ -64,7 +65,7 @@
@Test
fun testRequestWindow_afterHidingExistingWindow_createsNewWindow() {
val context1 = getOnUiThread { overlayController.requestWindow() }
- getInstrumentation().runOnMainSync { overlayController.hideWindow() }
+ runOnMainSync { overlayController.hideWindow() }
val context2 = getOnUiThread { overlayController.requestWindow() }
assertThat(context1).isNotSameInstanceAs(context2)
@@ -73,7 +74,7 @@
@Test
fun testRequestWindow_afterHidingOverlay_createsNewWindow() {
val context1 = getOnUiThread { overlayController.requestWindow() }
- getInstrumentation().runOnMainSync {
+ runOnMainSync {
TestOverlayView.show(context1)
overlayController.hideWindow()
}
@@ -84,16 +85,14 @@
@Test
fun testRequestWindow_addsProxyView() {
- getInstrumentation().runOnMainSync {
- TestOverlayView.show(overlayController.requestWindow())
- }
+ runOnMainSync { TestOverlayView.show(overlayController.requestWindow()) }
assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isTrue()
}
@Test
fun testRequestWindow_closeProxyView_closesOverlay() {
val overlay = getOnUiThread { TestOverlayView.show(overlayController.requestWindow()) }
- getInstrumentation().runOnMainSync {
+ runOnMainSync {
AbstractFloatingView.closeOpenContainer(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)
}
assertThat(overlay.isOpen).isFalse()
@@ -103,13 +102,13 @@
fun testRequestWindow_attachesDragLayer() {
val dragLayer = getOnUiThread { overlayController.requestWindow().dragLayer }
// Allow drag layer to attach before checking.
- getInstrumentation().runOnMainSync { assertThat(dragLayer.isAttachedToWindow).isTrue() }
+ runOnMainSync { assertThat(dragLayer.isAttachedToWindow).isTrue() }
}
@Test
fun testHideWindow_closesOverlay() {
val overlay = getOnUiThread { TestOverlayView.show(overlayController.requestWindow()) }
- getInstrumentation().runOnMainSync { overlayController.hideWindow() }
+ runOnMainSync { overlayController.hideWindow() }
assertThat(overlay.isOpen).isFalse()
}
@@ -118,7 +117,7 @@
val dragLayer = getOnUiThread { overlayController.requestWindow().dragLayer }
// Wait for drag layer to be attached to window before hiding.
- getInstrumentation().runOnMainSync {
+ runOnMainSync {
overlayController.hideWindow()
assertThat(dragLayer.isAttachedToWindow).isFalse()
}
@@ -132,7 +131,7 @@
Pair(TestOverlayView.show(context), TestOverlayView.show(context))
}
- getInstrumentation().runOnMainSync { overlay1.close(false) }
+ runOnMainSync { overlay1.close(false) }
assertThat(overlay2.isOpen).isTrue()
assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isTrue()
}
@@ -145,7 +144,7 @@
Pair(TestOverlayView.show(context), TestOverlayView.show(context))
}
- getInstrumentation().runOnMainSync {
+ runOnMainSync {
overlay1.close(false)
overlay2.close(false)
}
@@ -154,9 +153,7 @@
@Test
fun testRecreateTaskbar_closesWindow() {
- getInstrumentation().runOnMainSync {
- TestOverlayView.show(overlayController.requestWindow())
- }
+ runOnMainSync { TestOverlayView.show(overlayController.requestWindow()) }
taskbarUnitTestRule.recreateTaskbar()
assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isFalse()
}
@@ -166,29 +163,25 @@
val overlay = getOnUiThread { TestOverlayView.show(overlayController.requestWindow()) }
TaskStackChangeListeners.getInstance().listenerImpl.onTaskMovedToFront(RunningTaskInfo())
// Make sure TaskStackChangeListeners' Handler posts the callback before checking state.
- getInstrumentation().runOnMainSync { assertThat(overlay.isOpen).isFalse() }
+ runOnMainSync { assertThat(overlay.isOpen).isFalse() }
}
@Test
fun testTaskStackChanged_allAppsClosed_overlayStaysOpen() {
val overlay = getOnUiThread { TestOverlayView.show(overlayController.requestWindow()) }
- getInstrumentation().runOnMainSync {
- taskbarContext.controllers.sharedState?.allAppsVisible = false
- }
+ runOnMainSync { taskbarContext.controllers.sharedState?.allAppsVisible = false }
TaskStackChangeListeners.getInstance().listenerImpl.onTaskStackChanged()
- getInstrumentation().runOnMainSync { assertThat(overlay.isOpen).isTrue() }
+ runOnMainSync { assertThat(overlay.isOpen).isTrue() }
}
@Test
fun testTaskStackChanged_allAppsOpen_closesOverlay() {
val overlay = getOnUiThread { TestOverlayView.show(overlayController.requestWindow()) }
- getInstrumentation().runOnMainSync {
- taskbarContext.controllers.sharedState?.allAppsVisible = true
- }
+ runOnMainSync { taskbarContext.controllers.sharedState?.allAppsVisible = true }
TaskStackChangeListeners.getInstance().listenerImpl.onTaskStackChanged()
- getInstrumentation().runOnMainSync { assertThat(overlay.isOpen).isFalse() }
+ runOnMainSync { assertThat(overlay.isOpen).isFalse() }
}
@Test
@@ -198,7 +191,7 @@
TestOverlayView.show(context).apply { type = TYPE_OPTIONS_POPUP }
}
- getInstrumentation().runOnMainSync {
+ runOnMainSync {
overlayController.updateLauncherDeviceProfile(
overlayController.launcherDeviceProfile
.toBuilder(context)
@@ -217,7 +210,7 @@
TestOverlayView.show(context).apply { type = TYPE_TASKBAR_ALL_APPS }
}
- getInstrumentation().runOnMainSync {
+ runOnMainSync {
overlayController.updateLauncherDeviceProfile(
overlayController.launcherDeviceProfile
.toBuilder(context)
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 679a208..80b9489 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
@@ -24,6 +24,8 @@
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertFalse;
@@ -45,13 +47,16 @@
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
+import com.android.quickstep.DeviceConfigWrapper;
import com.android.quickstep.GestureState;
import com.android.quickstep.InputConsumer;
import com.android.quickstep.NavHandle;
import com.android.quickstep.RecentsAnimationDeviceState;
import com.android.quickstep.TopTaskTracker;
+import com.android.quickstep.util.TestExtensions;
import com.android.systemui.shared.system.InputMonitorCompat;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -68,7 +73,9 @@
private static final float SQUARED_TOUCH_SLOP = 100;
private final AtomicBoolean mLongPressTriggered = new AtomicBoolean();
+ private final Runnable mLongPressRunnable = () -> mLongPressTriggered.set(true);
private NavHandleLongPressInputConsumer mUnderTest;
+ private SandboxContext mContext;
private float mScreenWidth;
@Mock InputConsumer mDelegate;
@Mock InputMonitorCompat mInputMonitor;
@@ -85,15 +92,15 @@
when(mTopTaskTracker.getCachedTopTask(anyBoolean())).thenReturn(mTaskInfo);
when(mDeviceState.getSquaredTouchSlop()).thenReturn(SQUARED_TOUCH_SLOP);
when(mDelegate.allowInterceptByParent()).thenReturn(true);
+ MAIN_EXECUTOR.getHandler().removeCallbacks(mLongPressRunnable);
mLongPressTriggered.set(false);
- when(mNavHandleLongPressHandler.getLongPressRunnable(any())).thenReturn(
- () -> mLongPressTriggered.set(true));
- SandboxContext context = new SandboxContext(getApplicationContext());
- context.putObject(TopTaskTracker.INSTANCE, mTopTaskTracker);
- mScreenWidth = DisplayController.INSTANCE.get(context).getInfo().currentSize.x;
- mUnderTest = new NavHandleLongPressInputConsumer(context, mDelegate, mInputMonitor,
- mDeviceState, mNavHandle, mGestureState);
- mUnderTest.setNavHandleLongPressHandler(mNavHandleLongPressHandler);
+ when(mNavHandleLongPressHandler.getLongPressRunnable(any())).thenReturn(mLongPressRunnable);
+ initializeObjectUnderTest();
+ }
+
+ @After
+ public void tearDown() {
+ mContext.onDestroy();
}
@Test
@@ -173,6 +180,60 @@
}
@Test
+ public void testLongPressTriggeredWithExtendedTwoStageDuration() {
+ try (AutoCloseable flag = overrideTwoStageFlag(true)) {
+ // Reinitialize to pick up updated flag state.
+ initializeObjectUnderTest();
+
+ mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN));
+ mUnderTest.onMotionEvent(generateMotionEvent(ACTION_MOVE,
+ mScreenWidth / 2f - (TOUCH_SLOP - 1), 0));
+ // We have entered the second stage, so the normal timeout shouldn't trigger.
+ SystemClock.sleep(ViewConfiguration.getLongPressTimeout());
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
+ assertFalse(mLongPressTriggered.get());
+ verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
+ verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any());
+
+ // After an extended time, the long press should trigger.
+ float extendedDurationMultiplier =
+ (DeviceConfigWrapper.get().getTwoStageDurationPercentage() / 100f);
+ SystemClock.sleep((long) (ViewConfiguration.getLongPressTimeout()
+ * (extendedDurationMultiplier - 1))); // -1 because we already waited 1x
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_ACTIVE);
+ assertTrue(mLongPressTriggered.get());
+ verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
+ verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Test
+ public void testLongPressTriggeredWithNormalDurationInFirstStage() {
+ try (AutoCloseable flag = overrideTwoStageFlag(true)) {
+ // Reinitialize to pick up updated flag state.
+ initializeObjectUnderTest();
+
+ mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN));
+ // We have not entered the second stage, so the normal timeout should trigger.
+ SystemClock.sleep(ViewConfiguration.getLongPressTimeout());
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_ACTIVE);
+ assertTrue(mLongPressTriggered.get());
+ verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
+ verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Test
public void testLongPressAbortedByTouchUp() {
mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN));
SystemClock.sleep(ViewConfiguration.getLongPressTimeout() - 10);
@@ -255,6 +316,80 @@
}
@Test
+ public void testLongPressAbortedByTouchSlopPassedVertically_twoStageEnabled() {
+ try (AutoCloseable flag = overrideTwoStageFlag(true)) {
+ // Reinitialize to pick up updated flag state.
+ initializeObjectUnderTest();
+
+ mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN));
+ // Enter the second stage.
+ mUnderTest.onMotionEvent(generateCenteredMotionEventWithYOffset(ACTION_MOVE,
+ -(TOUCH_SLOP - 1)));
+ // Normal duration shouldn't trigger.
+ SystemClock.sleep(ViewConfiguration.getLongPressTimeout());
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
+ assertFalse(mLongPressTriggered.get());
+
+ // Move out of the second stage.
+ mUnderTest.onMotionEvent(generateCenteredMotionEventWithYOffset(ACTION_MOVE,
+ -(TOUCH_SLOP + 1)));
+ // Wait past the extended long press timeout, to be sure it wouldn't have triggered.
+ float extendedDurationMultiplier =
+ (DeviceConfigWrapper.get().getTwoStageDurationPercentage() / 100f);
+ SystemClock.sleep((long) (ViewConfiguration.getLongPressTimeout()
+ * (extendedDurationMultiplier - 1))); // -1 because we already waited 1x
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
+ assertFalse(mLongPressTriggered.get());
+ verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
+ // Touch cancelled.
+ verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Test
+ public void testLongPressAbortedByTouchSlopPassedHorizontally_twoStageEnabled() {
+ try (AutoCloseable flag = overrideTwoStageFlag(true)) {
+ // Reinitialize to pick up updated flag state.
+ initializeObjectUnderTest();
+
+ mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN));
+ // Enter the second stage.
+ mUnderTest.onMotionEvent(generateMotionEvent(ACTION_MOVE,
+ mScreenWidth / 2f - (TOUCH_SLOP - 1), 0));
+ // Normal duration shouldn't trigger.
+ SystemClock.sleep(ViewConfiguration.getLongPressTimeout());
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
+ assertFalse(mLongPressTriggered.get());
+
+ // Move out of the second stage.
+ mUnderTest.onMotionEvent(generateMotionEvent(ACTION_MOVE,
+ mScreenWidth / 2f - (TOUCH_SLOP + 1), 0));
+ // Wait past the extended long press timeout, to be sure it wouldn't have triggered.
+ float extendedDurationMultiplier =
+ (DeviceConfigWrapper.get().getTwoStageDurationPercentage() / 100f);
+ SystemClock.sleep((long) (ViewConfiguration.getLongPressTimeout()
+ * (extendedDurationMultiplier - 1))); // -1 because we already waited 1x
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
+ assertFalse(mLongPressTriggered.get());
+ verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
+ // Touch cancelled.
+ verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Test
public void testTouchOutsideNavHandleIgnored() {
// Touch the far left side of the screen. (y=0 is top of navbar region, picked arbitrarily)
mUnderTest.onMotionEvent(generateMotionEvent(ACTION_DOWN, 0, 0));
@@ -282,6 +417,18 @@
verify(mDelegate, times(2)).onHoverEvent(any());
}
+ private void initializeObjectUnderTest() {
+ if (mContext != null) {
+ mContext.onDestroy();
+ }
+ mContext = new SandboxContext(getApplicationContext());
+ mContext.putObject(TopTaskTracker.INSTANCE, mTopTaskTracker);
+ mScreenWidth = DisplayController.INSTANCE.get(mContext).getInfo().currentSize.x;
+ mUnderTest = new NavHandleLongPressInputConsumer(mContext, mDelegate, mInputMonitor,
+ mDeviceState, mNavHandle, mGestureState);
+ mUnderTest.setNavHandleLongPressHandler(mNavHandleLongPressHandler);
+ }
+
/** Generate a motion event centered horizontally in the screen. */
private MotionEvent generateCenteredMotionEvent(int motionAction) {
return generateCenteredMotionEventWithYOffset(motionAction, 0);
@@ -295,4 +442,11 @@
private static MotionEvent generateMotionEvent(int motionAction, float x, float y) {
return MotionEvent.obtain(0, 0, motionAction, x, y, 0);
}
+
+ private static AutoCloseable overrideTwoStageFlag(boolean value) {
+ return TestExtensions.overrideNavConfigFlag(
+ "ENABLE_LPNH_TWO_STAGES",
+ value,
+ () -> DeviceConfigWrapper.get().getEnableLpnhTwoStages());
+ }
}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 53fed20..fde7014 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -178,6 +178,11 @@
sIsRunningInTestHarness = true;
}
+ /** Disables running test in test harness mode */
+ public static void disableRunningInTestHarnessForTests() {
+ sIsRunningInTestHarness = false;
+ }
+
public static boolean isPropertyEnabled(String propertyName) {
return Log.isLoggable(propertyName, Log.VERBOSE);
}
diff --git a/src/com/android/launcher3/util/OnboardingPrefs.kt b/src/com/android/launcher3/util/OnboardingPrefs.kt
index ac6e97c..771594e 100644
--- a/src/com/android/launcher3/util/OnboardingPrefs.kt
+++ b/src/com/android/launcher3/util/OnboardingPrefs.kt
@@ -16,6 +16,7 @@
package com.android.launcher3.util
import android.content.Context
+import androidx.annotation.VisibleForTesting
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.LauncherPrefs.Companion.backedUpItem
@@ -26,7 +27,7 @@
val sharedPrefKey: String,
val maxCount: Int,
) {
- private val prefItem = backedUpItem(sharedPrefKey, 0)
+ @VisibleForTesting val prefItem = backedUpItem(sharedPrefKey, 0)
/** @return The number of times we have seen the given event. */
fun get(c: Context): Int {