Merge "Readjust the available size for the recommendations in 2-pane sheet" into main
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 3654e5f..abb763a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -376,6 +376,7 @@
(flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0 ||
(flags & FLAG_KEYGUARD_OCCLUDED) != 0;
return (flags & FLAG_DISABLE_BACK) == 0
+ && (!mContext.isGestureNav() || !mContext.isUserSetupComplete())
&& ((flags & FLAG_KEYGUARD_VISIBLE) == 0 || showingOnKeyguard);
}));
// Hide back button in SUW if keyboard is showing (IME draws its own back).
@@ -406,8 +407,8 @@
mPropertyHolders.add(
new StatePropertyHolder(mHomeButtonAlpha.get(
ALPHA_INDEX_KEYGUARD_OR_DISABLE),
- flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 &&
- (flags & FLAG_DISABLE_HOME) == 0));
+ flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0
+ && (flags & FLAG_DISABLE_HOME) == 0 && !mContext.isGestureNav()));
// Recents button
mRecentsButton = addButton(R.drawable.ic_sysbar_recent, BUTTON_RECENTS,
@@ -425,7 +426,7 @@
});
mPropertyHolders.add(new StatePropertyHolder(mRecentsButton,
flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 && (flags & FLAG_DISABLE_RECENTS) == 0
- && !mContext.isNavBarKidsModeActive()));
+ && !mContext.isNavBarKidsModeActive() && !mContext.isGestureNav()));
// A11y button
mA11yButton = addButton(R.drawable.ic_sysbar_accessibility_button, BUTTON_A11Y,
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index e5396ee..b24be54 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -1487,7 +1487,8 @@
((LauncherTaskbarUIController) uiController).addLauncherVisibilityChangedAnimation(
fullAnimation, duration);
}
- mControllers.taskbarStashController.addUnstashToHotseatAnimation(fullAnimation, duration);
+ mControllers.taskbarStashController.addUnstashToHotseatAnimationFromSuw(fullAnimation,
+ duration);
View allAppsButton = mControllers.taskbarViewController.getAllAppsButtonView();
if (allAppsButton != null && !FeatureFlags.ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT.get()) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 182ff7e..4da7762 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -355,7 +355,6 @@
boolean hideTaskbar = isVisible || !mActivity.isUserSetupComplete();
updateStateForFlag(FLAG_IN_SETUP, hideTaskbar);
updateStateForFlag(FLAG_STASHED_IN_APP_SETUP, hideTaskbar);
- updateStateForFlag(FLAG_STASHED_SMALL_SCREEN, mActivity.isPhoneGestureNavMode());
applyState(hideTaskbar ? 0 : getStashDuration());
}
@@ -547,11 +546,12 @@
* sub-animations are properly coordinated. This duration should not
* actually be used since this animation tracks a swipe progress.
*/
- protected void addUnstashToHotseatAnimation(AnimatorSet animation, int placeholderDuration) {
+ protected void addUnstashToHotseatAnimationFromSuw(AnimatorSet animation,
+ int placeholderDuration) {
// Defer any UI updates now to avoid the UI becoming stale when the animation plays.
mControllers.taskbarViewController.setDeferUpdatesForSUW(true);
createAnimToIsStashed(
- /* isStashed= */ false,
+ /* isStashed= */ mActivity.isPhoneMode(),
placeholderDuration,
TRANSITION_UNSTASH_SUW_MANUAL,
/* jankTag= */ "SUW_MANUAL");
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 95dd24b..0b74e15 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -173,6 +173,13 @@
}
}
+ /** Notifies that the stash state is changing. */
+ public void onStashStateChanging() {
+ if (isAnimatingNewBubble()) {
+ mBubbleBarViewAnimator.onStashStateChangingWhileAnimating();
+ }
+ }
+
//
// The below animators are exposed to BubbleStashController so it can manage the stashing
// animation.
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
index f689a05..4b3416c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
@@ -250,6 +250,11 @@
&& !mBubblesShowingOnHome
&& !mBubblesShowingOnOverview;
if (mIsStashed != 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
+ mBarViewController.onStashStateChanging();
mIsStashed = isStashed;
if (mAnimator != null) {
mAnimator.cancel();
@@ -423,4 +428,29 @@
mIsStashed = true;
onIsStashedChanged();
}
+
+ /**
+ * Updates the values of the internal animators after the new bubble animation was interrupted
+ *
+ * @param isStashed whether the current state should be stashed
+ * @param bubbleBarTranslationY the current bubble bar translation. this is only used if the
+ * bubble bar is showing to ensure that the stash animator runs
+ * smoothly.
+ */
+ public void onNewBubbleAnimationInterrupted(boolean isStashed, float bubbleBarTranslationY) {
+ if (isStashed) {
+ mBubbleStashedHandleAlpha.setValue(1);
+ mIconAlphaForStash.setValue(0);
+ mIconScaleForStash.updateValue(STASHED_BAR_SCALE);
+ mIconTranslationYForStash.updateValue(getStashTranslation());
+ } else {
+ mBubbleStashedHandleAlpha.setValue(0);
+ mHandleViewController.setTranslationYForSwipe(0);
+ mIconAlphaForStash.setValue(1);
+ mIconScaleForStash.updateValue(1);
+ mIconTranslationYForStash.updateValue(bubbleBarTranslationY);
+ }
+ mIsStashed = isStashed;
+ onIsStashedChanged();
+ }
}
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 a6d0ff8..66521c1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
@@ -179,7 +179,17 @@
}
}
}
- animator.addEndListener { _, _, _, _, _, _, _ ->
+ 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
+ return@addEndListener
+ }
// the bubble bar is now fully settled in. update taskbar touch region so it's touchable
bubbleStashController.updateTaskbarTouchRegion()
}
@@ -200,6 +210,7 @@
* 3. The third part is the overshoot. The handle is made fully visible.
*/
private fun buildHideAnimation() = Runnable {
+ if (animatingBubble == null) return@Runnable
val offset = bubbleStashController.diffBetweenHandleAndBarCenters
val stashedHandleTranslationY =
bubbleStashController.stashedHandleTranslationForNewBubbleAnimation
@@ -238,9 +249,9 @@
}
}
}
- animator.addEndListener { _, _, _, _, _, _, _ ->
+ animator.addEndListener { _, _, _, canceled, _, _, _ ->
animatingBubble = null
- bubbleStashController.stashBubbleBarImmediate()
+ if (!canceled) bubbleStashController.stashBubbleBarImmediate()
bubbleBarView.onAnimatingBubbleCompleted()
bubbleBarView.relativePivotY = 1f
bubbleStashController.updateTaskbarTouchRegion()
@@ -256,4 +267,18 @@
bubbleBarView.relativePivotY = 1f
animatingBubble = null
}
+
+ /** Notifies the animator that the taskbar area was touched during an animation. */
+ fun onStashStateChangingWhileAnimating() {
+ val hideAnimation = animatingBubble?.hideAnimation ?: return
+ scheduler.cancel(hideAnimation)
+ animatingBubble = null
+ bubbleStashController.stashedHandlePhysicsAnimator.cancel()
+ bubbleBarView.onAnimatingBubbleCompleted()
+ bubbleBarView.relativePivotY = 1f
+ bubbleStashController.onNewBubbleAnimationInterrupted(
+ /* isStashed= */ bubbleBarView.alpha == 0f,
+ bubbleBarView.translationY
+ )
+ }
}
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 f46fdac..7065075 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
@@ -43,6 +43,7 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -52,47 +53,23 @@
class BubbleBarViewAnimatorTest {
private val context = ApplicationProvider.getApplicationContext<Context>()
- private val animatorScheduler = TestBubbleBarViewAnimatorScheduler()
+ private lateinit var animatorScheduler: TestBubbleBarViewAnimatorScheduler
+ private lateinit var overflowView: BubbleView
+ private lateinit var bubbleView: BubbleView
+ private lateinit var bubble: BubbleBarBubble
+ private lateinit var bubbleBarView: BubbleBarView
+ private lateinit var bubbleStashController: BubbleStashController
@Before
fun setUp() {
+ animatorScheduler = TestBubbleBarViewAnimatorScheduler()
PhysicsAnimatorTestUtils.prepareForTest()
}
@Test
fun animateBubbleInForStashed() {
- lateinit var overflowView: BubbleView
- lateinit var bubbleView: BubbleView
- lateinit var bubble: BubbleBarBubble
- val bubbleBarView = BubbleBarView(context)
- InstrumentationRegistry.getInstrumentation().runOnMainSync {
- bubbleBarView.layoutParams = FrameLayout.LayoutParams(0, 0)
- val inflater = LayoutInflater.from(context)
-
- val bitmap = ColorDrawable(Color.WHITE).toBitmap(width = 20, height = 20)
- overflowView =
- inflater.inflate(R.layout.bubblebar_item_view, bubbleBarView, false) as BubbleView
- overflowView.setOverflow(BubbleBarOverflow(overflowView), bitmap)
- bubbleBarView.addView(overflowView)
-
- val bubbleInfo = BubbleInfo("key", 0, null, null, 0, context.packageName, null, false)
- bubbleView =
- inflater.inflate(R.layout.bubblebar_item_view, bubbleBarView, false) as BubbleView
- bubble =
- BubbleBarBubble(bubbleInfo, bubbleView, bitmap, bitmap, Color.WHITE, Path(), "")
- bubbleView.setBubble(bubble)
- bubbleBarView.addView(bubbleView)
- }
- InstrumentationRegistry.getInstrumentation().waitForIdleSync()
-
- val bubbleStashController = mock<BubbleStashController>()
- whenever(bubbleStashController.isStashed).thenReturn(true)
- whenever(bubbleStashController.diffBetweenHandleAndBarCenters)
- .thenReturn(DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS)
- whenever(bubbleStashController.stashedHandleTranslationForNewBubbleAnimation)
- .thenReturn(HANDLE_TRANSLATION)
- whenever(bubbleStashController.bubbleBarTranslationYForTaskbar)
- .thenReturn(BAR_TRANSLATION_Y_FOR_TASKBAR)
+ setUpBubbleBar()
+ setUpBubbleStashController()
val handle = View(context)
val handleAnimator = PhysicsAnimator.getInstance(handle)
@@ -106,7 +83,7 @@
}
// let the animation start and wait for it to complete
- InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {}
PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
assertThat(handle.alpha).isEqualTo(0)
@@ -123,7 +100,7 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!)
// let the animation start and wait for it to complete
- InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {}
PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
assertThat(handle.alpha).isEqualTo(1)
@@ -135,38 +112,8 @@
@Test
fun animateBubbleInForStashed_tapAnimatingBubble() {
- lateinit var overflowView: BubbleView
- lateinit var bubbleView: BubbleView
- lateinit var bubble: BubbleBarBubble
- val bubbleBarView = BubbleBarView(context)
- InstrumentationRegistry.getInstrumentation().runOnMainSync {
- bubbleBarView.layoutParams = FrameLayout.LayoutParams(0, 0)
- val inflater = LayoutInflater.from(context)
-
- val bitmap = ColorDrawable(Color.WHITE).toBitmap(width = 20, height = 20)
- overflowView =
- inflater.inflate(R.layout.bubblebar_item_view, bubbleBarView, false) as BubbleView
- overflowView.setOverflow(BubbleBarOverflow(overflowView), bitmap)
- bubbleBarView.addView(overflowView)
-
- val bubbleInfo = BubbleInfo("key", 0, null, null, 0, context.packageName, null, false)
- bubbleView =
- inflater.inflate(R.layout.bubblebar_item_view, bubbleBarView, false) as BubbleView
- bubble =
- BubbleBarBubble(bubbleInfo, bubbleView, bitmap, bitmap, Color.WHITE, Path(), "")
- bubbleView.setBubble(bubble)
- bubbleBarView.addView(bubbleView)
- }
- InstrumentationRegistry.getInstrumentation().waitForIdleSync()
-
- val bubbleStashController = mock<BubbleStashController>()
- whenever(bubbleStashController.isStashed).thenReturn(true)
- whenever(bubbleStashController.diffBetweenHandleAndBarCenters)
- .thenReturn(DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS)
- whenever(bubbleStashController.stashedHandleTranslationForNewBubbleAnimation)
- .thenReturn(HANDLE_TRANSLATION)
- whenever(bubbleStashController.bubbleBarTranslationYForTaskbar)
- .thenReturn(BAR_TRANSLATION_Y_FOR_TASKBAR)
+ setUpBubbleBar()
+ setUpBubbleStashController()
val handle = View(context)
val handleAnimator = PhysicsAnimator.getInstance(handle)
@@ -180,7 +127,7 @@
}
// let the animation start and wait for it to complete
- InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {}
PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
assertThat(handle.alpha).isEqualTo(0)
@@ -206,6 +153,151 @@
assertThat(bubbleBarView.isAnimatingNewBubble).isFalse()
}
+ @Test
+ fun animateBubbleInForStashed_touchTaskbarArea_whileShowing() {
+ setUpBubbleBar()
+ setUpBubbleStashController()
+
+ val handle = View(context)
+ val handleAnimator = PhysicsAnimator.getInstance(handle)
+ whenever(bubbleStashController.stashedHandlePhysicsAnimator).thenReturn(handleAnimator)
+
+ val animator =
+ BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ animator.animateBubbleInForStashed(bubble)
+ }
+
+ // wait for the animation to start
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {}
+ PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(handleAnimator) { true }
+
+ assertThat(handleAnimator.isRunning()).isTrue()
+ assertThat(bubbleBarView.isAnimatingNewBubble).isTrue()
+ // verify the hide bubble animation is pending
+ assertThat(animatorScheduler.delayedBlock).isNotNull()
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ animator.onStashStateChangingWhileAnimating()
+ }
+
+ // verify that the hide animation was canceled
+ assertThat(animatorScheduler.delayedBlock).isNull()
+ assertThat(bubbleBarView.isAnimatingNewBubble).isFalse()
+ verify(bubbleStashController).onNewBubbleAnimationInterrupted(any(), any())
+
+ // PhysicsAnimatorTestUtils posts the cancellation to the main thread so we need to wait
+ // again
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ assertThat(handleAnimator.isRunning()).isFalse()
+ }
+
+ @Test
+ fun animateBubbleInForStashed_touchTaskbarArea_whileHiding() {
+ setUpBubbleBar()
+ setUpBubbleStashController()
+
+ val handle = View(context)
+ val handleAnimator = PhysicsAnimator.getInstance(handle)
+ whenever(bubbleStashController.stashedHandlePhysicsAnimator).thenReturn(handleAnimator)
+
+ val animator =
+ BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ animator.animateBubbleInForStashed(bubble)
+ }
+
+ // let the animation start and wait for it to complete
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {}
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
+
+ // execute the hide bubble animation
+ assertThat(animatorScheduler.delayedBlock).isNotNull()
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!)
+
+ // wait for the hide animation to start
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {}
+ assertThat(handleAnimator.isRunning()).isTrue()
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ animator.onStashStateChangingWhileAnimating()
+ }
+
+ assertThat(bubbleBarView.isAnimatingNewBubble).isFalse()
+ verify(bubbleStashController).onNewBubbleAnimationInterrupted(any(), any())
+
+ // PhysicsAnimatorTestUtils posts the cancellation to the main thread so we need to wait
+ // again
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ assertThat(handleAnimator.isRunning()).isFalse()
+ }
+
+ @Test
+ fun animateBubbleInForStashed_showAnimationCanceled() {
+ setUpBubbleBar()
+ setUpBubbleStashController()
+
+ val handle = View(context)
+ val handleAnimator = PhysicsAnimator.getInstance(handle)
+ whenever(bubbleStashController.stashedHandlePhysicsAnimator).thenReturn(handleAnimator)
+
+ val animator =
+ BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ animator.animateBubbleInForStashed(bubble)
+ }
+
+ // wait for the animation to start
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {}
+ PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(handleAnimator) { true }
+
+ assertThat(handleAnimator.isRunning()).isTrue()
+ assertThat(bubbleBarView.isAnimatingNewBubble).isTrue()
+ assertThat(animatorScheduler.delayedBlock).isNotNull()
+
+ handleAnimator.cancel()
+ assertThat(handleAnimator.isRunning()).isFalse()
+ assertThat(bubbleBarView.isAnimatingNewBubble).isFalse()
+ assertThat(animatorScheduler.delayedBlock).isNull()
+ }
+
+ private fun setUpBubbleBar() {
+ bubbleBarView = BubbleBarView(context)
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleBarView.layoutParams = FrameLayout.LayoutParams(0, 0)
+ val inflater = LayoutInflater.from(context)
+
+ val bitmap = ColorDrawable(Color.WHITE).toBitmap(width = 20, height = 20)
+ overflowView =
+ inflater.inflate(R.layout.bubblebar_item_view, bubbleBarView, false) as BubbleView
+ overflowView.setOverflow(BubbleBarOverflow(overflowView), bitmap)
+ bubbleBarView.addView(overflowView)
+
+ val bubbleInfo = BubbleInfo("key", 0, null, null, 0, context.packageName, null, false)
+ bubbleView =
+ inflater.inflate(R.layout.bubblebar_item_view, bubbleBarView, false) as BubbleView
+ bubble =
+ BubbleBarBubble(bubbleInfo, bubbleView, bitmap, bitmap, Color.WHITE, Path(), "")
+ bubbleView.setBubble(bubble)
+ bubbleBarView.addView(bubbleView)
+ }
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ }
+
+ private fun setUpBubbleStashController() {
+ bubbleStashController = mock<BubbleStashController>()
+ whenever(bubbleStashController.isStashed).thenReturn(true)
+ whenever(bubbleStashController.diffBetweenHandleAndBarCenters)
+ .thenReturn(DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS)
+ whenever(bubbleStashController.stashedHandleTranslationForNewBubbleAnimation)
+ .thenReturn(HANDLE_TRANSLATION)
+ whenever(bubbleStashController.bubbleBarTranslationYForTaskbar)
+ .thenReturn(BAR_TRANSLATION_Y_FOR_TASKBAR)
+ }
+
private class TestBubbleBarViewAnimatorScheduler : BubbleBarViewAnimator.Scheduler {
var delayedBlock: Runnable? = null
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
index a21d7be..16630967 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
@@ -44,7 +44,6 @@
import com.android.launcher3.R;
import com.android.launcher3.allapps.search.SearchAdapterProvider;
import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.views.ActivityContext;
/**
@@ -273,8 +272,8 @@
privateProfileManager.isPrivateSpaceItem(adapterItem);
if (icon.getAlpha() == 0 || icon.getAlpha() == 1) {
icon.setAlpha(isPrivateSpaceItem
- && privateProfileManager.getAnimationScrolling()
- && privateProfileManager.getAnimate()
+ && (privateProfileManager.getAnimationScrolling() ||
+ privateProfileManager.getAnimate())
&& privateProfileManager.getCurrentState() == STATE_ENABLED
? 0 : 1);
}
diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java
index 551fa94..ae0e80c 100644
--- a/src/com/android/launcher3/allapps/PrivateProfileManager.java
+++ b/src/com/android/launcher3/allapps/PrivateProfileManager.java
@@ -89,7 +89,15 @@
*/
public class PrivateProfileManager extends UserProfileManager {
private static final int EXPAND_COLLAPSE_DURATION = 800;
- private static final int SETTINGS_OPACITY_DURATION = 160;
+ private static final int SETTINGS_OPACITY_DURATION = 400;
+ private static final int TEXT_UNLOCK_OPACITY_DURATION = 300;
+ private static final int TEXT_LOCK_OPACITY_DURATION = 50;
+ private static final int APP_OPACITY_DURATION = 400;
+ private static final int APP_OPACITY_DELAY = 400;
+ private static final int SETTINGS_AND_LOCK_GROUP_TRANSITION_DELAY = 400;
+ private static final int SETTINGS_OPACITY_DELAY = 400;
+ private static final int LOCK_TEXT_OPACITY_DELAY = 500;
+ private static final int NO_DELAY = 0;
private final ActivityAllAppsContainerView<?> mAllApps;
private final Predicate<UserHandle> mPrivateProfileMatcher;
private final int mPsHeaderHeight;
@@ -445,7 +453,6 @@
if (getCurrentState() == STATE_ENABLED
&& isPrivateSpaceSettingsAvailable()) {
settingsButton.setVisibility(VISIBLE);
- settingsButton.setAlpha(1f);
settingsButton.setOnClickListener(
view -> {
logEvents(LAUNCHER_PRIVATE_SPACE_SETTINGS_TAP);
@@ -590,7 +597,9 @@
List<BaseAllAppsAdapter.AdapterItem> allAppsAdapterItems =
mAllApps.getActiveRecyclerView().getApps().getAdapterItems();
ValueAnimator alphaAnim = ObjectAnimator.ofFloat(from, to);
- alphaAnim.setDuration(EXPAND_COLLAPSE_DURATION);
+ alphaAnim.setDuration(APP_OPACITY_DURATION)
+ .setStartDelay(isExpanding ? APP_OPACITY_DELAY : NO_DELAY);
+ alphaAnim.setInterpolator(Interpolators.LINEAR);
alphaAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
@@ -627,20 +636,25 @@
}
ViewGroup settingsAndLockGroup = mPSHeader.findViewById(R.id.settingsAndLockGroup);
ViewGroup lockButton = mPSHeader.findViewById(R.id.ps_lock_unlock_button);
+ TextView lockText = lockButton.findViewById(R.id.lock_text);
if (settingsAndLockGroup.getLayoutTransition() == null) {
// Set a new transition if the current ViewGroup does not already contain one as each
// transition should only happen once when applied.
enableLayoutTransition(settingsAndLockGroup);
}
+ settingsAndLockGroup.getLayoutTransition().setStartDelay(
+ LayoutTransition.CHANGING,
+ expand ? SETTINGS_AND_LOCK_GROUP_TRANSITION_DELAY : NO_DELAY);
PropertySetter headerSetter = new AnimatedPropertySetter();
ImageButton settingsButton = mPSHeader.findViewById(R.id.ps_settings_button);
updateSettingsGearAlpha(settingsButton, expand, headerSetter);
+ updateLockTextAlpha(lockText, expand, headerSetter);
AnimatorSet animatorSet = headerSetter.buildAnim();
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
// Animate the collapsing of the text at the same time while updating lock button.
- lockButton.findViewById(R.id.lock_text).setVisibility(expand ? VISIBLE : GONE);
+ lockText.setVisibility(expand ? VISIBLE : GONE);
setAnimationRunning(true);
}
});
@@ -696,6 +710,8 @@
LayoutTransition settingsAndLockTransition = new LayoutTransition();
settingsAndLockTransition.enableTransitionType(LayoutTransition.CHANGING);
settingsAndLockTransition.setDuration(EXPAND_COLLAPSE_DURATION);
+ settingsAndLockTransition.setInterpolator(LayoutTransition.CHANGING,
+ Interpolators.STANDARD);
settingsAndLockTransition.addTransitionListener(new LayoutTransition.TransitionListener() {
@Override
public void startTransition(LayoutTransition transition, ViewGroup viewGroup,
@@ -716,7 +732,15 @@
PropertySetter setter) {
float toAlpha = expand ? 1 : 0;
setter.setFloat(settingsButton, VIEW_ALPHA, toAlpha, Interpolators.LINEAR)
- .setDuration(SETTINGS_OPACITY_DURATION).setStartDelay(0);
+ .setDuration(SETTINGS_OPACITY_DURATION).setStartDelay(expand ?
+ SETTINGS_OPACITY_DELAY : NO_DELAY);
+ }
+
+ private void updateLockTextAlpha(TextView textView, boolean expand, PropertySetter setter) {
+ float toAlpha = expand ? 1 : 0;
+ setter.setFloat(textView, VIEW_ALPHA, toAlpha, Interpolators.LINEAR)
+ .setDuration(expand ? TEXT_UNLOCK_OPACITY_DURATION : TEXT_LOCK_OPACITY_DURATION)
+ .setStartDelay(expand ? LOCK_TEXT_OPACITY_DELAY : NO_DELAY);
}
void expandPrivateSpace() {
diff --git a/tests/Android.bp b/tests/Android.bp
index 3822ff8..5ec2263 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -23,7 +23,8 @@
srcs: [
"src/**/*.java",
"src/**/*.kt",
- ":launcher3-robo-src",
+ "multivalentTests/src/**/*.java",
+ "multivalentTests/src/**/*.kt",
],
exclude_srcs: [
":launcher-non-quickstep-tests-src",
@@ -37,6 +38,8 @@
srcs: [
"multivalentTests/src/**/*.java",
"multivalentTests/src/**/*.kt",
+ "src_deviceless/**/*.java",
+ "src_deviceless/**/*.kt",
],
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/icons/FastBitmapDrawableTest.java b/tests/multivalentTests/src/com/android/launcher3/icons/FastBitmapDrawableTest.java
index 7e9b68d..58dce0b 100644
--- a/tests/multivalentTests/src/com/android/launcher3/icons/FastBitmapDrawableTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/icons/FastBitmapDrawableTest.java
@@ -37,15 +37,12 @@
import android.view.animation.PathInterpolator;
import androidx.test.annotation.UiThreadTest;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.launcher3.util.rule.RobolectricUiThreadRule;
+import com.android.launcher3.util.LauncherMultivalentJUnit;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -55,14 +52,11 @@
* Tests for FastBitmapDrawable.
*/
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(LauncherMultivalentJUnit.class)
@UiThreadTest
public class FastBitmapDrawableTest {
private static final float EPSILON = 0.00001f;
- @Rule
- public final TestRule roboUiThreadRule = new RobolectricUiThreadRule();
-
@Spy
FastBitmapDrawable mFastBitmapDrawable =
spy(new FastBitmapDrawable(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)));
diff --git a/tests/multivalentTests/src/com/android/launcher3/logging/StartupLatencyLoggerTest.kt b/tests/multivalentTests/src/com/android/launcher3/logging/StartupLatencyLoggerTest.kt
index 12f6c8c..713d4d5 100644
--- a/tests/multivalentTests/src/com/android/launcher3/logging/StartupLatencyLoggerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/logging/StartupLatencyLoggerTest.kt
@@ -2,22 +2,18 @@
import androidx.core.util.isEmpty
import androidx.test.annotation.UiThreadTest
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.launcher3.util.rule.RobolectricUiThreadRule
+import com.android.launcher3.util.LauncherMultivalentJUnit
import com.google.common.truth.Truth.assertThat
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
/** Unit test for [ColdRebootStartupLatencyLogger]. */
@SmallTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(LauncherMultivalentJUnit::class)
class StartupLatencyLoggerTest {
- @get:Rule val roboUiThreadRule = RobolectricUiThreadRule()
-
private val underTest = ColdRebootStartupLatencyLogger()
@Before
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/EmulatedDeviceAndroidJUnit.kt b/tests/multivalentTests/src/com/android/launcher3/util/LauncherMultivalentJUnit.kt
similarity index 68%
rename from tests/multivalentTests/src/com/android/launcher3/util/EmulatedDeviceAndroidJUnit.kt
rename to tests/multivalentTests/src/com/android/launcher3/util/LauncherMultivalentJUnit.kt
index 694f257..e8560af 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/EmulatedDeviceAndroidJUnit.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/LauncherMultivalentJUnit.kt
@@ -24,23 +24,24 @@
import org.junit.runners.Suite
/**
- * A custom runner which emulates multiple devices when running in robolectric framework. Runs
- * normally when running on device
+ * A custom runner for multivalent tests with launcher specific features
+ * 1) Adds support for @UiThread annotations in deviceless tests
+ * 2) Allows emulating multiple devices when running in deviceless mode
*/
-class EmulatedDeviceAndroidJUnit(klass: Class<*>?) : Suite(klass, ImmutableList.of()) {
+class LauncherMultivalentJUnit(klass: Class<*>?) : Suite(klass, ImmutableList.of()) {
val runners: List<Runner> =
- testClass.getAnnotation(Devices::class.java)?.value?.let { devices ->
- if (devices.isEmpty() || !isRunningInRobolectric) {
+ (testClass.getAnnotation(EmulatedDevices::class.java)?.value ?: emptyArray()).let { devices
+ ->
+ if (!isRunningInRobolectric) {
return@let null
}
try {
(testClass.javaClass.classLoader.loadClass(ROBOLECTRIC_RUNNER) as Class<Runner>)
.getConstructor(Class::class.java, String::class.java)
.let { ctor ->
- devices.map { deviceName ->
- ctor.newInstance(testClass.javaClass, deviceName)
- }
+ if (devices.isEmpty()) listOf(ctor.newInstance(testClass.javaClass, null))
+ else devices.map { ctor.newInstance(testClass.javaClass, it) }
}
} catch (e: Exception) {
null
@@ -50,11 +51,13 @@
override fun getChildren() = runners
- @Retention(RUNTIME) @Target(CLASS) annotation class Devices(val value: Array<String>)
+ /**
+ * Annotation to be added to a test so run it on a list of emulated devices for deviceless test
+ */
+ @Retention(RUNTIME) @Target(CLASS) annotation class EmulatedDevices(val value: Array<String>)
companion object {
- private const val ROBOLECTRIC_RUNNER =
- "com.android.launcher3.util.RobolectricEmulatedDeviceRunner"
+ private const val ROBOLECTRIC_RUNNER = "com.android.launcher3.util.RobolectricDeviceRunner"
val isRunningInRobolectric: Boolean
get() =
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/rule/RobolectricUiThreadRule.kt b/tests/multivalentTests/src/com/android/launcher3/util/rule/RobolectricUiThreadRule.kt
deleted file mode 100644
index b65c443..0000000
--- a/tests/multivalentTests/src/com/android/launcher3/util/rule/RobolectricUiThreadRule.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.util.rule
-
-import androidx.test.annotation.UiThreadTest
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.launcher3.util.EmulatedDeviceAndroidJUnit.Companion.isRunningInRobolectric
-import java.util.concurrent.atomic.AtomicReference
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
-
-/**
- * A test rule to add support for @UiThreadTest annotations when running in robolectric until is it
- * natively supported by the robolectric runner:
- * https://github.com/robolectric/robolectric/issues/9026
- */
-class RobolectricUiThreadRule : TestRule {
-
- override fun apply(base: Statement, description: Description): Statement =
- if (!shouldRunOnUiThread(description)) base else UiThreadStatement(base)
-
- private fun shouldRunOnUiThread(description: Description): Boolean {
- if (!isRunningInRobolectric) {
- // If not running in robolectric, let the default runner handle this
- return false
- }
- var clazz = description.testClass
- try {
- if (
- clazz
- .getDeclaredMethod(description.methodName)
- .getAnnotation(UiThreadTest::class.java) != null
- ) {
- return true
- }
- } catch (_: Exception) {
- // Ignore
- }
-
- while (!clazz.isAnnotationPresent(UiThreadTest::class.java)) {
- clazz = clazz.superclass ?: return false
- }
- return true
- }
-
- private class UiThreadStatement(val base: Statement) : Statement() {
-
- override fun evaluate() {
- val exceptionRef = AtomicReference<Throwable>()
- InstrumentationRegistry.getInstrumentation().runOnMainSync {
- try {
- base.evaluate()
- } catch (throwable: Throwable) {
- exceptionRef.set(throwable)
- }
- }
- exceptionRef.get()?.let { throw it }
- }
- }
-}
diff --git a/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
index 70c0333..362596c 100644
--- a/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
+++ b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
@@ -177,6 +177,8 @@
*/
@Test
@PortraitLandscape
+ @ScreenRecordRule.ScreenRecord // b/338869019
+ @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/338869019
public void testAddDeleteShortcutOnHotseat() {
mLauncher.getWorkspace()
.deleteAppIcon(mLauncher.getWorkspace().getHotseatAppIcon(0))
diff --git a/tests/src_deviceless/com/android/launcher3/util/RobolectricDeviceRunner.kt b/tests/src_deviceless/com/android/launcher3/util/RobolectricDeviceRunner.kt
new file mode 100644
index 0000000..dc6d716
--- /dev/null
+++ b/tests/src_deviceless/com/android/launcher3/util/RobolectricDeviceRunner.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util
+
+import androidx.test.annotation.UiThreadTest
+import androidx.test.platform.app.InstrumentationRegistry
+import java.lang.reflect.Method
+import java.util.concurrent.atomic.AtomicReference
+import org.junit.runners.model.FrameworkMethod
+import org.junit.runners.model.Statement
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.internal.bytecode.Sandbox
+import org.robolectric.util.ReflectionHelpers
+import org.robolectric.util.ReflectionHelpers.ClassParameter
+
+/** Runner which emulates the provided display before running the actual test */
+class RobolectricDeviceRunner(testClass: Class<*>?, private val deviceName: String?) :
+ RobolectricTestRunner(testClass) {
+
+ private val nameSuffix = deviceName?.let { "-$it" } ?: ""
+
+ override fun getName() = super.getName() + nameSuffix
+
+ override fun testName(method: FrameworkMethod) = super.testName(method) + nameSuffix
+
+ @Throws(Throwable::class)
+ override fun beforeTest(sandbox: Sandbox, method: FrameworkMethod, bootstrappedMethod: Method) {
+ super.beforeTest(sandbox, method, bootstrappedMethod)
+
+ deviceName ?: return
+
+ val emulator =
+ try {
+ ReflectionHelpers.loadClass(
+ bootstrappedMethod.declaringClass.classLoader,
+ DEVICE_EMULATOR
+ )
+ } catch (e: Exception) {
+ // Ignore, if the device emulator is not present
+ return
+ }
+ ReflectionHelpers.callStaticMethod<Any>(
+ emulator,
+ "updateDevice",
+ ClassParameter.from(String::class.java, deviceName)
+ )
+ }
+
+ override fun getHelperTestRunner(clazz: Class<*>) = MyHelperTestRunner(clazz)
+
+ class MyHelperTestRunner(clazz: Class<*>) : HelperTestRunner(clazz) {
+
+ override fun methodBlock(method: FrameworkMethod): Statement =
+ // this needs to be run in the test classLoader
+ ReflectionHelpers.callStaticMethod(
+ method.declaringClass.classLoader,
+ RobolectricDeviceRunner::class.qualifiedName,
+ "wrapUiThreadMethod",
+ ClassParameter.from(FrameworkMethod::class.java, method),
+ ClassParameter.from(Statement::class.java, super.methodBlock(method))
+ )
+ }
+
+ private class UiThreadStatement(val base: Statement) : Statement() {
+
+ override fun evaluate() {
+ val exceptionRef = AtomicReference<Throwable>()
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ try {
+ base.evaluate()
+ } catch (throwable: Throwable) {
+ exceptionRef.set(throwable)
+ }
+ }
+ exceptionRef.get()?.let { throw it }
+ }
+ }
+
+ companion object {
+
+ private const val DEVICE_EMULATOR = "com.android.launcher3.util.RoboDeviceEmulator"
+
+ @JvmStatic
+ fun wrapUiThreadMethod(method: FrameworkMethod, base: Statement): Statement =
+ if (
+ method.method.isAnnotationPresent(UiThreadTest::class.java) ||
+ method.declaringClass.isAnnotationPresent(UiThreadTest::class.java)
+ ) {
+ UiThreadStatement(base)
+ } else {
+ base
+ }
+ }
+}