From 8fa1dcdc03d186e97e74df606f8de11e2ee52d31 Mon Sep 17 00:00:00 2001 From: Fengjiang Li Date: Thu, 14 Nov 2024 09:04:55 +0800 Subject: [PATCH 1/6] Revert "Revert AllAppsRecyclerViewPoolTest.kt" This reverts commit 6554ab99e34230c3e386f3a91b82151b10a07bc4. Reason for revert: Since ag/28600866 was the real fix of b/354560500, we should restore the AllAppsRecyclerViewPoolTest.kt Fix: 359247985 Test: Presubmit Flag: NONE - released Change-Id: I54c3a4d15ec5a9e96cd03c465d10898ef6ac2078 --- .../recyclerview/AllAppsRecyclerViewPool.kt | 60 +++++++-- .../AllAppsRecyclerViewPoolTest.kt | 120 ++++++++++++++++++ 2 files changed, 168 insertions(+), 12 deletions(-) create mode 100644 tests/multivalentTests/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPoolTest.kt diff --git a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt index 82229f8f21..e4c50f08b1 100644 --- a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt +++ b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt @@ -18,18 +18,23 @@ package com.android.launcher3.recyclerview import android.content.Context import android.util.Log +import android.view.InflateException +import androidx.annotation.VisibleForTesting +import androidx.annotation.VisibleForTesting.Companion.PROTECTED import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.RecycledViewPool import androidx.recyclerview.widget.RecyclerView.ViewHolder import com.android.launcher3.BubbleTextView import com.android.launcher3.BuildConfig import com.android.launcher3.allapps.BaseAllAppsAdapter +import com.android.launcher3.config.FeatureFlags import com.android.launcher3.util.CancellableTask import com.android.launcher3.util.Executors.MAIN_EXECUTOR import com.android.launcher3.util.Executors.VIEW_PREINFLATION_EXECUTOR import com.android.launcher3.util.Themes import com.android.launcher3.views.ActivityContext import com.android.launcher3.views.ActivityContext.ActivityContextDelegate +import java.lang.IllegalStateException const val PREINFLATE_ICONS_ROW_COUNT = 4 const val EXTRA_ICONS_COUNT = 2 @@ -39,10 +44,11 @@ const val EXTRA_ICONS_COUNT = 2 * [RecyclerView]. The view inflation will happen on background thread and inflated [ViewHolder]s * will be added to [RecycledViewPool] on main thread. */ -class AllAppsRecyclerViewPool : RecycledViewPool() { +class AllAppsRecyclerViewPool : RecycledViewPool() where T : Context, T : ActivityContext { var hasWorkProfile = false - private var mCancellableTask: CancellableTask>? = null + @VisibleForTesting(otherwise = PROTECTED) + var mCancellableTask: CancellableTask>? = null companion object { private const val TAG = "AllAppsRecyclerViewPool" @@ -53,7 +59,7 @@ class AllAppsRecyclerViewPool : RecycledViewPool() { /** * Preinflate app icons. If all apps RV cannot be scrolled down, we don't need to preinflate. */ - fun preInflateAllAppsViewHolders(context: T) where T : Context, T : ActivityContext { + fun preInflateAllAppsViewHolders(context: T) { val appsView = context.appsView ?: return val activeRv: RecyclerView = appsView.activeRecyclerView ?: return val preInflateCount = getPreinflateCount(context) @@ -97,36 +103,65 @@ class AllAppsRecyclerViewPool : RecycledViewPool() { override fun getLayoutManager(): RecyclerView.LayoutManager? = null } + preInflateAllAppsViewHolders( + adapter, + BaseAllAppsAdapter.VIEW_TYPE_ICON, + activeRv, + preInflateCount, + ) { + getPreinflateCount(context) + } + } + + @VisibleForTesting(otherwise = PROTECTED) + fun preInflateAllAppsViewHolders( + adapter: RecyclerView.Adapter<*>, + viewType: Int, + activeRv: RecyclerView, + preInflationCount: Int, + preInflationCountProvider: () -> Int, + ) { + if (preInflationCount <= 0) { + return + } mCancellableTask?.cancel() var task: CancellableTask>? = null task = CancellableTask( { val list: ArrayList = ArrayList() - for (i in 0 until preInflateCount) { + for (i in 0 until preInflationCount) { if (task?.canceled == true) { break } // If activeRv's layout manager has been reset to null on main thread, skip // the preinflation as we cannot generate correct LayoutParams if (activeRv.layoutManager == null) { + list.clear() + break + } + try { + list.add(adapter.createViewHolder(activeRv, viewType)) + } catch (e: InflateException) { + list.clear() + // It's still possible for UI thread to set activeRv's layout manager to + // null and we should break the loop and cancel the preinflation. break } - list.add( - adapter.createViewHolder(activeRv, BaseAllAppsAdapter.VIEW_TYPE_ICON) - ) } list }, MAIN_EXECUTOR, { viewHolders -> - for (i in 0 until minOf(viewHolders.size, getPreinflateCount(context))) { + // Run preInflationCountProvider again as the needed VH might have changed + val newPreInflationCount = preInflationCountProvider.invoke() + for (i in 0 until minOf(viewHolders.size, newPreInflationCount)) { putRecycledView(viewHolders[i]) } }, ) mCancellableTask = task - VIEW_PREINFLATION_EXECUTOR.submit(mCancellableTask) + VIEW_PREINFLATION_EXECUTOR.execute(mCancellableTask) } /** @@ -143,10 +178,11 @@ class AllAppsRecyclerViewPool : RecycledViewPool() { * app icons plus [EXTRA_ICONS_COUNT] is the magic minimal count of app icons to preinflate to * suffice fast scrolling. * - * Note that we need to preinfate extra app icons in size of one all apps pages, so that opening - * all apps don't need to inflate app icons. + * Note that if [FeatureFlags.ALL_APPS_GONE_VISIBILITY] is enabled, we need to preinfate extra + * app icons in size of one all apps pages, so that opening all apps don't need to inflate app + * icons. */ - fun getPreinflateCount(context: T): Int where T : Context, T : ActivityContext { + fun getPreinflateCount(context: T): Int { var targetPreinflateCount = PREINFLATE_ICONS_ROW_COUNT * context.deviceProfile.numShownAllAppsColumns + EXTRA_ICONS_COUNT diff --git a/tests/multivalentTests/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPoolTest.kt b/tests/multivalentTests/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPoolTest.kt new file mode 100644 index 0000000000..3afb0b503f --- /dev/null +++ b/tests/multivalentTests/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPoolTest.kt @@ -0,0 +1,120 @@ +/* + * 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.recyclerview + +import android.content.Context +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.LayoutManager +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.launcher3.util.Executors +import com.android.launcher3.views.ActivityContext +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.spy +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidJUnit4::class) +class AllAppsRecyclerViewPoolTest where T : Context, T : ActivityContext { + + private lateinit var underTest: AllAppsRecyclerViewPool + private lateinit var adapter: RecyclerView.Adapter<*> + + @Mock private lateinit var parent: RecyclerView + @Mock private lateinit var itemView: View + @Mock private lateinit var layoutManager: LayoutManager + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + underTest = spy(AllAppsRecyclerViewPool()) + adapter = + object : RecyclerView.Adapter() { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = + object : ViewHolder(itemView) {} + + override fun getItemCount() = 0 + + override fun onBindViewHolder(holder: ViewHolder, position: Int) {} + } + underTest.setMaxRecycledViews(VIEW_TYPE, 20) + `when`(parent.layoutManager).thenReturn(layoutManager) + } + + @Test + fun preinflate_success() { + underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent, 10) { 10 } + + awaitTasksCompleted() + assertThat(underTest.getRecycledViewCount(VIEW_TYPE)).isEqualTo(10) + } + + @Test + fun preinflate_not_triggered() { + underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent, 0) { 0 } + + awaitTasksCompleted() + assertThat(underTest.getRecycledViewCount(VIEW_TYPE)).isEqualTo(0) + } + + @Test + fun preinflate_cancel_before_runOnMainThread() { + underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent, 10) { 10 } + assertThat(underTest.mCancellableTask!!.canceled).isFalse() + + underTest.clear() + + awaitTasksCompleted() + verify(underTest, never()).putRecycledView(any(ViewHolder::class.java)) + assertThat(underTest.mCancellableTask!!.canceled).isTrue() + assertThat(underTest.getRecycledViewCount(VIEW_TYPE)).isEqualTo(0) + } + + @Test + fun preinflate_cancel_after_run() { + underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent, 10) { 10 } + assertThat(underTest.mCancellableTask!!.canceled).isFalse() + awaitTasksCompleted() + + underTest.clear() + + verify(underTest, times(10)).putRecycledView(any(ViewHolder::class.java)) + assertThat(underTest.mCancellableTask!!.canceled).isTrue() + assertThat(underTest.getRecycledViewCount(VIEW_TYPE)).isEqualTo(0) + } + + private fun awaitTasksCompleted() { + Executors.VIEW_PREINFLATION_EXECUTOR.submit { null }.get() + Executors.MAIN_EXECUTOR.submit { null }.get() + } + + companion object { + private const val VIEW_TYPE: Int = 4 + } +} From a9b502b464ea8e1ba186343a90e833321a1b4ee4 Mon Sep 17 00:00:00 2001 From: Jagrut Desai Date: Fri, 11 Oct 2024 13:37:40 -0700 Subject: [PATCH 2/6] DesktopMode Entry/Exit Signals This cl includes - adding callback support for entering/exiting DesktopMode on launcher side. Test: Presubmit Bug: 343882478 Flag: com.android.window.flags.enable_desktop_windowing_mode Change-Id: Ib86746e6cca42dcdbb181d5323715a24c722f004 --- .../statehandlers/DesktopVisibilityController.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java index 57444648ea..fd0243a035 100644 --- a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java +++ b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java @@ -488,6 +488,15 @@ public class DesktopVisibilityController { } }); } + + public void onEnterDesktopModeTransitionStarted(int transitionDuration) { + + } + + @Override + public void onExitDesktopModeTransitionStarted(int transitionDuration) { + + } } /** A listener for Taskbar in Desktop Mode. */ From 596594c68431311370fa6f49e8ece92dbcf2f047 Mon Sep 17 00:00:00 2001 From: Jagrut Desai Date: Mon, 21 Oct 2024 10:40:39 -0700 Subject: [PATCH 3/6] Fix launching app animation from launcher all apps. This cl includes - making pinned taskbar animate slide in/out animation from bottom of screen. - no icon aligment with hotseat when taksbar is pinned The solution - upon state change applied on TaskbarLauncherStateController. offset taskbar background and icons to bottom of screeen and animate with slide in/out effects. Test: manual, presubmit Bug: 314279101 Flag: EXEMPT bugfix Change-Id: Ife336ebd57eb2f60c2bc33ed6a4527c42111808f --- .../launcher3/QuickstepTransitionManager.java | 18 +++- .../taskbar/LauncherTaskbarUIController.java | 8 +- .../TaskbarLauncherStateController.java | 101 +++++++++++++++--- .../taskbar/TaskbarStashController.java | 4 + .../taskbar/TaskbarViewController.java | 4 +- .../QuickstepAtomicAnimationFactory.java | 7 +- .../android/quickstep/AbsSwipeUpHandler.java | 4 +- .../util/StaggeredWorkspaceAnim.java | 8 +- .../taskbar/TaskbarStashControllerTest.kt | 3 +- 9 files changed, 130 insertions(+), 27 deletions(-) diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java index 18337d3991..2c726ffb77 100644 --- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java +++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java @@ -214,6 +214,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener public static final int CONTENT_ALPHA_DURATION = 217; public static final int TRANSIENT_TASKBAR_TRANSITION_DURATION = 417; + public static final int PINNED_TASKBAR_TRANSITION_DURATION = 600; public static final int TASKBAR_TO_APP_DURATION = 600; // TODO(b/236145847): Tune TASKBAR_TO_HOME_DURATION to 383 after conflict with unlock animation // is solved. @@ -1726,8 +1727,21 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener return new AnimatorBackState(rectFSpringAnim, anim); } - public static int getTaskbarToHomeDuration() { - if (enableScalingRevealHomeAnimation()) { + /** Get animation duration for taskbar for going to home. */ + public static int getTaskbarToHomeDuration(boolean isPinnedTaskbar) { + return getTaskbarToHomeDuration(false, isPinnedTaskbar); + } + + /** + * Get animation duration for taskbar for going to home. + * + * @param shouldOverrideToFastAnimation should overwrite scaling reveal home animation duration + */ + public static int getTaskbarToHomeDuration(boolean shouldOverrideToFastAnimation, + boolean isPinnedTaskbar) { + if (isPinnedTaskbar) { + return PINNED_TASKBAR_TRANSITION_DURATION; + } else if (enableScalingRevealHomeAnimation() && !shouldOverrideToFastAnimation) { return TASKBAR_TO_HOME_DURATION_SLOW; } else { return TASKBAR_TO_HOME_DURATION_FAST; diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java index 4a94be71bb..53b8f61d26 100644 --- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java @@ -210,8 +210,12 @@ public class LauncherTaskbarUIController extends TaskbarUIController { } private int getTaskbarAnimationDuration(boolean isVisible) { - if (isVisible && !mLauncher.getPredictiveBackToHomeInProgress()) { - return getTaskbarToHomeDuration(); + // fast animation duration since we will not be playing workspace reveal animation. + boolean shouldOverrideToFastAnimation = + !isHotseatIconOnTopWhenAligned() || mLauncher.getPredictiveBackToHomeInProgress(); + boolean isPinnedTaskbar = DisplayController.isPinnedTaskbar(mLauncher); + if (isVisible || isPinnedTaskbar) { + return getTaskbarToHomeDuration(shouldOverrideToFastAnimation, isPinnedTaskbar); } else { return DisplayController.isTransientTaskbar(mLauncher) ? TRANSIENT_TASKBAR_TRANSITION_DURATION diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java index fa0473910b..29988927bb 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java @@ -17,6 +17,7 @@ package com.android.launcher3.taskbar; import static com.android.app.animation.Interpolators.EMPHASIZED; import static com.android.app.animation.Interpolators.FINAL_FRAME; +import static com.android.app.animation.Interpolators.INSTANT; import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation; import static com.android.launcher3.Hotseat.ALPHA_CHANNEL_TASKBAR_ALIGNMENT; import static com.android.launcher3.Hotseat.ALPHA_CHANNEL_TASKBAR_STASH; @@ -222,7 +223,9 @@ public class TaskbarLauncherStateController { updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, true); if (!mShouldDelayLauncherStateAnim) { if (toState == LauncherState.NORMAL) { - applyState(QuickstepTransitionManager.getTaskbarToHomeDuration()); + applyState(QuickstepTransitionManager.getTaskbarToHomeDuration( + DisplayController.isPinnedTaskbar( + mControllers.taskbarActivityContext))); } else { applyState(); } @@ -459,9 +462,12 @@ public class TaskbarLauncherStateController { private Animator onStateChangeApplied(int changedFlags, long duration, boolean start) { final boolean isInLauncher = isInLauncher(); + final boolean isInOverview = mControllers.uiController.isInOverviewUi(); final boolean isIconAlignedWithHotseat = isIconAlignedWithHotseat(); final float toAlignment = isIconAlignedWithHotseat ? 1 : 0; boolean handleOpenFloatingViews = false; + boolean isPinnedTaskbar = DisplayController.isPinnedTaskbar( + mControllers.taskbarActivityContext); if (DEBUG) { Log.d(TAG, "onStateChangeApplied - isInLauncher: " + isInLauncher + ", mLauncherState: " + mLauncherState @@ -573,10 +579,17 @@ public class TaskbarLauncherStateController { } float backgroundAlpha = isInLauncher && isTaskbarAlignedWithHotseat() ? 0 : 1; + AnimatedFloat taskbarBgOffset = + mControllers.taskbarDragLayerController.getTaskbarBackgroundOffset(); + boolean showTaskbar = !isInLauncher || isInOverview; + float taskbarBgOffsetEnd = showTaskbar ? 0f : 1f; + float taskbarBgOffsetStart = showTaskbar ? 1f : 0f; // Don't animate if background has reached desired value. if (mTaskbarBackgroundAlpha.isAnimating() - || mTaskbarBackgroundAlpha.value != backgroundAlpha) { + || mTaskbarBackgroundAlpha.value != backgroundAlpha + || taskbarBgOffset.isAnimatingToValue(taskbarBgOffsetStart) + || taskbarBgOffset.value != taskbarBgOffsetEnd) { mTaskbarBackgroundAlpha.cancelAnimation(); if (DEBUG) { Log.d(TAG, "onStateChangeApplied - taskbarBackgroundAlpha - " @@ -587,25 +600,35 @@ public class TaskbarLauncherStateController { boolean isInLauncherIconNotAligned = isInLauncher && !isIconAlignedWithHotseat; boolean notInLauncherIconNotAligned = !isInLauncher && !isIconAlignedWithHotseat; boolean isInLauncherIconIsAligned = isInLauncher && isIconAlignedWithHotseat; + // When Hotseat icons are not on top don't change duration or add start delay. + // This will keep the duration in sync for icon alignment and background fade in/out. + // For example, launching app from launcher all apps. + boolean isHotseatIconOnTopWhenAligned = + mControllers.uiController.isHotseatIconOnTopWhenAligned(); float startDelay = 0; // We want to delay the background from fading in so that the icons have time to move // into the bounds of the background before it appears. if (isInLauncherIconNotAligned) { startDelay = duration * TASKBAR_BG_ALPHA_LAUNCHER_NOT_ALIGNED_DELAY_MULT; - } else if (notInLauncherIconNotAligned) { + } else if (notInLauncherIconNotAligned && isHotseatIconOnTopWhenAligned) { startDelay = duration * TASKBAR_BG_ALPHA_NOT_LAUNCHER_NOT_ALIGNED_DELAY_MULT; } float newDuration = duration - startDelay; - if (isInLauncherIconIsAligned) { + if (isInLauncherIconIsAligned && isHotseatIconOnTopWhenAligned) { // Make the background fade out faster so that it is gone by the time the // icons move outside of the bounds of the background. newDuration = duration * TASKBAR_BG_ALPHA_LAUNCHER_IS_ALIGNED_DURATION_MULT; } - Animator taskbarBackgroundAlpha = mTaskbarBackgroundAlpha - .animateToValue(backgroundAlpha) - .setDuration((long) newDuration); - taskbarBackgroundAlpha.setStartDelay((long) startDelay); + Animator taskbarBackgroundAlpha = mTaskbarBackgroundAlpha.animateToValue( + backgroundAlpha); + if (isPinnedTaskbar) { + setupPinnedTaskbarAnimation(animatorSet, showTaskbar, taskbarBgOffset, + taskbarBgOffsetStart, taskbarBgOffsetEnd, duration, taskbarBackgroundAlpha); + } else { + taskbarBackgroundAlpha.setDuration((long) newDuration); + taskbarBackgroundAlpha.setStartDelay((long) startDelay); + } animatorSet.play(taskbarBackgroundAlpha); } @@ -671,15 +694,18 @@ public class TaskbarLauncherStateController { + mIconAlignment.value + " -> " + toAlignment + ": " + duration); } - if (hasAnyFlag(FLAG_TASKBAR_HIDDEN)) { - iconAlignAnim.setInterpolator(FINAL_FRAME); - } else { - animatorSet.play(iconAlignAnim); + if (!isPinnedTaskbar) { + if (hasAnyFlag(FLAG_TASKBAR_HIDDEN)) { + iconAlignAnim.setInterpolator(FINAL_FRAME); + } else { + animatorSet.play(iconAlignAnim); + } } } - Interpolator interpolator = enableScalingRevealHomeAnimation() + Interpolator interpolator = enableScalingRevealHomeAnimation() && !isPinnedTaskbar ? ScalingWorkspaceRevealAnim.SCALE_INTERPOLATOR : EMPHASIZED; + animatorSet.setInterpolator(interpolator); if (start) { @@ -688,6 +714,49 @@ public class TaskbarLauncherStateController { return animatorSet; } + private void setupPinnedTaskbarAnimation(AnimatorSet animatorSet, boolean showTaskbar, + AnimatedFloat taskbarBgOffset, float taskbarBgOffsetStart, float taskbarBgOffsetEnd, + long duration, Animator taskbarBackgroundAlpha) { + float targetAlpha = !showTaskbar ? 1 : 0; + mLauncher.getHotseat().setIconsAlpha(targetAlpha, ALPHA_CHANNEL_TASKBAR_ALIGNMENT); + if (mIsQsbInline) { + mLauncher.getHotseat().setQsbAlpha(targetAlpha, + ALPHA_CHANNEL_TASKBAR_ALIGNMENT); + } + + if ((taskbarBgOffset.value != taskbarBgOffsetEnd && !taskbarBgOffset.isAnimating()) + || taskbarBgOffset.isAnimatingToValue(taskbarBgOffsetStart)) { + taskbarBgOffset.cancelAnimation(); + Animator taskbarIconAlpha = mTaskbarAlphaForHome.animateToValue( + showTaskbar ? 1f : 0f); + AnimatedFloat taskbarIconTranslationYForHome = + mControllers.taskbarViewController.mTaskbarIconTranslationYForHome; + ObjectAnimator taskbarBackgroundOffset = taskbarBgOffset.animateToValue( + taskbarBgOffsetStart, + taskbarBgOffsetEnd); + ObjectAnimator taskbarIconsYTranslation = null; + float taskbarHeight = + mControllers.taskbarActivityContext.getDeviceProfile().taskbarHeight; + if (showTaskbar) { + taskbarIconsYTranslation = taskbarIconTranslationYForHome.animateToValue( + taskbarHeight, 0); + } else { + taskbarIconsYTranslation = taskbarIconTranslationYForHome.animateToValue(0, + taskbarHeight); + } + + taskbarIconAlpha.setDuration(duration); + taskbarIconsYTranslation.setDuration(duration); + taskbarBackgroundOffset.setDuration(duration); + + animatorSet.play(taskbarIconAlpha); + animatorSet.play(taskbarIconsYTranslation); + animatorSet.play(taskbarBackgroundOffset); + } + taskbarBackgroundAlpha.setInterpolator(showTaskbar ? INSTANT : FINAL_FRAME); + taskbarBackgroundAlpha.setDuration(duration); + } + /** * Whether the taskbar is aligned with the hotseat in the current/target launcher state. * @@ -950,8 +1019,9 @@ public class TaskbarLauncherStateController { * * @param finishedToApp {@code true} if the recents animation finished to showing an app and * not workspace or overview - * @param canceled {@code true} if the recents animation was canceled instead of finishing - * to completion + * @param canceled {@code true} if the recents animation was canceled instead of + * finishing + * to completion */ private void endGestureStateOverride(boolean finishedToApp, boolean canceled) { mCallbacks.removeListener(this); @@ -968,6 +1038,7 @@ public class TaskbarLauncherStateController { /** * Updates the visible state immediately to ensure a seamless handoff. + * * @param finishedToApp True iff user is in an app. */ private void updateStateForUserFinishedToApp(boolean finishedToApp) { diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java index c1dd216a9c..67be8dac05 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java @@ -23,6 +23,7 @@ import static com.android.app.animation.Interpolators.INSTANT; import static com.android.app.animation.Interpolators.LINEAR; import static com.android.internal.jank.InteractionJankMonitor.Configuration; import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation; +import static com.android.launcher3.QuickstepTransitionManager.PINNED_TASKBAR_TRANSITION_DURATION; import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_HIDE; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_SHOW; @@ -398,6 +399,9 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba * Returns how long the stash/unstash animation should play. */ public long getStashDuration() { + if (DisplayController.isPinnedTaskbar(mActivity)) { + return PINNED_TASKBAR_TRANSITION_DURATION; + } return DisplayController.isTransientTaskbar(mActivity) ? TRANSIENT_TASKBAR_STASH_DURATION : TASKBAR_STASH_DURATION; diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java index cebabffa75..bb4f07a72f 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java @@ -120,7 +120,7 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar private final TaskbarView mTaskbarView; private final MultiValueAlpha mTaskbarIconAlpha; private final AnimatedFloat mTaskbarIconScaleForStash = new AnimatedFloat(this::updateScale); - private final AnimatedFloat mTaskbarIconTranslationYForHome = new AnimatedFloat( + public final AnimatedFloat mTaskbarIconTranslationYForHome = new AnimatedFloat( this::updateTranslationY); private final AnimatedFloat mTaskbarIconTranslationYForStash = new AnimatedFloat( this::updateTranslationY); @@ -796,6 +796,8 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar */ private AnimatorPlaybackController createIconAlignmentController(DeviceProfile launcherDp) { PendingAnimation setter = new PendingAnimation(100); + // icon alignment not needed for pinned taskbar. + if (DisplayController.isPinnedTaskbar(mActivity)) return setter.createPlaybackController(); mOnControllerPreCreateCallback.run(); DeviceProfile taskbarDp = mActivity.getDeviceProfile(); Rect hotseatPadding = launcherDp.getHotseatLayoutPadding(mActivity); diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java index 3a39cf28b1..8ad00bfd88 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java +++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java @@ -95,6 +95,7 @@ public class QuickstepAtomicAnimationFactory extends public void prepareForAtomicAnimation(LauncherState fromState, LauncherState toState, StateAnimationConfig config) { RecentsView overview = mContainer.getOverviewPanel(); + boolean isPinnedTaskbar = DisplayController.isPinnedTaskbar(mContainer); if ((fromState == OVERVIEW || fromState == OVERVIEW_SPLIT_SELECT) && toState == NORMAL) { overview.switchToScreenshot(() -> overview.finishRecentsAnimation(true /* toRecents */, null)); @@ -109,7 +110,8 @@ public class QuickstepAtomicAnimationFactory extends // We sync the scrim fade with the taskbar animation duration to avoid any flickers for // taskbar icons disappearing before hotseat icons show up. float scrimUpperBoundFromSplit = - QuickstepTransitionManager.getTaskbarToHomeDuration() / (float) config.duration; + QuickstepTransitionManager.getTaskbarToHomeDuration(isPinnedTaskbar) + / (float) config.duration; scrimUpperBoundFromSplit = Math.min(scrimUpperBoundFromSplit, 1f); config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, clampToProgress(LINEAR, 0, 0.25f)); config.setInterpolator(ANIM_SCRIM_FADE, @@ -139,7 +141,8 @@ public class QuickstepAtomicAnimationFactory extends // Sync scroll so that it ends before or at the same time as the taskbar animation. if (mContainer.getDeviceProfile().isTaskbarPresent) { config.duration = Math.min( - config.duration, QuickstepTransitionManager.getTaskbarToHomeDuration()); + config.duration, + QuickstepTransitionManager.getTaskbarToHomeDuration(isPinnedTaskbar)); } overview.snapToPage(DEFAULT_PAGE, Math.toIntExact(config.duration)); } else { diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java index 97d71799fd..ff31eac913 100644 --- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java +++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java @@ -100,6 +100,7 @@ import com.android.internal.jank.Cuj; import com.android.internal.util.LatencyTracker; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.DeviceProfile; +import com.android.launcher3.QuickstepTransitionManager; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimationSuccessListener; @@ -1373,8 +1374,9 @@ public abstract class AbsSwipeUpHandler< mInputConsumerProxy.enable(); } if (endTarget == HOME) { + boolean isPinnedTaskbar = DisplayController.isPinnedTaskbar(mContext); duration = mContainer != null && mContainer.getDeviceProfile().isTaskbarPresent - ? StaggeredWorkspaceAnim.DURATION_TASKBAR_MS + ? QuickstepTransitionManager.getTaskbarToHomeDuration(isPinnedTaskbar) : StaggeredWorkspaceAnim.DURATION_MS; ContextualEduStatsManager.INSTANCE.get(mContext).updateEduStats( mGestureState.isTrackpadGesture(), GestureType.HOME); diff --git a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java index 997a842dc2..12ca25702e 100644 --- a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java +++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java @@ -49,6 +49,7 @@ import com.android.launcher3.celllayout.CellLayoutLayoutParams; import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.states.StateAnimationConfig; import com.android.launcher3.uioverrides.QuickstepLauncher; +import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.DynamicResource; import com.android.quickstep.views.RecentsView; import com.android.systemui.plugins.ResourceProvider; @@ -63,8 +64,7 @@ public class StaggeredWorkspaceAnim { private static final int APP_CLOSE_ROW_START_DELAY_MS = 10; // Should be used for animations running alongside this StaggeredWorkspaceAnim. public static final int DURATION_MS = 250; - public static final int DURATION_TASKBAR_MS = - QuickstepTransitionManager.getTaskbarToHomeDuration(); + private final int mTaskbarDurationInMs; private static final float MAX_VELOCITY_PX_PER_S = 22f; @@ -81,6 +81,8 @@ public class StaggeredWorkspaceAnim { public StaggeredWorkspaceAnim(QuickstepLauncher launcher, float velocity, boolean animateOverviewScrim, @Nullable View ignoredView, boolean staggerWorkspace) { + mTaskbarDurationInMs = QuickstepTransitionManager.getTaskbarToHomeDuration( + DisplayController.isPinnedTaskbar(launcher)); prepareToAnimate(launcher, animateOverviewScrim); mIgnoredView = ignoredView; @@ -93,7 +95,7 @@ public class StaggeredWorkspaceAnim { .getDimensionPixelSize(R.dimen.swipe_up_max_workspace_trans_y); DeviceProfile grid = launcher.getDeviceProfile(); - long duration = grid.isTaskbarPresent ? DURATION_TASKBAR_MS : DURATION_MS; + long duration = grid.isTaskbarPresent ? mTaskbarDurationInMs : DURATION_MS; if (staggerWorkspace) { Workspace workspace = launcher.getWorkspace(); Hotseat hotseat = launcher.getHotseat(); diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt index 71f4ef4ee6..5e438bd8e1 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt @@ -21,6 +21,7 @@ import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING +import com.android.launcher3.QuickstepTransitionManager.PINNED_TASKBAR_TRANSITION_DURATION import com.android.launcher3.R import com.android.launcher3.taskbar.StashedHandleViewController.ALPHA_INDEX_STASHED import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_EDU_OPEN @@ -158,7 +159,7 @@ class TaskbarStashControllerTest { @Test @TaskbarMode(PINNED) fun testGetStashDuration_pinnedMode() { - assertThat(stashController.stashDuration).isEqualTo(TASKBAR_STASH_DURATION) + assertThat(stashController.stashDuration).isEqualTo(PINNED_TASKBAR_TRANSITION_DURATION) } @Test From 5f8e2d7c13466f1c3d8d9bda91f328fb45016742 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Mon, 11 Nov 2024 20:04:58 -0800 Subject: [PATCH 4/6] Migrating widgets tests off TAPL Bug: 377772352 Test: Presubmit Flag: EXEMPT test refactor Change-Id: I6e9ffb3dee913617ca9ad897504e8457cfbdccd6 --- tests/Android.bp | 1 - .../launcher3/ui/AbstractLauncherUiTest.java | 36 ------ ...dgetTest.java => AddConfigWidgetTest.java} | 117 +++++++++-------- ...indWidgetTest.java => BindWidgetTest.java} | 95 +++++++------- ...nItemTest.java => RequestPinItemTest.java} | 118 ++++++++---------- ...tPickerTest.java => WidgetPickerTest.java} | 37 +++--- .../ui/workspace/ThemeIconsTest.java | 39 +----- .../util/BaseLauncherActivityTest.kt | 42 ++++++- .../util/BlockingBroadcastReceiver.kt | 54 ++++++++ 9 files changed, 284 insertions(+), 255 deletions(-) rename tests/src/com/android/launcher3/ui/widget/{TaplAddConfigWidgetTest.java => AddConfigWidgetTest.java} (54%) rename tests/src/com/android/launcher3/ui/widget/{TaplBindWidgetTest.java => BindWidgetTest.java} (77%) rename tests/src/com/android/launcher3/ui/widget/{TaplRequestPinItemTest.java => RequestPinItemTest.java} (61%) rename tests/src/com/android/launcher3/ui/widget/{TaplWidgetPickerTest.java => WidgetPickerTest.java} (68%) create mode 100644 tests/src/com/android/launcher3/util/BlockingBroadcastReceiver.kt diff --git a/tests/Android.bp b/tests/Android.bp index b1d4ef6703..e4fecc5ec8 100644 --- a/tests/Android.bp +++ b/tests/Android.bp @@ -63,7 +63,6 @@ filegroup { "src/com/android/launcher3/dragging/TaplDragTest.java", "src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java", "src/com/android/launcher3/ui/TaplTestsLauncher3Test.java", - "src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java", "src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java", ], } diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index ed5762d08c..8e4db5c6cf 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -21,11 +21,8 @@ import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static org.junit.Assert.assertTrue; -import android.content.BroadcastReceiver; import android.content.ComponentName; -import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; import android.os.Process; import android.system.OsConstants; import android.util.Log; @@ -53,7 +50,6 @@ import org.junit.rules.TestRule; import java.util.Objects; import java.util.concurrent.Callable; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Consumer; @@ -216,38 +212,6 @@ public abstract class AbstractLauncherUiTest }, mLauncher, timeout); } - /** - * Broadcast receiver which blocks until the result is received. - */ - public class BlockingBroadcastReceiver extends BroadcastReceiver { - - private final CountDownLatch latch = new CountDownLatch(1); - private Intent mIntent; - - public BlockingBroadcastReceiver(String action) { - mTargetContext.registerReceiver(this, new IntentFilter(action), - Context.RECEIVER_EXPORTED/*UNAUDITED*/); - } - - @Override - public void onReceive(Context context, Intent intent) { - mIntent = intent; - latch.countDown(); - } - - public Intent blockingGetIntent() throws InterruptedException { - assertTrue("Timed Out", latch.await(DEFAULT_BROADCAST_TIMEOUT_SECS, TimeUnit.SECONDS)); - mTargetContext.unregisterReceiver(this); - return mIntent; - } - - public Intent blockingGetExtraIntent() throws InterruptedException { - Intent intent = blockingGetIntent(); - return intent == null ? null : (Intent) intent.getParcelableExtra( - Intent.EXTRA_INTENT); - } - } - public static void startAppFast(String packageName) { startIntent( getInstrumentation().getContext().getPackageManager().getLaunchIntentForPackage( diff --git a/tests/src/com/android/launcher3/ui/widget/TaplAddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java similarity index 54% rename from tests/src/com/android/launcher3/ui/widget/TaplAddConfigWidgetTest.java rename to tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java index 7845222b6e..bb645d714f 100644 --- a/tests/src/com/android/launcher3/ui/widget/TaplAddConfigWidgetTest.java +++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java @@ -1,17 +1,17 @@ /* * Copyright (C) 2017 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 + * 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 + * 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. + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package com.android.launcher3.ui.widget; @@ -24,23 +24,34 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import android.appwidget.AppWidgetManager; +import android.content.Context; import android.content.Intent; +import android.os.Process; import android.view.View; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.LargeTest; -import androidx.test.runner.AndroidJUnit4; +import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.Launcher; import com.android.launcher3.celllayout.FavoriteItemsTransaction; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.LauncherAppWidgetInfo; import com.android.launcher3.testcomponent.WidgetConfigActivity; -import com.android.launcher3.ui.AbstractLauncherUiTest; import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape; import com.android.launcher3.ui.TestViewHelpers; +import com.android.launcher3.util.BaseLauncherActivityTest; +import com.android.launcher3.util.BlockingBroadcastReceiver; +import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.Wait; import com.android.launcher3.util.rule.ShellCommandRule; +import com.android.launcher3.views.OptionsPopupView; import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; +import com.android.launcher3.widget.PendingAddWidgetInfo; +import com.android.launcher3.widget.WidgetCell; +import com.android.launcher3.widget.picker.WidgetsFullSheet; +import com.android.launcher3.widget.picker.WidgetsListAdapter; +import com.android.launcher3.widget.picker.WidgetsRecyclerView; import org.junit.Before; import org.junit.Rule; @@ -52,7 +63,7 @@ import org.junit.runner.RunWith; */ @LargeTest @RunWith(AndroidJUnit4.class) -public class TaplAddConfigWidgetTest extends AbstractLauncherUiTest { +public class AddConfigWidgetTest extends BaseLauncherActivityTest { @Rule public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind(); @@ -62,12 +73,10 @@ public class TaplAddConfigWidgetTest extends AbstractLauncherUiTest { private int mWidgetId; - @Override @Before public void setUp() throws Exception { - super.setUp(); mWidgetInfo = TestViewHelpers.findWidgetProvider(true /* hasConfigureScreen */); - mAppWidgetManager = AppWidgetManager.getInstance(mTargetContext); + mAppWidgetManager = AppWidgetManager.getInstance(targetContext()); } @Test @@ -82,80 +91,86 @@ public class TaplAddConfigWidgetTest extends AbstractLauncherUiTest { runTest(false); } - /** * @param acceptConfig accept the config activity */ private void runTest(boolean acceptConfig) throws Throwable { - commitTransactionAndLoadHome(new FavoriteItemsTransaction(mTargetContext)); + new FavoriteItemsTransaction(targetContext()).commit(); + loadLauncherSync(); - // Drag widget to homescreen + // Add widget to homescreen WidgetConfigStartupMonitor monitor = new WidgetConfigStartupMonitor(); - mLauncher.getWorkspace() - .openAllWidgets() - .getWidget(mWidgetInfo.getLabel()) - .dragToWorkspace(true, false); + executeOnLauncher(OptionsPopupView::openWidgets); + uiDevice.waitForIdle(); + + // Select the widget header + Context testContext = getInstrumentation().getContext(); + String packageName = testContext.getPackageName(); + executeOnLauncher(l -> { + WidgetsRecyclerView wrv = WidgetsFullSheet.getWidgetsView(l); + WidgetsListAdapter adapter = (WidgetsListAdapter) wrv.getAdapter(); + int pos = adapter.getItems().indexOf( + adapter.getItems().stream() + .filter(entry -> packageName.equals(entry.mPkgItem.packageName)) + .findFirst() + .get()); + wrv.getLayoutManager().scrollToPosition(pos); + adapter.onHeaderClicked(true, new PackageUserKey(packageName, Process.myUserHandle())); + }); + uiDevice.waitForIdle(); + + View widgetView = getOnceNotNull("Widget not found", l -> searchView(l.getDragLayer(), v -> + v instanceof WidgetCell + && v.getTag() instanceof PendingAddWidgetInfo pawi + && mWidgetInfo.provider.equals(pawi.componentName))); + addToWorkspace(widgetView); + // Widget id for which the config activity was opened mWidgetId = monitor.getWidgetId(); // Verify that the widget id is valid and bound assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId)); + setResult(acceptConfig); - setResultAndWaitForAnimation(acceptConfig); if (acceptConfig) { - Wait.atMost("", new WidgetSearchCondition(), mLauncher); + getOnceNotNull("Widget was not added", l -> { + // Close the resize frame before searching for widget + AbstractFloatingView.closeAllOpenViews(l); + return l.getWorkspace().getFirstMatch(new WidgetSearchCondition()); + }); assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId)); } else { // Verify that the widget id is deleted. - Wait.atMost("", () -> mAppWidgetManager.getAppWidgetInfo(mWidgetId) == null, - mLauncher); + Wait.atMost("", () -> mAppWidgetManager.getAppWidgetInfo(mWidgetId) == null); } } - private static void setResult(boolean success) { + private void setResult(boolean success) { getInstrumentation().getTargetContext().sendBroadcast( WidgetConfigActivity.getCommandIntent(WidgetConfigActivity.class, success ? "clickOK" : "clickCancel")); - } - - private void setResultAndWaitForAnimation(boolean success) { - if (mLauncher.isLauncher3()) { - setResult(success); - } else { - mLauncher.executeAndWaitForWallpaperAnimation( - () -> setResult(success), - "setting widget coinfig result"); - } + uiDevice.waitForIdle(); } /** * Condition for searching widget id */ - private class WidgetSearchCondition implements Wait.Condition, ItemOperator { - - @Override - public boolean isTrue() throws Throwable { - return mMainThreadExecutor.submit(() -> { - Launcher l = Launcher.ACTIVITY_TRACKER.getCreatedContext(); - return l != null && l.getWorkspace().getFirstMatch(this) != null; - }).get(); - } + private class WidgetSearchCondition implements ItemOperator { @Override public boolean evaluate(ItemInfo info, View view) { - return info instanceof LauncherAppWidgetInfo - && ((LauncherAppWidgetInfo) info).providerName.getClassName().equals( - mWidgetInfo.provider.getClassName()) - && ((LauncherAppWidgetInfo) info).appWidgetId == mWidgetId; + return info instanceof LauncherAppWidgetInfo lawi + && lawi.providerName.equals(mWidgetInfo.provider) + && lawi.appWidgetId == mWidgetId; } } /** * Broadcast receiver for receiving widget config activity status. */ - private class WidgetConfigStartupMonitor extends BlockingBroadcastReceiver { + private static class WidgetConfigStartupMonitor extends BlockingBroadcastReceiver { - public WidgetConfigStartupMonitor() { + WidgetConfigStartupMonitor() { super(WidgetConfigActivity.class.getName()); } diff --git a/tests/src/com/android/launcher3/ui/widget/TaplBindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java similarity index 77% rename from tests/src/com/android/launcher3/ui/widget/TaplBindWidgetTest.java rename to tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java index 4cdbd96641..8846d6593a 100644 --- a/tests/src/com/android/launcher3/ui/widget/TaplBindWidgetTest.java +++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java @@ -1,17 +1,17 @@ /* * Copyright (C) 2017 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 + * 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 + * 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. + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package com.android.launcher3.ui.widget; @@ -22,12 +22,12 @@ import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_PROVID import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_RESTORE_STARTED; import static com.android.launcher3.provider.LauncherDbUtils.itemIdMatch; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; +import static com.android.launcher3.util.TestUtil.getOnUiThread; +import static com.android.launcher3.util.Wait.atMost; import static com.android.launcher3.util.WidgetUtils.createWidgetInfo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; import android.appwidget.AppWidgetManager; import android.content.ComponentName; @@ -36,6 +36,7 @@ import android.content.pm.PackageInstaller.SessionParams; import android.content.pm.PackageManager; import android.database.Cursor; import android.os.Bundle; +import android.text.TextUtils; import android.widget.RemoteViews; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -49,13 +50,12 @@ import com.android.launcher3.R; import com.android.launcher3.celllayout.FavoriteItemsTransaction; import com.android.launcher3.model.data.LauncherAppWidgetInfo; import com.android.launcher3.pm.InstallSessionHelper; -import com.android.launcher3.tapl.Widget; -import com.android.launcher3.tapl.Workspace; -import com.android.launcher3.ui.AbstractLauncherUiTest; import com.android.launcher3.ui.TestViewHelpers; -import com.android.launcher3.util.TestUtil; +import com.android.launcher3.util.BaseLauncherActivityTest; import com.android.launcher3.util.rule.ShellCommandRule; +import com.android.launcher3.widget.LauncherAppWidgetHostView; import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; +import com.android.launcher3.widget.PendingAppWidgetHostView; import com.android.launcher3.widget.WidgetManagerHelper; import org.junit.After; @@ -67,6 +67,7 @@ import org.junit.runner.RunWith; import java.util.HashSet; import java.util.Set; import java.util.function.Consumer; +import java.util.function.Function; /** * Tests for bind widget flow. @@ -75,7 +76,7 @@ import java.util.function.Consumer; */ @LargeTest @RunWith(AndroidJUnit4.class) -public class TaplBindWidgetTest extends AbstractLauncherUiTest { +public class BindWidgetTest extends BaseLauncherActivityTest { @Rule public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind(); @@ -87,11 +88,9 @@ public class TaplBindWidgetTest extends AbstractLauncherUiTest { private LauncherModel mModel; - @Override @Before public void setUp() throws Exception { - super.setUp(); - mModel = LauncherAppState.getInstance(mTargetContext).getModel(); + mModel = LauncherAppState.getInstance(targetContext()).getModel(); } @After @@ -101,7 +100,7 @@ public class TaplBindWidgetTest extends AbstractLauncherUiTest { } if (mSessionId > -1) { - mTargetContext.getPackageManager().getPackageInstaller().abandonSession(mSessionId); + targetContext().getPackageManager().getPackageInstaller().abandonSession(mSessionId); } } @@ -122,13 +121,12 @@ public class TaplBindWidgetTest extends AbstractLauncherUiTest { LauncherAppWidgetProviderInfo info = addWidgetToScreen(false, false, item -> item.appWidgetId = -33); - final Workspace workspace = mLauncher.getWorkspace(); // Item deleted from db mCursor = queryItem(); assertEquals(0, mCursor.getCount()); // The view does not exist - assertTrue("Widget exists", workspace.tryGetWidget(info.label, 0) == null); + verifyItemEventuallyNull("Widget exists", widgetProvider(info)); } @Test @@ -154,18 +152,19 @@ public class TaplBindWidgetTest extends AbstractLauncherUiTest { // Widget has a valid Id now. assertEquals(0, mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED)) & FLAG_ID_NOT_VALID); - assertNotNull(AppWidgetManager.getInstance(mTargetContext) + assertNotNull(AppWidgetManager.getInstance(targetContext()) .getAppWidgetInfo(mCursor.getInt(mCursor.getColumnIndex( LauncherSettings.Favorites.APPWIDGET_ID)))); // send OPTION_APPWIDGET_RESTORE_COMPLETED int appWidgetId = mCursor.getInt( mCursor.getColumnIndex(LauncherSettings.Favorites.APPWIDGET_ID)); - AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mTargetContext); + AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(targetContext()); Bundle b = new Bundle(); b.putBoolean(WidgetManagerHelper.WIDGET_OPTION_RESTORE_COMPLETED, true); - RemoteViews remoteViews = new RemoteViews(mTargetPackage, R.layout.appwidget_not_ready); + RemoteViews remoteViews = new RemoteViews( + targetContext().getPackageName(), R.layout.appwidget_not_ready); appWidgetManager.updateAppWidgetOptions(appWidgetId, b); appWidgetManager.updateAppWidget(appWidgetId, remoteViews); @@ -175,15 +174,14 @@ public class TaplBindWidgetTest extends AbstractLauncherUiTest { WidgetManagerHelper.WIDGET_OPTION_RESTORE_COMPLETED)); executeOnLauncher(l -> l.getAppWidgetHolder().startListening()); verifyWidgetPresent(info); - assertNull(mLauncher.getWorkspace().tryGetPendingWidget(100)); + verifyItemEventuallyNull("Pending widget exists", pendingWidgetProvider()); } @Test public void testPendingWidget_notRestored_removed() { addPendingItemToScreen(getInvalidWidgetInfo(), FLAG_ID_NOT_VALID | FLAG_PROVIDER_NOT_READY); - assertTrue("Pending widget exists", - mLauncher.getWorkspace().tryGetPendingWidget(0) == null); + verifyItemEventuallyNull("Pending widget exists", pendingWidgetProvider()); // Item deleted from db mCursor = queryItem(); assertEquals(0, mCursor.getCount()); @@ -216,7 +214,7 @@ public class TaplBindWidgetTest extends AbstractLauncherUiTest { // Create an active installer session SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL); params.setAppPackageName(item.providerName.getPackageName()); - PackageInstaller installer = mTargetContext.getPackageManager().getPackageInstaller(); + PackageInstaller installer = targetContext().getPackageManager().getPackageInstaller(); mSessionId = installer.createSession(params); addPendingItemToScreen(item, FLAG_ID_NOT_VALID | FLAG_PROVIDER_NOT_READY); @@ -234,36 +232,47 @@ public class TaplBindWidgetTest extends AbstractLauncherUiTest { } private void verifyWidgetPresent(LauncherAppWidgetProviderInfo info) { - final Widget widget = mLauncher.getWorkspace().tryGetWidget(info.label, - TestUtil.DEFAULT_UI_TIMEOUT); - assertTrue("Widget is not present", - widget != null); + getOnceNotNull("Widget is not present", widgetProvider(info)); } private void verifyPendingWidgetPresent() { - final Widget widget = mLauncher.getWorkspace().tryGetPendingWidget( - TestUtil.DEFAULT_UI_TIMEOUT); - assertTrue("Pending widget is not present", - widget != null); + getOnceNotNull("Widget is not present", pendingWidgetProvider()); + } + + private Function pendingWidgetProvider() { + return l -> l.getWorkspace().getFirstMatch( + (item, view) -> view instanceof PendingAppWidgetHostView); + } + + private Function widgetProvider(LauncherAppWidgetProviderInfo info) { + return l -> l.getWorkspace().getFirstMatch((item, view) -> + view instanceof LauncherAppWidgetHostView + && TextUtils.equals(info.label, view.getContentDescription())); + } + + private void verifyItemEventuallyNull(String message, Function provider) { + atMost(message, () -> getFromLauncher(provider) == null); } private void addPendingItemToScreen(LauncherAppWidgetInfo item, int restoreStatus) { item.restoreStatus = restoreStatus; item.screenId = FIRST_SCREEN_ID; - commitTransactionAndLoadHome( - new FavoriteItemsTransaction(mTargetContext).addItem(() -> item)); + new FavoriteItemsTransaction(targetContext()).addItem(() -> item).commit(); + loadLauncherSync(); } private LauncherAppWidgetProviderInfo addWidgetToScreen(boolean hasConfigureScreen, boolean bindWidget, Consumer itemOverride) { LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(hasConfigureScreen); - commitTransactionAndLoadHome(new FavoriteItemsTransaction(mTargetContext) + new FavoriteItemsTransaction(targetContext()) .addItem(() -> { - LauncherAppWidgetInfo item = createWidgetInfo(info, mTargetContext, bindWidget); + LauncherAppWidgetInfo item = + createWidgetInfo(info, targetContext(), bindWidget); item.screenId = FIRST_SCREEN_ID; itemOverride.accept(item); return item; - })); + }).commit(); + loadLauncherSync(); return info; } @@ -277,13 +286,13 @@ public class TaplBindWidgetTest extends AbstractLauncherUiTest { Set activePackage = getOnUiThread(() -> { Set packages = new HashSet<>(); - InstallSessionHelper.INSTANCE.get(mTargetContext).getActiveSessions() + InstallSessionHelper.INSTANCE.get(targetContext()).getActiveSessions() .keySet().forEach(packageUserKey -> packages.add(packageUserKey.mPackageName)); return packages; }); while (true) { try { - mTargetContext.getPackageManager().getPackageInfo( + targetContext().getPackageManager().getPackageInfo( pkg, PackageManager.GET_UNINSTALLED_PACKAGES); } catch (Exception e) { if (!activePackage.contains(pkg)) { diff --git a/tests/src/com/android/launcher3/ui/widget/TaplRequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java similarity index 61% rename from tests/src/com/android/launcher3/ui/widget/TaplRequestPinItemTest.java rename to tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java index fe3b2eea6c..2fb7987f05 100644 --- a/tests/src/com/android/launcher3/ui/widget/TaplRequestPinItemTest.java +++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java @@ -1,34 +1,41 @@ /* * Copyright (C) 2017 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 + * 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 + * 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. + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package com.android.launcher3.ui.widget; import static android.app.PendingIntent.FLAG_MUTABLE; import static android.app.PendingIntent.FLAG_ONE_SHOT; +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; +import static java.util.regex.Pattern.CASE_INSENSITIVE; + import android.app.PendingIntent; import android.appwidget.AppWidgetManager; +import android.content.Context; import android.content.Intent; import android.graphics.Color; import android.view.View; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.LargeTest; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.uiautomator.By; +import androidx.test.uiautomator.BySelector; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherSettings.Favorites; @@ -37,14 +44,13 @@ import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.LauncherAppWidgetInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.shortcuts.ShortcutKey; -import com.android.launcher3.tapl.AddToHomeScreenPrompt; import com.android.launcher3.testcomponent.AppWidgetNoConfig; import com.android.launcher3.testcomponent.AppWidgetWithConfig; import com.android.launcher3.testcomponent.RequestPinItemActivity; -import com.android.launcher3.ui.AbstractLauncherUiTest; +import com.android.launcher3.util.BaseLauncherActivityTest; +import com.android.launcher3.util.BlockingBroadcastReceiver; import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator; -import com.android.launcher3.util.Wait; -import com.android.launcher3.util.Wait.Condition; +import com.android.launcher3.util.TestUtil; import com.android.launcher3.util.rule.ShellCommandRule; import org.junit.Before; @@ -53,25 +59,27 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.util.UUID; +import java.util.regex.Pattern; /** * Test to verify pin item request flow. */ @LargeTest @RunWith(AndroidJUnit4.class) -public class TaplRequestPinItemTest extends AbstractLauncherUiTest { +public class RequestPinItemTest extends BaseLauncherActivityTest { @Rule public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind(); + @Rule + public ShellCommandRule mDefaultLauncherRule = ShellCommandRule.setDefaultLauncher(); + private String mCallbackAction; private String mShortcutId; private int mAppWidgetId; - @Override @Before public void setUp() throws Exception { - super.setUp(); mCallbackAction = UUID.randomUUID().toString(); mShortcutId = UUID.randomUUID().toString(); } @@ -81,9 +89,9 @@ public class TaplRequestPinItemTest extends AbstractLauncherUiTest { @Test public void testPinWidgetNoConfig() throws Throwable { - runTest("pinWidgetNoConfig", true, (info, view) -> info instanceof LauncherAppWidgetInfo && - ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId && - ((LauncherAppWidgetInfo) info).providerName.getClassName() + runTest("pinWidgetNoConfig", true, (info, view) -> info instanceof LauncherAppWidgetInfo + && ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId + && ((LauncherAppWidgetInfo) info).providerName.getClassName() .equals(AppWidgetNoConfig.class.getName())); } @@ -94,18 +102,18 @@ public class TaplRequestPinItemTest extends AbstractLauncherUiTest { RequestPinItemActivity.class, "setRemoteViewColor").putExtra( RequestPinItemActivity.EXTRA_PARAM + "0", Color.RED); - runTest("pinWidgetNoConfig", true, (info, view) -> info instanceof LauncherAppWidgetInfo && - ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId && - ((LauncherAppWidgetInfo) info).providerName.getClassName() + runTest("pinWidgetNoConfig", true, (info, view) -> info instanceof LauncherAppWidgetInfo + && ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId + && ((LauncherAppWidgetInfo) info).providerName.getClassName() .equals(AppWidgetNoConfig.class.getName()), command); } @Test public void testPinWidgetWithConfig() throws Throwable { runTest("pinWidgetWithConfig", true, - (info, view) -> info instanceof LauncherAppWidgetInfo && - ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId && - ((LauncherAppWidgetInfo) info).providerName.getClassName() + (info, view) -> info instanceof LauncherAppWidgetInfo + && ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId + && ((LauncherAppWidgetInfo) info).providerName.getClassName() .equals(AppWidgetWithConfig.class.getName())); } @@ -119,47 +127,48 @@ public class TaplRequestPinItemTest extends AbstractLauncherUiTest { runTest("pinShortcut", false, new ItemOperator() { @Override public boolean evaluate(ItemInfo info, View view) { - return info instanceof WorkspaceItemInfo && - info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT && - ShortcutKey.fromItemInfo(info).getId().equals(mShortcutId); + return info instanceof WorkspaceItemInfo + && info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT + && ShortcutKey.fromItemInfo(info).getId().equals(mShortcutId); } }, command); } private void runTest(String activityMethod, boolean isWidget, ItemOperator itemMatcher, Intent... commandIntents) throws Throwable { - commitTransactionAndLoadHome(new FavoriteItemsTransaction(mTargetContext)); + new FavoriteItemsTransaction(targetContext()).commit(); + loadLauncherSync(); // Open Pin item activity BlockingBroadcastReceiver openMonitor = new BlockingBroadcastReceiver( RequestPinItemActivity.class.getName()); - mLauncher. - getWorkspace(). - switchToAllApps(). - getAppIcon("Test Pin Item"). - launch(getAppPackageName()); + Context testContext = getInstrumentation().getContext(); + startAppFast( + testContext.getPackageName(), + new Intent(testContext, RequestPinItemActivity.class)); assertNotNull(openMonitor.blockingGetExtraIntent()); // Set callback - PendingIntent callback = PendingIntent.getBroadcast(mTargetContext, 0, - new Intent(mCallbackAction).setPackage(mTargetContext.getPackageName()), + PendingIntent callback = PendingIntent.getBroadcast(targetContext(), 0, + new Intent(mCallbackAction).setPackage(targetContext().getPackageName()), FLAG_ONE_SHOT | FLAG_MUTABLE); - mTargetContext.sendBroadcast(RequestPinItemActivity.getCommandIntent( + targetContext().sendBroadcast(RequestPinItemActivity.getCommandIntent( RequestPinItemActivity.class, "setCallback").putExtra( RequestPinItemActivity.EXTRA_PARAM + "0", callback)); for (Intent command : commandIntents) { - mTargetContext.sendBroadcast(command); + targetContext().sendBroadcast(command); } // call the requested method to start the flow - mTargetContext.sendBroadcast(RequestPinItemActivity.getCommandIntent( + targetContext().sendBroadcast(RequestPinItemActivity.getCommandIntent( RequestPinItemActivity.class, activityMethod)); - final AddToHomeScreenPrompt addToHomeScreenPrompt = mLauncher.getAddToHomeScreenPrompt(); // Accept confirmation: BlockingBroadcastReceiver resultReceiver = new BlockingBroadcastReceiver(mCallbackAction); - addToHomeScreenPrompt.addAutomatically(); + BySelector selector = By.text(Pattern.compile("^Add to home screen$", CASE_INSENSITIVE)) + .pkg(targetContext().getPackageName()); + uiDevice.wait(device -> device.findObject(selector), TestUtil.DEFAULT_UI_TIMEOUT).click(); Intent result = resultReceiver.blockingGetIntent(); assertNotNull(result); mAppWidgetId = result.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1); @@ -167,28 +176,9 @@ public class TaplRequestPinItemTest extends AbstractLauncherUiTest { assertNotSame(-1, mAppWidgetId); } - // Go back to home - mLauncher.goHome(); - Wait.atMost("", new ItemSearchCondition(itemMatcher), mLauncher); - } - - /** - * Condition for for an item - */ - private class ItemSearchCondition implements Condition { - - private final ItemOperator mOp; - - ItemSearchCondition(ItemOperator op) { - mOp = op; - } - - @Override - public boolean isTrue() throws Throwable { - return mMainThreadExecutor.submit(() -> { - Launcher l = Launcher.ACTIVITY_TRACKER.getCreatedContext(); - return l != null && l.getWorkspace().getFirstMatch(mOp) != null; - }).get(); - } + // Reload activity, so that the activity is focused + closeCurrentActivity(); + loadLauncherSync(); + getOnceNotNull("", l -> l.getWorkspace().getFirstMatch(itemMatcher)); } } diff --git a/tests/src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java b/tests/src/com/android/launcher3/ui/widget/WidgetPickerTest.java similarity index 68% rename from tests/src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java rename to tests/src/com/android/launcher3/ui/widget/WidgetPickerTest.java index 19c585085b..caad1d98b7 100644 --- a/tests/src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java +++ b/tests/src/com/android/launcher3/ui/widget/WidgetPickerTest.java @@ -22,24 +22,30 @@ import static org.junit.Assert.assertTrue; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.MediumTest; +import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.Launcher; -import com.android.launcher3.tapl.Widgets; -import com.android.launcher3.ui.AbstractLauncherUiTest; import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape; +import com.android.launcher3.util.BaseLauncherActivityTest; +import com.android.launcher3.util.rule.ScreenRecordRule; import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord; +import com.android.launcher3.views.OptionsPopupView; import com.android.launcher3.widget.picker.WidgetsFullSheet; import com.android.launcher3.widget.picker.WidgetsRecyclerView; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TestRule; import org.junit.runner.RunWith; /** - * This test run in both Out of process (Oop) and in-process (Ipc). * Make sure the basic interactions with the WidgetPicker works. */ @MediumTest @RunWith(AndroidJUnit4.class) -public class TaplWidgetPickerTest extends AbstractLauncherUiTest { +public class WidgetPickerTest extends BaseLauncherActivityTest { + + @Rule + public TestRule screenRecordRule = new ScreenRecordRule(); private WidgetsRecyclerView getWidgetsView(Launcher launcher) { return WidgetsFullSheet.getWidgetsView(launcher); @@ -56,30 +62,21 @@ public class TaplWidgetPickerTest extends AbstractLauncherUiTest { @ScreenRecord @PortraitLandscape public void testWidgets() { - mLauncher.goHome(); + loadLauncherSync(); // Test opening widgets. executeOnLauncher(launcher -> assertTrue("Widgets is initially opened", getWidgetsView(launcher) == null)); - Widgets widgets = mLauncher.getWorkspace().openAllWidgets(); - assertNotNull("openAllWidgets() returned null", widgets); - widgets = mLauncher.getAllWidgets(); + assertNotNull("openAllWidgets() returned null", + getFromLauncher(OptionsPopupView::openWidgets)); + WidgetsRecyclerView widgets = getFromLauncher(this::getWidgetsView); assertNotNull("getAllWidgets() returned null", widgets); - executeOnLauncher(launcher -> - assertTrue("Widgets is not shown", getWidgetsView(launcher).isShown())); + executeOnLauncher(launcher -> assertTrue("Widgets is not shown", widgets.isShown())); executeOnLauncher(launcher -> assertEquals("Widgets is scrolled upon opening", 0, getWidgetsScroll(launcher))); - // Test flinging widgets. - widgets.flingForward(); - Integer flingForwardY = getFromLauncher(launcher -> getWidgetsScroll(launcher)); - executeOnLauncher(launcher -> assertTrue("Flinging forward didn't scroll widgets", - flingForwardY > 0)); + executeOnLauncher(AbstractFloatingView::closeAllOpenViews); + uiDevice.waitForIdle(); - widgets.flingBackward(); - executeOnLauncher(launcher -> assertTrue("Flinging backward didn't scroll widgets", - getWidgetsScroll(launcher) < flingForwardY)); - - mLauncher.goHome(); waitForLauncherCondition("Widgets were not closed", launcher -> getWidgetsView(launcher) == null); } diff --git a/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java b/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java index cfc0a6b286..c623513f07 100644 --- a/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java +++ b/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java @@ -15,8 +15,6 @@ */ package com.android.launcher3.ui.workspace; -import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; - import static com.android.launcher3.AbstractFloatingView.TYPE_ACTION_POPUP; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.TestConstants.AppNames.TEST_APP_NAME; @@ -29,11 +27,9 @@ import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.ContentValues; import android.net.Uri; -import android.view.View; import android.view.ViewGroup; import androidx.test.filters.LargeTest; -import androidx.test.uiautomator.UiDevice; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.BubbleTextView; @@ -49,9 +45,6 @@ import com.android.launcher3.util.TestUtil; import org.junit.Test; -import java.util.ArrayDeque; -import java.util.Queue; - /** * Tests for theme icon support in Launcher * @@ -137,27 +130,10 @@ public class ThemeIconsTest extends BaseLauncherActivityTest { } catch (Exception e) { throw new RuntimeException(e); } - - // Find the app icon - Queue viewQueue = new ArrayDeque<>(); - viewQueue.add(parent); - BubbleTextView icon = null; - while (!viewQueue.isEmpty()) { - View view = viewQueue.poll(); - if (view instanceof ViewGroup) { - parent = (ViewGroup) view; - for (int i = parent.getChildCount() - 1; i >= 0; i--) { - viewQueue.add(parent.getChildAt(i)); - } - } else if (view instanceof BubbleTextView btv) { - if (btv.getContentDescription() != null - && title.equals(btv.getContentDescription().toString())) { - icon = btv; - break; - } - } - } - return icon; + return (BubbleTextView) searchView(parent, v -> + v instanceof BubbleTextView btv + && btv.getContentDescription() != null + && title.equals(btv.getContentDescription().toString())); } private BubbleTextView verifyIconTheme(String title, ViewGroup parent, boolean isThemed) { @@ -193,11 +169,4 @@ public class ThemeIconsTest extends BaseLauncherActivityTest { rv.getLayoutManager().scrollToPosition(pos); }); } - - private void addToWorkspace(View btv) { - TestUtil.runOnExecutorSync(MAIN_EXECUTOR, () -> - btv.getAccessibilityDelegate().performAccessibilityAction( - btv, com.android.launcher3.R.id.action_add_to_workspace, null)); - UiDevice.getInstance(getInstrumentation()).waitForIdle(); - } } diff --git a/tests/src/com/android/launcher3/util/BaseLauncherActivityTest.kt b/tests/src/com/android/launcher3/util/BaseLauncherActivityTest.kt index 476e497807..644659296d 100644 --- a/tests/src/com/android/launcher3/util/BaseLauncherActivityTest.kt +++ b/tests/src/com/android/launcher3/util/BaseLauncherActivityTest.kt @@ -22,6 +22,9 @@ import android.view.InputDevice import android.view.KeyCharacterMap import android.view.KeyEvent import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup +import androidx.core.view.children import androidx.lifecycle.Lifecycle.State.RESUMED import androidx.test.core.app.ActivityScenario import androidx.test.core.app.ActivityScenario.ActivityAction @@ -30,11 +33,13 @@ import androidx.test.uiautomator.UiDevice import com.android.launcher3.Launcher import com.android.launcher3.LauncherAppState import com.android.launcher3.LauncherState +import com.android.launcher3.R import com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST import com.android.launcher3.tapl.TestHelpers import com.android.launcher3.util.ModelTestExtensions.loadModelSync import com.android.launcher3.util.Wait.atMost import java.util.function.Function +import java.util.function.Predicate import java.util.function.Supplier import org.junit.After @@ -56,6 +61,8 @@ open class BaseLauncherActivityTest { ) .also { currentScenario = it } + @JvmField val uiDevice = UiDevice.getInstance(getInstrumentation()) + @After fun closeCurrentActivity() { currentScenario?.close() @@ -136,18 +143,43 @@ open class BaseLauncherActivityTest { event.recycle() } - fun startAppFast(packageName: String) { - val intent = targetContext().packageManager.getLaunchIntentForPackage(packageName)!! + @JvmOverloads + fun startAppFast( + packageName: String, + intent: Intent = targetContext().packageManager.getLaunchIntentForPackage(packageName)!!, + ) { intent.addCategory(Intent.CATEGORY_LAUNCHER) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) targetContext().startActivity(intent) - UiDevice.getInstance(getInstrumentation()).waitForIdle() + uiDevice.waitForIdle() } fun freezeAllApps() = executeOnLauncher { it.appsView.appsStore.enableDeferUpdates(DEFER_UPDATES_TEST) } - fun executeShellCommand(cmd: String) = - UiDevice.getInstance(getInstrumentation()).executeShellCommand(cmd) + fun executeShellCommand(cmd: String) = uiDevice.executeShellCommand(cmd) + + fun addToWorkspace(view: View) { + TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) { + view.accessibilityDelegate.performAccessibilityAction( + view, + R.id.action_add_to_workspace, + null, + ) + } + UiDevice.getInstance(getInstrumentation()).waitForIdle() + } + + fun ViewGroup.searchView(filter: Predicate): View? { + if (filter.test(this)) return this + for (child in children) { + if (filter.test(child)) return child + if (child is ViewGroup) + child.searchView(filter)?.let { + return it + } + } + return null + } } diff --git a/tests/src/com/android/launcher3/util/BlockingBroadcastReceiver.kt b/tests/src/com/android/launcher3/util/BlockingBroadcastReceiver.kt new file mode 100644 index 0000000000..20881d19fb --- /dev/null +++ b/tests/src/com/android/launcher3/util/BlockingBroadcastReceiver.kt @@ -0,0 +1,54 @@ +/* + * 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 android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.Parcelable +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import java.util.concurrent.CompletableFuture +import java.util.concurrent.TimeUnit.SECONDS + +private const val DEFAULT_BROADCAST_TIMEOUT_SECS: Long = 10 + +/** Broadcast receiver which blocks until the result is received. */ +open class BlockingBroadcastReceiver(action: String) : BroadcastReceiver() { + + val value = CompletableFuture() + + init { + getInstrumentation() + .targetContext + .registerReceiver(this, IntentFilter(action), Context.RECEIVER_EXPORTED) + } + + override fun onReceive(context: Context, intent: Intent) { + value.complete(intent) + } + + @Throws(InterruptedException::class) + fun blockingGetIntent(): Intent = + value.get(DEFAULT_BROADCAST_TIMEOUT_SECS, SECONDS).also { + getInstrumentation().targetContext.unregisterReceiver(this) + } + + @Throws(InterruptedException::class) + fun blockingGetExtraIntent(): Intent? = + blockingGetIntent().getParcelableExtra(Intent.EXTRA_INTENT) as Intent? +} From cdde399a00c4601c88b4bc661573722247f91b92 Mon Sep 17 00:00:00 2001 From: fbaron Date: Thu, 14 Nov 2024 22:05:57 -0800 Subject: [PATCH 5/6] Add new heuristic for deciding whether we should add extra rows on grid migration to the bottom or top of the grid Bug: 364711064 Flag: com.android.launcher3.one_grid_specs Test: GridSizeMigrationTest Change-Id: I070ed1eddc2c7ab475267268ebdbb2e559ab6dda --- .../model/GridSizeMigrationDBController.java | 39 ++++++++++--- .../launcher3/model/GridSizeMigrationLogic.kt | 55 +++++++++++++------ .../launcher3/provider/LauncherDbUtils.kt | 5 ++ 3 files changed, 73 insertions(+), 26 deletions(-) diff --git a/src/com/android/launcher3/model/GridSizeMigrationDBController.java b/src/com/android/launcher3/model/GridSizeMigrationDBController.java index 617cac7f59..bfa00bda9e 100644 --- a/src/com/android/launcher3/model/GridSizeMigrationDBController.java +++ b/src/com/android/launcher3/model/GridSizeMigrationDBController.java @@ -17,12 +17,14 @@ package com.android.launcher3.model; import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle; +import static com.android.launcher3.Flags.oneGridSpecs; import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME; import static com.android.launcher3.LauncherSettings.Favorites.TMP_TABLE; import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET; import static com.android.launcher3.model.LoaderTask.SMARTSPACE_ON_HOME_SCREEN; import static com.android.launcher3.provider.LauncherDbUtils.copyTable; import static com.android.launcher3.provider.LauncherDbUtils.dropTable; +import static com.android.launcher3.provider.LauncherDbUtils.shiftTableByXCells; import android.content.ComponentName; import android.content.ContentValues; @@ -130,6 +132,20 @@ public class GridSizeMigrationDBController { // Only use this strategy when comparing the previous grid to the new grid and the // columns are the same and the destination has more rows copyTable(source, TABLE_NAME, target.getWritableDatabase(), TABLE_NAME, context); + + if (oneGridSpecs()) { + DbReader destReader = new DbReader( + target.getWritableDatabase(), TABLE_NAME, context); + boolean shouldShiftCells = shouldShiftCells(destReader, srcDeviceState.getRows()); + if (shouldShiftCells) { + shiftTableByXCells( + target.getWritableDatabase(), + (destDeviceState.getRows() - srcDeviceState.getRows()), + TABLE_NAME); + } + } + + // Save current configuration, so that the migration does not run again. destDeviceState.writeToPrefs(context); return true; } @@ -427,17 +443,22 @@ public class GridSizeMigrationDBController { } } - static void copyCurrentGridToNewGrid( - @NonNull Context context, - @NonNull DeviceGridState destDeviceState, - @NonNull DatabaseHelper target, - @NonNull SQLiteDatabase source) { - // Only use this strategy when comparing the previous grid to the new grid and the - // columns are the same and the destination has more rows - copyTable(source, TABLE_NAME, target.getWritableDatabase(), TABLE_NAME, context); - destDeviceState.writeToPrefs(context); + private static boolean shouldShiftCells(final DbReader destReader, final int srcGridRowCount) { + List workspaceItems = destReader.loadAllWorkspaceEntries(); + int firstPageItemsRowPosSum = workspaceItems.stream() + .filter(entry -> entry.screenId == 0) + .mapToInt(entry -> entry.cellY).sum(); + int firstPageWorkspaceItemsCount = (int) workspaceItems.stream() + .filter(entry -> entry.screenId == 0).count(); + if (firstPageWorkspaceItemsCount == 0) { + return false; + } + float srcGridMidPoint = srcGridRowCount / 2f; + float firstPageItemPosAvg = (float) firstPageItemsRowPosSum / firstPageWorkspaceItemsCount; + return (firstPageItemPosAvg >= srcGridMidPoint); } + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) public static class DbReader { diff --git a/src/com/android/launcher3/model/GridSizeMigrationLogic.kt b/src/com/android/launcher3/model/GridSizeMigrationLogic.kt index c856d4bb46..3f52d8a73f 100644 --- a/src/com/android/launcher3/model/GridSizeMigrationLogic.kt +++ b/src/com/android/launcher3/model/GridSizeMigrationLogic.kt @@ -21,15 +21,20 @@ import android.graphics.Point import android.util.Log import androidx.annotation.VisibleForTesting import com.android.launcher3.Flags +import com.android.launcher3.Flags.oneGridSpecs import com.android.launcher3.LauncherPrefs import com.android.launcher3.LauncherPrefs.Companion.get import com.android.launcher3.LauncherPrefs.Companion.getPrefs import com.android.launcher3.LauncherSettings +import com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME +import com.android.launcher3.LauncherSettings.Favorites.TMP_TABLE import com.android.launcher3.Utilities import com.android.launcher3.config.FeatureFlags import com.android.launcher3.model.GridSizeMigrationDBController.DbReader -import com.android.launcher3.provider.LauncherDbUtils import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction +import com.android.launcher3.provider.LauncherDbUtils.copyTable +import com.android.launcher3.provider.LauncherDbUtils.dropTable +import com.android.launcher3.provider.LauncherDbUtils.shiftTableByXCells import com.android.launcher3.util.CellAndSpan import com.android.launcher3.util.GridOccupancy import com.android.launcher3.util.IntArray @@ -59,27 +64,30 @@ class GridSizeMigrationLogic { // amount of rows we simply copy over the source grid to the destination grid, rather // than undergoing the general grid migration. if (shouldMigrateToStrictlyTallerGrid(isDestNewDb, srcDeviceState, destDeviceState)) { - GridSizeMigrationDBController.copyCurrentGridToNewGrid( - context, - destDeviceState, - target, - source, - ) + copyTable(source, TABLE_NAME, target.writableDatabase, TABLE_NAME, context) + if (oneGridSpecs()) { + val destReader = DbReader(target.writableDatabase, TABLE_NAME, context) + val shouldShiftCells = shouldShiftCells(destReader, srcDeviceState.rows) + if (shouldShiftCells) { + shiftTableByXCells( + target.writableDatabase, + (destDeviceState.rows - srcDeviceState.rows), + TABLE_NAME, + ) + } + } + // Save current configuration, so that the migration does not run again. + destDeviceState.writeToPrefs(context) return } - LauncherDbUtils.copyTable( - source, - LauncherSettings.Favorites.TABLE_NAME, - target.writableDatabase, - LauncherSettings.Favorites.TMP_TABLE, - context, - ) + + copyTable(source, TABLE_NAME, target.writableDatabase, TMP_TABLE, context) val migrationStartTime = System.currentTimeMillis() try { SQLiteTransaction(target.writableDatabase).use { t -> - val srcReader = DbReader(t.db, LauncherSettings.Favorites.TMP_TABLE, context) - val destReader = DbReader(t.db, LauncherSettings.Favorites.TABLE_NAME, context) + val srcReader = DbReader(t.db, TMP_TABLE, context) + val destReader = DbReader(t.db, TABLE_NAME, context) val targetSize = Point(destDeviceState.columns, destDeviceState.rows) @@ -95,7 +103,7 @@ class GridSizeMigrationLogic { // Migrate workspace. migrateWorkspace(srcReader, destReader, target, targetSize, idsInUse) - LauncherDbUtils.dropTable(t.db, LauncherSettings.Favorites.TMP_TABLE) + dropTable(t.db, TMP_TABLE) t.commit() } } catch (e: Exception) { @@ -112,6 +120,19 @@ class GridSizeMigrationLogic { } } + private fun shouldShiftCells(destReader: DbReader, srcGridRowCount: Int): Boolean { + val workspaceItems = destReader.loadAllWorkspaceEntries() + val firstPageItemsRowPosSum = + workspaceItems.sumOf { entry -> if (entry.screenId == 0) entry.cellY else 0 } + val firstPageWorkspaceItemsCount = workspaceItems.count { entry -> entry.screenId == 0 } + if (firstPageWorkspaceItemsCount == 0) { + return false + } + val srcGridMidPoint = srcGridRowCount / 2f + val firstPageItemPosAvg = firstPageItemsRowPosSum / firstPageWorkspaceItemsCount.toFloat() + return (firstPageItemPosAvg >= srcGridMidPoint) + } + /** Handles hotseat migration. */ @VisibleForTesting fun migrateHotseat( diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.kt b/src/com/android/launcher3/provider/LauncherDbUtils.kt index 3c68e462a8..6f1d0dddaf 100644 --- a/src/com/android/launcher3/provider/LauncherDbUtils.kt +++ b/src/com/android/launcher3/provider/LauncherDbUtils.kt @@ -131,6 +131,11 @@ object LauncherDbUtils { } } + @JvmStatic + fun shiftTableByXCells(db: SQLiteDatabase, x: Int, toTable: String) { + db.run { execSQL("UPDATE $toTable SET cellY = cellY + $x") } + } + /** * Migrates the legacy shortcuts to deep shortcuts pinned under Launcher. Removes any invalid * shortcut or any shortcut which requires some permission to launch From ec111e24e5851fd57096d3c3055a904662d19082 Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Mon, 11 Nov 2024 10:51:43 -0800 Subject: [PATCH 6/6] Check if all apps are translucent when finishing recents animation. If true, then launcher will be finished at the end of the animation. This fixes the issue where taskbar will stash in overview because we assumed the app was fully opaque. Bug: 354627538 Test: open app, go to overview, tap icon, pause app, note no issue Flag: EXEMPT bugfix Change-Id: Id51e0d9f63c9615127e27455f7edf171381c2011 --- .../TaskbarLauncherStateController.java | 28 +++++++++++++++---- .../quickstep/RecentsAnimationController.java | 27 +++++++++++++++++- .../android/quickstep/views/RecentsView.java | 13 +++++++-- 3 files changed, 58 insertions(+), 10 deletions(-) diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java index 29988927bb..dce377d5d7 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java @@ -1009,7 +1009,12 @@ public class TaskbarLauncherStateController { @Override public void onRecentsAnimationFinished(RecentsAnimationController controller) { - endGestureStateOverride(!controller.getFinishTargetIsLauncher(), false /*canceled*/); + endGestureStateOverride(!controller.getFinishTargetIsLauncher(), + controller.getLauncherIsVisibleAtFinish(), false /*canceled*/); + } + + private void endGestureStateOverride(boolean finishedToApp, boolean canceled) { + endGestureStateOverride(finishedToApp, finishedToApp, canceled); } /** @@ -1019,11 +1024,13 @@ public class TaskbarLauncherStateController { * * @param finishedToApp {@code true} if the recents animation finished to showing an app and * not workspace or overview + * @param launcherIsVisible {code true} if launcher is visible at finish * @param canceled {@code true} if the recents animation was canceled instead of * finishing * to completion */ - private void endGestureStateOverride(boolean finishedToApp, boolean canceled) { + private void endGestureStateOverride(boolean finishedToApp, boolean launcherIsVisible, + boolean canceled) { mCallbacks.removeListener(this); mTaskBarRecentsAnimationListener = null; ((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(null); @@ -1032,18 +1039,27 @@ public class TaskbarLauncherStateController { mSkipNextRecentsAnimEnd = false; return; } - updateStateForUserFinishedToApp(finishedToApp); + updateStateForUserFinishedToApp(finishedToApp, launcherIsVisible); } } + /** + * @see #updateStateForUserFinishedToApp(boolean, boolean) + */ + private void updateStateForUserFinishedToApp(boolean finishedToApp) { + updateStateForUserFinishedToApp(finishedToApp, !finishedToApp); + } + /** * Updates the visible state immediately to ensure a seamless handoff. * * @param finishedToApp True iff user is in an app. + * @param launcherIsVisible True iff launcher is still visible (ie. transparent app) */ - private void updateStateForUserFinishedToApp(boolean finishedToApp) { + private void updateStateForUserFinishedToApp(boolean finishedToApp, + boolean launcherIsVisible) { // Update the visible state immediately to ensure a seamless handoff - boolean launcherVisible = !finishedToApp; + boolean launcherVisible = !finishedToApp || launcherIsVisible; updateStateForFlag(FLAG_TRANSITION_TO_VISIBLE, false); updateStateForFlag(FLAG_VISIBLE, launcherVisible); applyState(); @@ -1052,7 +1068,7 @@ public class TaskbarLauncherStateController { if (DEBUG) { Log.d(TAG, "endGestureStateOverride - FLAG_IN_APP: " + finishedToApp); } - controller.updateStateForFlag(FLAG_IN_APP, finishedToApp); + controller.updateStateForFlag(FLAG_IN_APP, finishedToApp && !launcherIsVisible); controller.applyState(); } diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java index dcb01087ee..60fcff86f3 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java @@ -53,6 +53,8 @@ public class RecentsAnimationController { private boolean mFinishRequested = false; // Only valid when mFinishRequested == true. private boolean mFinishTargetIsLauncher; + // Only valid when mFinishRequested == true + private boolean mLauncherIsVisibleAtFinish; private RunnableList mPendingFinishCallbacks = new RunnableList(); public RecentsAnimationController(RecentsAnimationControllerCompat controller, @@ -116,14 +118,28 @@ public class RecentsAnimationController { finishController(toRecents, onFinishComplete, sendUserLeaveHint); } + @UiThread + public void finish(boolean toRecents, boolean launcherIsVisibleAtFinish, + Runnable onFinishComplete, boolean sendUserLeaveHint) { + Preconditions.assertUIThread(); + finishController(toRecents, launcherIsVisibleAtFinish, onFinishComplete, sendUserLeaveHint, + false); + } + @UiThread public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint) { - finishController(toRecents, callback, sendUserLeaveHint, false /* forceFinish */); + finishController(toRecents, false, callback, sendUserLeaveHint, false /* forceFinish */); } @UiThread public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint, boolean forceFinish) { + finishController(toRecents, toRecents, callback, sendUserLeaveHint, forceFinish); + } + + @UiThread + public void finishController(boolean toRecents, boolean launcherIsVisibleAtFinish, + Runnable callback, boolean sendUserLeaveHint, boolean forceFinish) { mPendingFinishCallbacks.add(callback); if (!forceFinish && mFinishRequested) { // If finish has already been requested, then add the callback to the pending list. @@ -135,6 +151,7 @@ public class RecentsAnimationController { // Finish not yet requested mFinishRequested = true; mFinishTargetIsLauncher = toRecents; + mLauncherIsVisibleAtFinish = launcherIsVisibleAtFinish; mOnFinishedListener.accept(this); Runnable finishCb = () -> { mController.finish(toRecents, sendUserLeaveHint, new IResultReceiver.Stub() { @@ -211,6 +228,14 @@ public class RecentsAnimationController { return mFinishTargetIsLauncher; } + /** + * RecentsAnimationListeners can check this in onRecentsAnimationFinished() to determine whether + * the animation was finished to launcher vs an app. + */ + public boolean getLauncherIsVisibleAtFinish() { + return mLauncherIsVisibleAtFinish; + } + public void dump(String prefix, PrintWriter pw) { pw.println(prefix + "RecentsAnimationController:"); diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 8982850532..92c93ff9ee 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -5854,15 +5854,22 @@ public abstract class RecentsView< * Finish recents animation. */ public void finishRecentsAnimation(boolean toRecents, @Nullable Runnable onFinishComplete) { - finishRecentsAnimation(toRecents, true /* shouldPip */, onFinishComplete); + finishRecentsAnimation(toRecents, false, true /* shouldPip */, onFinishComplete); } + /** + * Finish recents animation. + */ + public void finishRecentsAnimation(boolean toRecents, boolean shouldPip, + @Nullable Runnable onFinishComplete) { + finishRecentsAnimation(toRecents, shouldPip, false, onFinishComplete); + } /** * NOTE: Whatever value gets passed through to the toRecents param may need to also be set on * {@link #mRecentsAnimationController#setWillFinishToHome}. */ public void finishRecentsAnimation(boolean toRecents, boolean shouldPip, - @Nullable Runnable onFinishComplete) { + boolean allAppTargetsAreTranslucent, @Nullable Runnable onFinishComplete) { Log.d(TAG, "finishRecentsAnimation - mRecentsAnimationController: " + mRecentsAnimationController); // TODO(b/197232424#comment#10) Move this back into onRecentsAnimationComplete(). Maybe? @@ -5894,7 +5901,7 @@ public abstract class RecentsView< tx, null /* overlay */); } } - mRecentsAnimationController.finish(toRecents, () -> { + mRecentsAnimationController.finish(toRecents, allAppTargetsAreTranslucent, () -> { if (onFinishComplete != null) { onFinishComplete.run(); }