diff options
13 files changed, 254 insertions, 152 deletions
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java index aae8a56593..954272c612 100644 --- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java +++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java @@ -92,6 +92,7 @@ import android.os.Looper; import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings; +import android.util.Log; import android.util.Pair; import android.util.Size; import android.view.CrossWindowBlurListeners; @@ -182,6 +183,7 @@ import java.util.Map.Entry; * Manages the opening and closing app transitions from Launcher */ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener { + private static final String TAG = "QuickstepTransitionManager"; private static final boolean ENABLE_SHELL_STARTING_SURFACE = SystemProperties.getBoolean("persist.debug.shell_starting_surface", true); @@ -1207,7 +1209,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener mLauncher.removeOnDeviceProfileChangeListener(this); SystemUiProxy.INSTANCE.get(mLauncher).setStartingWindowListener(null); if (BuildConfig.IS_STUDIO_BUILD && !mRegisteredTaskStackChangeListener.isEmpty()) { - throw new IllegalStateException("Failed to run onEndCallback created from" + Log.e(TAG, "IllegalState: Failed to run onEndCallback created from" + " getActivityLaunchOptions()"); } mRegisteredTaskStackChangeListener.forEach(TaskRestartedDuringLaunchListener::unregister); diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchAnimatorHelper.kt b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchAnimatorHelper.kt index 1438edf8ef..a9e5145530 100644 --- a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchAnimatorHelper.kt +++ b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchAnimatorHelper.kt @@ -93,7 +93,7 @@ class DesktopAppLaunchAnimatorHelper( } if (trampolineCloseChange != null) { val trampolineCloseAnimator = - createTrampolineCloseAnimator(trampolineCloseChange, transaction) + createTrampolineCloseAnimator(trampolineCloseChange, transaction, finishCallback) animatorsList.add(trampolineCloseAnimator) } return animatorsList @@ -112,7 +112,7 @@ class DesktopAppLaunchAnimatorHelper( private fun getTrampolineCloseChange(info: TransitionInfo): Change? { if ( info.changes.size < 2 || - !DesktopModeFlags.ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX.isTrue + !DesktopModeFlags.ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX.isTrue ) { return null } @@ -194,13 +194,22 @@ class DesktopAppLaunchAnimatorHelper( ) } - private fun createTrampolineCloseAnimator(change: Change, transaction: Transaction): Animator { + private fun createTrampolineCloseAnimator( + change: Change, + transaction: Transaction, + onAnimFinish: (Animator) -> Unit, + ): Animator { return ValueAnimator.ofFloat(1f, 0f).apply { duration = 100L interpolator = Interpolators.LINEAR addUpdateListener { animation -> transaction.setAlpha(change.leash, animation.animatedValue as Float).apply() } + addListener( + onEnd = { animation -> + onAnimFinish(animation) + } + ) } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index de91c548a7..e41b2d2748 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -139,6 +139,7 @@ import com.android.launcher3.taskbar.bubbles.stashing.PersistentBubbleStashContr import com.android.launcher3.taskbar.bubbles.stashing.TransientBubbleStashController; import com.android.launcher3.taskbar.customization.TaskbarFeatureEvaluator; import com.android.launcher3.taskbar.customization.TaskbarSpecsEvaluator; +import com.android.launcher3.taskbar.growth.NudgeController; import com.android.launcher3.taskbar.navbutton.NearestTouchFrame; import com.android.launcher3.taskbar.overlay.TaskbarOverlayController; import com.android.launcher3.testing.TestLogging; @@ -381,7 +382,8 @@ public class TaskbarActivityContext extends BaseTaskbarContext { new TaskbarPinningController(this), bubbleControllersOptional, new TaskbarDesktopModeController(this, - DesktopVisibilityController.INSTANCE.get(this))); + DesktopVisibilityController.INSTANCE.get(this)), + new NudgeController(this)); mLauncherPrefs = LauncherPrefs.get(this); onViewCreated(); diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java index a2b642353a..9e15a60ed1 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java @@ -25,6 +25,7 @@ import androidx.annotation.VisibleForTesting; import com.android.launcher3.anim.AnimatedFloat; import com.android.launcher3.taskbar.allapps.TaskbarAllAppsController; import com.android.launcher3.taskbar.bubbles.BubbleControllers; +import com.android.launcher3.taskbar.growth.NudgeController; import com.android.launcher3.taskbar.overlay.TaskbarOverlayController; import com.android.systemui.shared.rotation.RotationButtonController; import com.android.wm.shell.shared.bubbles.BubbleBarLocation; @@ -67,6 +68,7 @@ public class TaskbarControllers { public final TaskbarPinningController taskbarPinningController; public final Optional<BubbleControllers> bubbleControllers; public final TaskbarDesktopModeController taskbarDesktopModeController; + public final NudgeController nudgeController; @Nullable private LoggableTaskbarController[] mControllersToLog = null; @Nullable private BackgroundRendererController[] mBackgroundRendererControllers = null; @@ -115,7 +117,8 @@ public class TaskbarControllers { KeyboardQuickSwitchController keyboardQuickSwitchController, TaskbarPinningController taskbarPinningController, Optional<BubbleControllers> bubbleControllers, - TaskbarDesktopModeController taskbarDesktopModeController) { + TaskbarDesktopModeController taskbarDesktopModeController, + NudgeController nudgeController) { this.taskbarActivityContext = taskbarActivityContext; this.taskbarDragController = taskbarDragController; this.navButtonController = navButtonController; @@ -143,6 +146,7 @@ public class TaskbarControllers { this.taskbarPinningController = taskbarPinningController; this.bubbleControllers = bubbleControllers; this.taskbarDesktopModeController = taskbarDesktopModeController; + this.nudgeController = nudgeController; } /** @@ -179,6 +183,7 @@ public class TaskbarControllers { keyboardQuickSwitchController.init(this); taskbarPinningController.init(this, mSharedState); taskbarDesktopModeController.init(this, mSharedState); + nudgeController.init(this); mControllersToLog = new LoggableTaskbarController[] { taskbarDragController, navButtonController, navbarButtonsViewController, @@ -189,6 +194,7 @@ public class TaskbarControllers { voiceInteractionWindowController, taskbarRecentAppsController, taskbarTranslationController, taskbarEduTooltipController, keyboardQuickSwitchController, taskbarPinningController, + nudgeController }; mBackgroundRendererControllers = new BackgroundRendererController[] { taskbarDragLayerController, taskbarScrimViewController, @@ -344,7 +350,7 @@ public class TaskbarControllers { return taskbarActivityContext; } - protected interface LoggableTaskbarController { + public interface LoggableTaskbarController { void dumpLogs(String prefix, PrintWriter pw); } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt index e3e7499c39..b66344413f 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt @@ -63,17 +63,14 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 false, ) as TaskbarDividerPopupView<*> - return taskMenuViewWithArrow.populateForView( - view, - horizontalPosition, - taskbarActivityContext, - ) + return taskMenuViewWithArrow.populateForView(view, horizontalPosition) } } private lateinit var dividerView: View private var horizontalPosition = 0.0f - private lateinit var taskbarActivityContext: TaskbarActivityContext + private val taskbarActivityContext: TaskbarActivityContext = + ActivityContext.lookupContext(context) private val popupCornerRadius = Themes.getDialogCornerRadius(context) private val arrowWidth = resources.getDimension(R.dimen.popup_arrow_width) @@ -82,6 +79,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 private val minPaddingFromScreenEdge = resources.getDimension(R.dimen.taskbar_pinning_popup_menu_min_padding_from_screen_edge) + // TODO: add test for isTransientTaskbar & long presses divider and ensures the popup shows up. private var alwaysShowTaskbarOn = !taskbarActivityContext.isTransientTaskbar private var didPreferenceChange = false private var verticalOffsetForPopupView = @@ -117,7 +115,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 } alwaysShowTaskbarSwitch.setOnClickListener { view -> (view.parent as View).performClick() } - if (ActivityContext.lookupContext<TaskbarActivityContext>(context).isGestureNav) { + if (taskbarActivityContext.isGestureNav) { taskbarSwitchOption.setOnClickListener { alwaysShowTaskbarSwitch.isChecked = !alwaysShowTaskbarOn onClickAlwaysShowTaskbarSwitchOption() @@ -179,13 +177,8 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 return false } - private fun populateForView( - view: View, - horizontalPosition: Float, - taskbar: TaskbarActivityContext, - ): TaskbarDividerPopupView<*> { + private fun populateForView(view: View, horizontalPosition: Float): TaskbarDividerPopupView<*> { dividerView = view - taskbarActivityContext = taskbar this@TaskbarDividerPopupView.horizontalPosition = horizontalPosition tryUpdateBackground() return this diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarAllAppsButtonContainer.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarAllAppsButtonContainer.kt index 82b1295753..bb2acd6362 100644 --- a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarAllAppsButtonContainer.kt +++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarAllAppsButtonContainer.kt @@ -68,7 +68,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) backgroundTintList = ColorStateList.valueOf(TRANSPARENT) setIconDrawable(drawable) - if (activityContext.isTransientTaskbar) { + if (!activityContext.isTransientTaskbar) { setPadding(dpToPx(activityContext.taskbarSpecsEvaluator.taskbarIconPadding.toFloat())) } setForegroundTint(activityContext.getColor(R.color.all_apps_button_color)) @@ -105,7 +105,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 @DimenRes fun getAllAppsButtonTranslationXOffset(isTransientTaskbar: Boolean): Int { - return if (isTransientTaskbar && activityContext.isTransientTaskbar) { + return if (isTransientTaskbar) { R.dimen.transient_taskbar_all_apps_button_translation_x_offset } else { R.dimen.taskbar_all_apps_search_button_translation_x_offset diff --git a/quickstep/src/com/android/launcher3/taskbar/growth/NudgeController.kt b/quickstep/src/com/android/launcher3/taskbar/growth/NudgeController.kt new file mode 100644 index 0000000000..04e41bc3bd --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/growth/NudgeController.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.taskbar.growth + +import android.content.Context +import com.android.launcher3.Utilities +import com.android.launcher3.taskbar.TaskbarActivityContext +import com.android.launcher3.taskbar.TaskbarControllers +import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController +import com.android.launcher3.util.DisplayController +import com.android.launcher3.views.ActivityContext +import java.io.PrintWriter + +/** Controls nudge lifecycles. */ +class NudgeController(context: Context) : LoggableTaskbarController { + + protected val activityContext: TaskbarActivityContext = ActivityContext.lookupContext(context) + + private val isNudgeEnabled: Boolean + get() { + return !Utilities.isRunningInTestHarness() && + !activityContext.isPhoneMode && + !activityContext.isTinyTaskbar + } + + private lateinit var controllers: TaskbarControllers + + fun init(controllers: TaskbarControllers) { + this.controllers = controllers + } + + fun maybeShow(payload: NudgePayload) { + if (!isNudgeEnabled || !DisplayController.isTransientTaskbar(activityContext)) { + return + } + // TODO: b/398033012 - create and show nudge view based on the payload. + } + + /** Closes the current [nudgeView]. */ + fun hide() { + // TODO: b/398033012 - hide the nudge view. + } + + override fun dumpLogs(prefix: String?, pw: PrintWriter?) { + pw?.println(prefix + "NudgeController:") + pw?.println("$prefix\tisNudgeEnabled=$isNudgeEnabled") + } +} diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index cd0a4f312c..806b8ab1cc 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -86,6 +86,7 @@ import android.os.Trace; import android.os.UserHandle; import android.text.TextUtils; import android.util.AttributeSet; +import android.util.Log; import android.view.Display; import android.view.HapticFeedbackConstants; import android.view.KeyEvent; @@ -226,6 +227,7 @@ import java.util.stream.Stream; public class QuickstepLauncher extends Launcher implements RecentsViewContainer, SystemShortcut.BubbleActivityStarter { + private static final String TAG = "QuickstepLauncher"; private static final boolean TRACE_LAYOUTS = SystemProperties.getBoolean("persist.debug.trace_layouts", false); private static final String TRACE_RELAYOUT_CLASS = @@ -561,20 +563,35 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer, @Override public void onDestroy() { + // wrap non-trivial clean up blocks in try-catch to avoid stopping clean up of rest of + // objects + if (mAppTransitionManager != null) { - mAppTransitionManager.onActivityDestroyed(); + try { + mAppTransitionManager.onActivityDestroyed(); + } catch (Exception e) { + Log.e(TAG, "Failed to destroy mAppTransitionManager", e); + } } mAppTransitionManager = null; mIsPredictiveBackToHomeInProgress = false; if (mUnfoldTransitionProgressProvider != null) { - SystemUiProxy.INSTANCE.get(this).setUnfoldAnimationListener(null); - mUnfoldTransitionProgressProvider.destroy(); + try { + SystemUiProxy.INSTANCE.get(this).setUnfoldAnimationListener(null); + mUnfoldTransitionProgressProvider.destroy(); + } catch (Exception e) { + Log.e(TAG, "Failed to destroy mUnfoldTransitionProgressProvider", e); + } } OverviewComponentObserver.INSTANCE.get(this) .removeOverviewChangeListener(mOverviewChangeListener); - mTISBindHelper.onDestroy(); + try { + mTISBindHelper.onDestroy(); + } catch (Exception e) { + Log.e(TAG, "Failed to destroy mTISBindHelper", e); + } if (mLauncherUnfoldAnimationController != null) { mLauncherUnfoldAnimationController.onDestroy(); @@ -584,15 +601,22 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer, mSplitSelectStateController.onDestroy(); } - RecentsView recentsView = getOverviewPanel(); - if (recentsView != null) { - recentsView.destroy(); + try { + RecentsView recentsView = getOverviewPanel(); + if (recentsView != null) { + recentsView.destroy(); + } + } catch (Exception e) { + Log.e(TAG, "Failed to destroy RecentsView", e); } - super.onDestroy(); - mHotseatPredictionController.destroy(); - if (mViewCapture != null) mViewCapture.close(); - removeBackAnimationCallback(mSplitSelectStateController.getSplitBackHandler()); + try { + super.onDestroy(); + } finally { // trivial close operations in finally. + mHotseatPredictionController.destroy(); + if (mViewCapture != null) mViewCapture.close(); + removeBackAnimationCallback(mSplitSelectStateController.getSplitBackHandler()); + } } @Override diff --git a/quickstep/src/com/android/quickstep/util/DesksUtils.kt b/quickstep/src/com/android/quickstep/util/DesksUtils.kt index ecddc3fa63..a10f7711fc 100644 --- a/quickstep/src/com/android/quickstep/util/DesksUtils.kt +++ b/quickstep/src/com/android/quickstep/util/DesksUtils.kt @@ -19,6 +19,7 @@ package com.android.quickstep.util import android.app.TaskInfo import android.content.ComponentName import android.content.res.Resources +import android.util.Log import android.window.DesktopExperienceFlags import com.android.systemui.shared.recents.model.Task @@ -38,8 +39,10 @@ class DesksUtils { task.key.component?.let(::isDesktopWallpaperComponent) == true @JvmStatic - fun isDesktopWallpaperTask(taskInfo: TaskInfo) = - taskInfo.baseIntent.component?.let(::isDesktopWallpaperComponent) == true + fun isDesktopWallpaperTask(taskInfo: TaskInfo): Boolean { + Log.d("b/403118101", "isDesktopWallpaperTask: $taskInfo") + return taskInfo.baseIntent.component?.let(::isDesktopWallpaperComponent) == true + } @JvmStatic fun isDesktopWallpaperComponent(component: ComponentName) = diff --git a/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt b/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt index 4ce18f50a1..c60cd08eb8 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt +++ b/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt @@ -429,8 +429,8 @@ class RecentsDismissUtils(private val recentsView: RecentsView<*, *>) { else -> 1f } * (if (recentsView.isRtl) 1f else -1f) - return (dismissedTaskView.layoutParams.width + recentsView.pageSpacing) * - dismissHorizontalFactor + return (recentsView.pagedOrientationHandler.getPrimarySize(dismissedTaskView) + + recentsView.pageSpacing) * dismissHorizontalFactor } private fun getTasksToReflow( diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt index 26f1197f7e..52d288a723 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt @@ -17,6 +17,7 @@ package com.android.launcher3.taskbar import com.android.launcher3.taskbar.allapps.TaskbarAllAppsController import com.android.launcher3.taskbar.bubbles.BubbleControllers +import com.android.launcher3.taskbar.growth.NudgeController import com.android.launcher3.taskbar.overlay.TaskbarOverlayController import com.android.systemui.shared.rotation.RotationButtonController import java.util.Optional @@ -58,6 +59,7 @@ abstract class TaskbarBaseTestCase { @Mock lateinit var taskbarPinningController: TaskbarPinningController @Mock lateinit var optionalBubbleControllers: Optional<BubbleControllers> @Mock lateinit var taskbarDesktopModeController: TaskbarDesktopModeController + @Mock lateinit var nudgeController: NudgeController lateinit var taskbarControllers: TaskbarControllers @@ -100,6 +102,7 @@ abstract class TaskbarBaseTestCase { taskbarPinningController, optionalBubbleControllers, taskbarDesktopModeController, + nudgeController, ) } } diff --git a/quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchAnimatorHelperTest.kt b/quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchAnimatorHelperTest.kt index 47108e0572..daa77d2a31 100644 --- a/quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchAnimatorHelperTest.kt +++ b/quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchAnimatorHelperTest.kt @@ -32,7 +32,9 @@ import android.util.DisplayMetrics import android.view.SurfaceControl import android.view.WindowManager import android.window.TransitionInfo +import android.window.TransitionInfo.Change import androidx.core.util.Supplier +import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread import com.android.app.animation.Interpolators import com.android.internal.jank.Cuj import com.android.launcher3.desktop.DesktopAppLaunchAnimatorHelper @@ -45,6 +47,7 @@ import org.junit.Rule import org.junit.Test import org.mockito.kotlin.any import org.mockito.kotlin.mock +import org.mockito.kotlin.verify import org.mockito.kotlin.whenever class DesktopAppLaunchAnimatorHelperTest { @@ -70,6 +73,10 @@ class DesktopAppLaunchAnimatorHelperTest { whenever(transactionSupplier.get()).thenReturn(transaction) whenever(transaction.setCrop(any(), any())).thenReturn(transaction) whenever(transaction.setCornerRadius(any(), any())).thenReturn(transaction) + whenever(transaction.setScale(any(), any(), any())).thenReturn(transaction) + whenever(transaction.setPosition(any(), any(), any())).thenReturn(transaction) + whenever(transaction.setAlpha(any(), any())).thenReturn(transaction) + whenever(transaction.setFrameTimeline(any())).thenReturn(transaction) whenever(context.resources).thenReturn(resources) whenever(resources.displayMetrics).thenReturn(DisplayMetrics()) @@ -77,14 +84,8 @@ class DesktopAppLaunchAnimatorHelperTest { } @Test - fun launchTransition_returnsLaunchAnimator() { - val openChange = - TransitionInfo.Change(mock(), mock()).apply { - mode = WindowManager.TRANSIT_OPEN - taskInfo = TASK_INFO_FREEFORM - } - val transitionInfo = TransitionInfo(WindowManager.TRANSIT_NONE, 0) - transitionInfo.addChange(openChange) + fun launchTransition_returnsLaunchAnimator() = runOnUiThread { + val transitionInfo = createTransitionInfo(listOf(OPEN_CHANGE)) val actual = helper.createAnimators(transitionInfo, finishCallback = {}) @@ -93,14 +94,27 @@ class DesktopAppLaunchAnimatorHelperTest { } @Test - fun noLaunchTransition_returnsEmptyAnimatorsList() { + fun launchTransition_callsAnimationEndListener() = runOnUiThread { + val finishCallback = mock<Function1<Animator, Unit>>() + val transitionInfo = createTransitionInfo(listOf(OPEN_CHANGE)) + + val animators = helper.createAnimators(transitionInfo, finishCallback = finishCallback) + + animators.forEach { animator -> + animator.start() + animator.end() + verify(finishCallback).invoke(animator) + } + } + + @Test + fun noLaunchTransition_returnsEmptyAnimatorsList() = runOnUiThread { val pipChange = TransitionInfo.Change(mock(), mock()).apply { mode = WindowManager.TRANSIT_PIP taskInfo = TASK_INFO_FREEFORM } - val transitionInfo = TransitionInfo(WindowManager.TRANSIT_NONE, 0) - transitionInfo.addChange(pipChange) + val transitionInfo = createTransitionInfo(listOf(pipChange)) val actual = helper.createAnimators(transitionInfo, finishCallback = {}) @@ -108,20 +122,8 @@ class DesktopAppLaunchAnimatorHelperTest { } @Test - fun minimizeTransition_returnsLaunchAndMinimizeAnimator() { - val openChange = - TransitionInfo.Change(mock(), mock()).apply { - mode = WindowManager.TRANSIT_OPEN - taskInfo = TASK_INFO_FREEFORM - } - val minimizeChange = - TransitionInfo.Change(mock(), mock()).apply { - mode = WindowManager.TRANSIT_TO_BACK - taskInfo = TASK_INFO_FREEFORM - } - val transitionInfo = TransitionInfo(WindowManager.TRANSIT_NONE, 0) - transitionInfo.addChange(openChange) - transitionInfo.addChange(minimizeChange) + fun minimizeTransition_returnsLaunchAndMinimizeAnimator() = runOnUiThread { + val transitionInfo = createTransitionInfo(listOf(OPEN_CHANGE, MINIMIZE_CHANGE)) val actual = helper.createAnimators(transitionInfo, finishCallback = {}) @@ -131,21 +133,23 @@ class DesktopAppLaunchAnimatorHelperTest { } @Test + fun minimizeTransition_callsAnimationEndListener() = runOnUiThread { + val finishCallback = mock<Function1<Animator, Unit>>() + val transitionInfo = createTransitionInfo(listOf(OPEN_CHANGE, MINIMIZE_CHANGE)) + + val animators = helper.createAnimators(transitionInfo, finishCallback = finishCallback) + + animators.forEach { animator -> + animator.start() + animator.end() + verify(finishCallback).invoke(animator) + } + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX) - fun trampolineTransition_flagEnabled_returnsLaunchAndCloseAnimator() { - val openChange = - TransitionInfo.Change(mock(), mock()).apply { - mode = WindowManager.TRANSIT_OPEN - taskInfo = TASK_INFO_FREEFORM - } - val closeChange = - TransitionInfo.Change(mock(), mock()).apply { - mode = WindowManager.TRANSIT_CLOSE - taskInfo = TASK_INFO_FREEFORM - } - val transitionInfo = TransitionInfo(WindowManager.TRANSIT_NONE, 0) - transitionInfo.addChange(openChange) - transitionInfo.addChange(closeChange) + fun trampolineTransition_flagEnabled_returnsLaunchAndCloseAnimator() = runOnUiThread { + val transitionInfo = createTransitionInfo(listOf(OPEN_CHANGE, CLOSE_CHANGE)) val actual = helper.createAnimators(transitionInfo, finishCallback = {}) @@ -155,21 +159,24 @@ class DesktopAppLaunchAnimatorHelperTest { } @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX) + fun trampolineTransition_flagEnabled_callsAnimationEndListener() = runOnUiThread { + val finishCallback = mock<Function1<Animator, Unit>>() + val transitionInfo = createTransitionInfo(listOf(OPEN_CHANGE, CLOSE_CHANGE)) + + val animators = helper.createAnimators(transitionInfo, finishCallback = finishCallback) + + animators.forEach { animator -> + animator.start() + animator.end() + verify(finishCallback).invoke(animator) + } + } + + @Test @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX) - fun trampolineTransition_flagDisabled_returnsLaunchAnimator() { - val openChange = - TransitionInfo.Change(mock(), mock()).apply { - mode = WindowManager.TRANSIT_OPEN - taskInfo = TASK_INFO_FREEFORM - } - val closeChange = - TransitionInfo.Change(mock(), mock()).apply { - mode = WindowManager.TRANSIT_CLOSE - taskInfo = TASK_INFO_FREEFORM - } - val transitionInfo = TransitionInfo(WindowManager.TRANSIT_NONE, 0) - transitionInfo.addChange(openChange) - transitionInfo.addChange(closeChange) + fun trampolineTransition_flagDisabled_returnsLaunchAnimator() = runOnUiThread { + val transitionInfo = createTransitionInfo(listOf(OPEN_CHANGE, CLOSE_CHANGE)) val actual = helper.createAnimators(transitionInfo, finishCallback = {}) @@ -179,26 +186,9 @@ class DesktopAppLaunchAnimatorHelperTest { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX) - fun trampolineTransition_flagEnabled_hitDesktopWindowLimit_returnsLaunchMinimizeCloseAnimator() { - val openChange = - TransitionInfo.Change(mock(), mock()).apply { - mode = WindowManager.TRANSIT_OPEN - taskInfo = TASK_INFO_FREEFORM - } - val minimizeChange = - TransitionInfo.Change(mock(), mock()).apply { - mode = WindowManager.TRANSIT_TO_BACK - taskInfo = TASK_INFO_FREEFORM - } - val closeChange = - TransitionInfo.Change(mock(), mock()).apply { - mode = WindowManager.TRANSIT_CLOSE - taskInfo = TASK_INFO_FREEFORM - } - val transitionInfo = TransitionInfo(WindowManager.TRANSIT_NONE, 0) - transitionInfo.addChange(openChange) - transitionInfo.addChange(minimizeChange) - transitionInfo.addChange(closeChange) + fun trampolineTransition_flagEnabled_hitDesktopWindowLimit_returnsLaunchMinimizeCloseAnimator() = runOnUiThread { + val transitionInfo = createTransitionInfo( + listOf(OPEN_CHANGE, MINIMIZE_CHANGE, CLOSE_CHANGE)) val actual = helper.createAnimators(transitionInfo, finishCallback = {}) @@ -210,26 +200,9 @@ class DesktopAppLaunchAnimatorHelperTest { @Test @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX) - fun trampolineTransition_flagDisabled_hitDesktopWindowLimit_returnsLaunchMinimizeAnimator() { - val openChange = - TransitionInfo.Change(mock(), mock()).apply { - mode = WindowManager.TRANSIT_OPEN - taskInfo = TASK_INFO_FREEFORM - } - val minimizeChange = - TransitionInfo.Change(mock(), mock()).apply { - mode = WindowManager.TRANSIT_TO_BACK - taskInfo = TASK_INFO_FREEFORM - } - val closeChange = - TransitionInfo.Change(mock(), mock()).apply { - mode = WindowManager.TRANSIT_CLOSE - taskInfo = TASK_INFO_FREEFORM - } - val transitionInfo = TransitionInfo(WindowManager.TRANSIT_NONE, 0) - transitionInfo.addChange(openChange) - transitionInfo.addChange(minimizeChange) - transitionInfo.addChange(closeChange) + fun trampolineTransition_flagDisabled_hitDesktopWindowLimit_returnsLaunchMinimizeAnimator() = runOnUiThread { + val transitionInfo = createTransitionInfo( + listOf(OPEN_CHANGE, MINIMIZE_CHANGE, CLOSE_CHANGE)) val actual = helper.createAnimators(transitionInfo, finishCallback = {}) @@ -280,6 +253,12 @@ class DesktopAppLaunchAnimatorHelperTest { assertThat(animator.duration).isEqualTo(100) } + private fun createTransitionInfo(changes: List<Change>): TransitionInfo { + val transitionInfo = TransitionInfo(WindowManager.TRANSIT_NONE, 0) + changes.forEach { transitionInfo.addChange(it) } + return transitionInfo + } + private companion object { val TASK_INFO_FREEFORM = ActivityManager.RunningTaskInfo().apply { @@ -290,5 +269,23 @@ class DesktopAppLaunchAnimatorHelperTest { configuration.windowConfiguration.windowingMode = WindowConfiguration.WINDOWING_MODE_FREEFORM } + + val OPEN_CHANGE = + TransitionInfo.Change(mock(), mock()).apply { + mode = WindowManager.TRANSIT_OPEN + taskInfo = TASK_INFO_FREEFORM + } + + val CLOSE_CHANGE = + TransitionInfo.Change(mock(), mock()).apply { + mode = WindowManager.TRANSIT_CLOSE + taskInfo = TASK_INFO_FREEFORM + } + + val MINIMIZE_CHANGE = + TransitionInfo.Change(mock(), mock()).apply { + mode = WindowManager.TRANSIT_TO_BACK + taskInfo = TASK_INFO_FREEFORM + } } } diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java index 05dc4a47d0..384f87623a 100644 --- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java +++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java @@ -131,10 +131,10 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator private final float mGapWidth; private final float mCircleGap; private final boolean mIsRtl; - private final VectorDrawable mArrowEnd; - private final VectorDrawable mArrowStart; - private final Rect mArrowEndBounds = new Rect(); - private final Rect mArrowStartBounds = new Rect(); + private final VectorDrawable mArrowRight; + private final VectorDrawable mArrowLeft; + private final Rect mArrowRightBounds = new Rect(); + private final Rect mArrowLeftBounds = new Rect(); private int mNumPages; private int mActivePage; @@ -186,8 +186,8 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator : DOT_GAP_FACTOR * mDotRadius; setOutlineProvider(new MyOutlineProver()); mIsRtl = Utilities.isRtl(getResources()); - mArrowEnd = (VectorDrawable) getResources().getDrawable(R.drawable.ic_chevron_end); - mArrowStart = (VectorDrawable) getResources().getDrawable(R.drawable.ic_chevron_start); + mArrowRight = (VectorDrawable) getResources().getDrawable(R.drawable.ic_chevron_end); + mArrowLeft = (VectorDrawable) getResources().getDrawable(R.drawable.ic_chevron_start); } @Override @@ -548,14 +548,14 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator if (mOnArrowClickListener != null && boundedPosition >= 1) { // Here we draw the Left Arrow - mArrowStart.setAlpha(alpha); + mArrowLeft.setAlpha(alpha); int size = (int) (mGapWidth * 4); - mArrowStartBounds.left = (int) (sTempRect.left - mGapWidth - size); - mArrowStartBounds.top = (int) (y - size / 2); - mArrowStartBounds.right = (int) (sTempRect.left - mGapWidth); - mArrowStartBounds.bottom = (int) (y + size / 2); - mArrowStart.setBounds(mArrowStartBounds); - mArrowStart.draw(canvas); + mArrowLeftBounds.left = (int) (sTempRect.left - mGapWidth - size); + mArrowLeftBounds.top = (int) (y - size / 2); + mArrowLeftBounds.right = (int) (sTempRect.left - mGapWidth); + mArrowLeftBounds.bottom = (int) (y + size / 2); + mArrowLeft.setBounds(mArrowLeftBounds); + mArrowLeft.draw(canvas); } // Here we draw the dots, one at a time from the left-most dot to the right-most dot @@ -609,14 +609,14 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator if (mOnArrowClickListener != null && boundedPosition <= mNumPages - 2) { // Here we draw the Right Arrow - mArrowEnd.setAlpha(alpha); + mArrowRight.setAlpha(alpha); int size = (int) (mGapWidth * 4); - mArrowEndBounds.left = (int) sTempRect.left; - mArrowEndBounds.top = (int) (y - size / 2); - mArrowEndBounds.right = (int) (int) (sTempRect.left + size); - mArrowEndBounds.bottom = (int) (y + size / 2); - mArrowEnd.setBounds(mArrowEndBounds); - mArrowEnd.draw(canvas); + mArrowRightBounds.left = (int) sTempRect.left; + mArrowRightBounds.top = (int) (y - size / 2); + mArrowRightBounds.right = (int) (int) (sTempRect.left + size); + mArrowRightBounds.bottom = (int) (y + size / 2); + mArrowRight.setBounds(mArrowRightBounds); + mArrowRight.draw(canvas); } } else { // Here we draw the dots @@ -640,9 +640,11 @@ public class PageIndicatorDots extends View implements Insettable, PageIndicator public boolean onTouchEvent(MotionEvent ev) { if (mOnArrowClickListener == null) { // No - Op. Don't care about touch events - } else if (withinExpandedBounds(mArrowStartBounds, ev)) { + } else if ((mIsRtl && withinExpandedBounds(mArrowRightBounds, ev)) + || (!mIsRtl && withinExpandedBounds(mArrowLeftBounds, ev))) { mOnArrowClickListener.accept(Direction.START); - } else if (withinExpandedBounds(mArrowEndBounds, ev)) { + } else if ((mIsRtl && withinExpandedBounds(mArrowLeftBounds, ev)) + || (!mIsRtl && withinExpandedBounds(mArrowRightBounds, ev))) { mOnArrowClickListener.accept(Direction.END); } return super.onTouchEvent(ev); |