• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.quickstep;
17 
18 import static com.android.app.animation.Interpolators.INSTANT;
19 import static com.android.app.animation.Interpolators.LINEAR;
20 import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR;
21 import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
22 import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
23 
24 import android.animation.Animator;
25 import android.animation.ObjectAnimator;
26 import android.content.Context;
27 import android.content.res.Resources;
28 import android.graphics.Color;
29 import android.graphics.PointF;
30 import android.graphics.Rect;
31 import android.view.Gravity;
32 import android.view.MotionEvent;
33 import android.view.RemoteAnimationTarget;
34 import android.view.View;
35 
36 import androidx.annotation.Nullable;
37 import androidx.annotation.UiThread;
38 
39 import com.android.launcher3.DeviceProfile;
40 import com.android.launcher3.Flags;
41 import com.android.launcher3.R;
42 import com.android.launcher3.statehandlers.DesktopVisibilityController;
43 import com.android.launcher3.statemanager.BaseState;
44 import com.android.launcher3.statemanager.StatefulContainer;
45 import com.android.launcher3.taskbar.TaskbarUIController;
46 import com.android.launcher3.util.DisplayController;
47 import com.android.launcher3.util.WindowBounds;
48 import com.android.launcher3.views.ScrimView;
49 import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
50 import com.android.quickstep.util.AnimatorControllerWithResistance;
51 import com.android.quickstep.util.ContextInitListener;
52 import com.android.quickstep.views.RecentsView;
53 import com.android.quickstep.views.RecentsViewContainer;
54 import com.android.systemui.shared.recents.model.ThumbnailData;
55 
56 import java.util.HashMap;
57 import java.util.List;
58 import java.util.function.Consumer;
59 import java.util.function.Predicate;
60 
61 public abstract class BaseContainerInterface<STATE_TYPE extends BaseState<STATE_TYPE>,
62         CONTAINER_TYPE extends RecentsViewContainer & StatefulContainer<STATE_TYPE>> {
63 
64     public boolean rotationSupportedByActivity = false;
65     protected final STATE_TYPE mBackgroundState;
66 
BaseContainerInterface(STATE_TYPE backgroundState)67     protected BaseContainerInterface(STATE_TYPE backgroundState) {
68         mBackgroundState = backgroundState;
69     }
70 
71     @UiThread
72     @Nullable
getVisibleRecentsView()73     public abstract <T extends RecentsView<?,?>> T getVisibleRecentsView();
74 
75     @UiThread
switchToRecentsIfVisible(Animator.AnimatorListener animatorListener)76     public abstract boolean switchToRecentsIfVisible(Animator.AnimatorListener animatorListener);
77 
78     @Nullable
getCreatedContainer()79     public abstract CONTAINER_TYPE getCreatedContainer();
80 
81     @Nullable
82     protected Runnable mOnInitBackgroundStateUICallback = null;
83 
isInLiveTileMode()84     public abstract boolean isInLiveTileMode();
85 
onAssistantVisibilityChanged(float assistantVisibility)86     public abstract void onAssistantVisibilityChanged(float assistantVisibility);
87 
isResumed()88     public abstract boolean isResumed();
89 
isStarted()90     public abstract boolean isStarted();
deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev)91     public abstract boolean deferStartingActivity(RecentsAnimationDeviceState deviceState,
92             MotionEvent ev);
93 
94     /**
95      * Returns the color of the scrim behind overview when at rest in this state.
96      * Return {@link Color#TRANSPARENT} for no scrim.
97      */
getOverviewScrimColorForState(CONTAINER_TYPE container, STATE_TYPE state)98     protected abstract int getOverviewScrimColorForState(CONTAINER_TYPE container,
99             STATE_TYPE state);
100 
getSwipeUpDestinationAndLength( DeviceProfile dp, Context context, Rect outRect, RecentsPagedOrientationHandler orientationHandler)101     public abstract int getSwipeUpDestinationAndLength(
102             DeviceProfile dp, Context context, Rect outRect,
103             RecentsPagedOrientationHandler orientationHandler);
104 
105     @Nullable
getTaskbarController()106     public abstract TaskbarUIController getTaskbarController();
107 
108     public interface AnimationFactory {
109 
createContainerInterface(long transitionLength)110         void createContainerInterface(long transitionLength);
111 
112         /**
113          * @param attached Whether to show RecentsView alongside the app window. If false, recents
114          *                 will be hidden by some property we can animate, e.g. alpha.
115          * @param animate Whether to animate recents to/from its new attached state.
116          * @param updateRunningTaskAlpha Whether to update the running task's attached alpha
117          */
setRecentsAttachedToAppWindow( boolean attached, boolean animate, boolean updateRunningTaskAlpha)118         default void setRecentsAttachedToAppWindow(
119                 boolean attached, boolean animate, boolean updateRunningTaskAlpha) { }
120 
isRecentsAttachedToAppWindow()121         default boolean isRecentsAttachedToAppWindow() {
122             return false;
123         }
124 
hasRecentsEverAttachedToAppWindow()125         default boolean hasRecentsEverAttachedToAppWindow() {
126             return false;
127         }
128 
129         /** Called when the gesture ends and we know what state it is going towards */
setEndTarget(GestureState.GestureEndTarget endTarget)130         default void setEndTarget(GestureState.GestureEndTarget endTarget) { }
131     }
132 
prepareRecentsUI( boolean activityVisible, Consumer<AnimatorControllerWithResistance> callback)133     public abstract BaseContainerInterface.AnimationFactory prepareRecentsUI(
134             boolean activityVisible,
135             Consumer<AnimatorControllerWithResistance> callback);
136 
createActivityInitListener( Predicate<Boolean> onInitListener)137     public abstract ContextInitListener createActivityInitListener(
138             Predicate<Boolean> onInitListener);
139     /**
140      * Returns the expected STATE_TYPE from the provided GestureEndTarget.
141      */
stateFromGestureEndTarget(GestureState.GestureEndTarget endTarget)142     public abstract STATE_TYPE stateFromGestureEndTarget(GestureState.GestureEndTarget endTarget);
143 
switchRunningTaskViewToScreenshot(HashMap<Integer, ThumbnailData> thumbnailDatas, Runnable runnable)144     public abstract void switchRunningTaskViewToScreenshot(HashMap<Integer,
145             ThumbnailData> thumbnailDatas, Runnable runnable);
146 
closeOverlay()147     public abstract void closeOverlay();
148 
getOverviewWindowBounds( Rect homeBounds, RemoteAnimationTarget target)149     public abstract Rect getOverviewWindowBounds(
150             Rect homeBounds, RemoteAnimationTarget target);
151 
onLaunchTaskFailed()152     public abstract void onLaunchTaskFailed();
153 
onExitOverview(Runnable exitRunnable)154     public abstract void onExitOverview(Runnable exitRunnable);
155 
156     /** Called when the animation to home has fully settled. */
onSwipeUpToHomeComplete()157     public void onSwipeUpToHomeComplete() {}
158 
159     /**
160      * Sets a callback to be run when an activity launch happens while launcher is not yet resumed.
161      */
setOnDeferredActivityLaunchCallback(Runnable r)162     public void setOnDeferredActivityLaunchCallback(Runnable r) {}
163     /**
164      * @return Whether the gesture in progress should be cancelled.
165      */
shouldCancelCurrentGesture()166     public boolean shouldCancelCurrentGesture() {
167         return false;
168     }
169 
runOnInitBackgroundStateUI(Runnable callback)170     public void runOnInitBackgroundStateUI(Runnable callback) {
171         StatefulContainer container = getCreatedContainer();
172         if (container != null
173                 && container.getStateManager().getState() == mBackgroundState) {
174             callback.run();
175             onInitBackgroundStateUI();
176             return;
177         }
178         mOnInitBackgroundStateUICallback = callback;
179     }
180 
181     /**
182      * Called when the gesture ends and the animation starts towards the given target. Used to add
183      * an optional additional animation with the same duration.
184      */
getParallelAnimationToGestureEndTarget( GestureState.GestureEndTarget endTarget, long duration, RecentsAnimationCallbacks callbacks)185     public @Nullable Animator getParallelAnimationToGestureEndTarget(
186             GestureState.GestureEndTarget endTarget, long duration,
187             RecentsAnimationCallbacks callbacks) {
188         if (endTarget == RECENTS) {
189             CONTAINER_TYPE container = getCreatedContainer();
190             if (container == null) {
191                 return null;
192             }
193             RecentsView recentsView = container.getOverviewPanel();
194             STATE_TYPE state = stateFromGestureEndTarget(endTarget);
195             ScrimView scrimView = container.getScrimView();
196             ObjectAnimator anim = ObjectAnimator.ofArgb(scrimView, VIEW_BACKGROUND_COLOR,
197                     getOverviewScrimColorForState(container, state));
198             anim.setDuration(duration);
199             anim.setInterpolator(recentsView == null || !recentsView.isKeyboardTaskFocusPending()
200                     ? LINEAR : INSTANT);
201             return anim;
202         }
203         return null;
204     }
205 
206     /**
207      * Called when the animation to the target has finished, but right before updating the state.
208      * @return A View that needs to draw before ending the recents animation to LAST_TASK.
209      * (This is a hack to ensure Taskbar draws its background first to avoid flickering.)
210      */
onSettledOnEndTarget(GestureState.GestureEndTarget endTarget)211     public @Nullable View onSettledOnEndTarget(GestureState.GestureEndTarget endTarget) {
212         TaskbarUIController taskbarUIController = getTaskbarController();
213         if (taskbarUIController != null) {
214             taskbarUIController.setSystemGestureInProgress(false);
215             return taskbarUIController.getRootView();
216         }
217         return null;
218     }
219 
220     /**
221      * Called when the current gesture transition is cancelled.
222      * @param activityVisible Whether the user can see the changes we make here, so try to animate.
223      * @param endTarget If the gesture ended before we got cancelled, where we were headed.
224      */
onTransitionCancelled(boolean activityVisible, @Nullable GestureState.GestureEndTarget endTarget)225     public void onTransitionCancelled(boolean activityVisible,
226             @Nullable GestureState.GestureEndTarget endTarget) {
227         RecentsViewContainer container = getCreatedContainer();
228         if (container == null) {
229             return;
230         }
231         RecentsView recentsView = container.getOverviewPanel();
232         BaseState startState = recentsView.getStateManager().getRestState();
233         if (endTarget != null) {
234             // We were on our way to this state when we got canceled, end there instead.
235             startState = stateFromGestureEndTarget(endTarget);
236             final var context = recentsView.getContext();
237             if (DesktopVisibilityController.INSTANCE.get(context)
238                     .isInDesktopModeAndNotInOverview(context.getDisplayId())
239                     && endTarget == LAST_TASK) {
240                 // When we are cancelling the transition and going back to last task, move to
241                 // rest state instead when desktop tasks are visible.
242                 // If a fullscreen task is visible, launcher goes to normal state when the
243                 // activity is stopped. This does not happen when desktop tasks are visible
244                 // on top of launcher. Force the launcher state to rest state here.
245                 startState = recentsView.getStateManager().getRestState();
246                 // Do not animate the transition
247                 activityVisible = false;
248             }
249         }
250         recentsView.getStateManager().goToState(startState, activityVisible);
251     }
252 
calculateTaskSize(Context context, DeviceProfile dp, Rect outRect, RecentsPagedOrientationHandler orientationHandler)253     public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect,
254             RecentsPagedOrientationHandler orientationHandler) {
255         if (dp.isTablet) {
256             calculateLargeTileSize(context, dp, outRect);
257         } else {
258             Resources res = context.getResources();
259             float maxScale = res.getFloat(R.dimen.overview_max_scale);
260             int taskMargin = dp.overviewTaskMarginPx;
261             // In fake orientation, OverviewActions is hidden and we only leave a margin there.
262             int overviewActionsClaimedSpace = orientationHandler.isLayoutNaturalToLauncher()
263                     ? dp.getOverviewActionsClaimedSpace() : dp.overviewActionsTopMarginPx;
264             calculateTaskSizeInternal(
265                     context,
266                     dp,
267                     dp.overviewTaskThumbnailTopMarginPx,
268                     overviewActionsClaimedSpace,
269                     res.getDimensionPixelSize(R.dimen.overview_minimum_next_prev_size) + taskMargin,
270                     maxScale,
271                     Gravity.CENTER,
272                     outRect,
273                     orientationHandler);
274         }
275     }
276 
calculateLargeTileSize(Context context, DeviceProfile dp, Rect outRect)277     private void calculateLargeTileSize(Context context, DeviceProfile dp, Rect outRect) {
278         Resources res = context.getResources();
279         float maxScale = res.getFloat(R.dimen.overview_max_scale);
280         Rect gridRect = new Rect();
281         calculateGridSize(dp, context, gridRect);
282         calculateTaskSizeInternal(context, dp, gridRect, maxScale, Gravity.CENTER, outRect);
283     }
284 
calculateTaskSizeInternal(Context context, DeviceProfile dp, int claimedSpaceAbove, int claimedSpaceBelow, int minimumHorizontalPadding, float maxScale, int gravity, Rect outRect, RecentsPagedOrientationHandler orientationHandler)285     private void calculateTaskSizeInternal(Context context, DeviceProfile dp, int claimedSpaceAbove,
286             int claimedSpaceBelow, int minimumHorizontalPadding, float maxScale, int gravity,
287             Rect outRect, RecentsPagedOrientationHandler orientationHandler) {
288         Rect potentialTaskRect = new Rect(0, 0, dp.widthPx, dp.heightPx);
289 
290         Rect insets;
291         if (orientationHandler.isLayoutNaturalToLauncher()) {
292             insets = dp.getInsets();
293         } else {
294             Rect portraitInsets = dp.getInsets();
295             DisplayController displayController = DisplayController.INSTANCE.get(context);
296             @Nullable List<WindowBounds> windowBounds =
297                     displayController.getInfo().getCurrentBounds();
298             Rect deviceRotationInsets = windowBounds != null
299                     ? windowBounds.get(orientationHandler.getRotation()).insets
300                     : new Rect();
301             // Obtain the landscape/seascape insets, and rotate it to portrait perspective.
302             orientationHandler.rotateInsets(deviceRotationInsets, outRect);
303             // Then combine with portrait's insets to leave space for status bar/nav bar in
304             // either orientations.
305             outRect.set(
306                     Math.max(outRect.left, portraitInsets.left),
307                     Math.max(outRect.top, portraitInsets.top),
308                     Math.max(outRect.right, portraitInsets.right),
309                     Math.max(outRect.bottom, portraitInsets.bottom)
310             );
311             insets = outRect;
312         }
313         potentialTaskRect.inset(insets);
314 
315         outRect.set(
316                 minimumHorizontalPadding,
317                 claimedSpaceAbove,
318                 minimumHorizontalPadding,
319                 claimedSpaceBelow);
320         // Rotate the paddings to portrait perspective,
321         orientationHandler.rotateInsets(outRect, outRect);
322         potentialTaskRect.inset(outRect);
323 
324         calculateTaskSizeInternal(context, dp, potentialTaskRect, maxScale, gravity, outRect);
325     }
326 
calculateTaskSizeInternal(Context context, DeviceProfile dp, Rect potentialTaskRect, float targetScale, int gravity, Rect outRect)327     private void calculateTaskSizeInternal(Context context, DeviceProfile dp,
328             Rect potentialTaskRect, float targetScale, int gravity, Rect outRect) {
329         PointF taskDimension = getTaskDimension(context, dp);
330 
331         float scale = Math.min(
332                 potentialTaskRect.width() / taskDimension.x,
333                 potentialTaskRect.height() / taskDimension.y);
334         scale = Math.min(scale, targetScale);
335         int outWidth = Math.round(scale * taskDimension.x);
336         int outHeight = Math.round(scale * taskDimension.y);
337 
338         Gravity.apply(gravity, outWidth, outHeight, potentialTaskRect, outRect);
339     }
340 
getTaskDimension(Context context, DeviceProfile dp)341     private static PointF getTaskDimension(Context context, DeviceProfile dp) {
342         PointF dimension = new PointF();
343         getTaskDimension(context, dp, dimension);
344         return dimension;
345     }
346 
347     /**
348      * Gets the dimension of the task in the current system state.
349      */
getTaskDimension(Context context, DeviceProfile dp, PointF out)350     public static void getTaskDimension(Context context, DeviceProfile dp, PointF out) {
351         out.x = dp.widthPx;
352         out.y = dp.heightPx;
353         if (dp.isTablet && !DisplayController.isTransientTaskbar(context)) {
354             out.y -= dp.taskbarHeight;
355         }
356     }
357 
358     /**
359      * Calculates the overview grid size for the provided device configuration.
360      */
calculateGridSize(DeviceProfile dp, Context context, Rect outRect)361     public final void calculateGridSize(DeviceProfile dp, Context context, Rect outRect) {
362         Rect insets = dp.getInsets();
363         int topMargin = dp.overviewTaskThumbnailTopMarginPx;
364         int bottomMargin = dp.getOverviewActionsClaimedSpace();
365         int sideMargin = dp.overviewGridSideMargin;
366 
367         outRect.set(0, 0, dp.widthPx, dp.heightPx);
368         outRect.inset(Math.max(insets.left, sideMargin), insets.top + topMargin,
369                 Math.max(insets.right, sideMargin), Math.max(insets.bottom, bottomMargin));
370     }
371 
372     /**
373      * Calculates the overview grid non-focused task size for the provided device configuration.
374      */
calculateGridTaskSize(Context context, DeviceProfile dp, Rect outRect, RecentsPagedOrientationHandler orientationHandler)375     public final void calculateGridTaskSize(Context context, DeviceProfile dp, Rect outRect,
376             RecentsPagedOrientationHandler orientationHandler) {
377         Resources res = context.getResources();
378         Rect potentialTaskRect = new Rect();
379         calculateLargeTileSize(context, dp, potentialTaskRect);
380 
381         float rowHeight = (potentialTaskRect.height() + dp.overviewTaskThumbnailTopMarginPx
382                 - dp.overviewRowSpacing) / 2f;
383 
384         PointF taskDimension = getTaskDimension(context, dp);
385         float scale = (rowHeight - dp.overviewTaskThumbnailTopMarginPx) / taskDimension.y;
386         int outWidth = Math.round(scale * taskDimension.x);
387         int outHeight = Math.round(scale * taskDimension.y);
388 
389         int gravity = Gravity.TOP;
390         gravity |= orientationHandler.getRecentsRtlSetting(res) ? Gravity.RIGHT : Gravity.LEFT;
391         Gravity.apply(gravity, outWidth, outHeight, potentialTaskRect, outRect);
392     }
393 
394     /**
395      * Calculates the modal taskView size for the provided device configuration
396      */
calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect, RecentsPagedOrientationHandler orientationHandler)397     public final void calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect,
398             RecentsPagedOrientationHandler orientationHandler) {
399         calculateTaskSize(context, dp, outRect, orientationHandler);
400         boolean isGridOnlyOverview = dp.isTablet && Flags.enableGridOnlyOverview();
401         int claimedSpaceBelow = isGridOnlyOverview
402                 ? dp.overviewActionsTopMarginPx + dp.overviewActionsHeight + dp.stashedTaskbarHeight
403                 : (dp.heightPx - outRect.bottom - dp.getInsets().bottom);
404         int minimumHorizontalPadding = 0;
405         if (!isGridOnlyOverview) {
406             float maxScale = context.getResources().getFloat(R.dimen.overview_modal_max_scale);
407             minimumHorizontalPadding =
408                     Math.round((dp.availableWidthPx - outRect.width() * maxScale) / 2);
409         }
410         calculateTaskSizeInternal(
411                 context,
412                 dp,
413                 dp.overviewTaskMarginPx,
414                 claimedSpaceBelow,
415                 minimumHorizontalPadding,
416                 1f /*maxScale*/,
417                 Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM,
418                 outRect,
419                 orientationHandler);
420     }
421 
onInitBackgroundStateUI()422     protected void onInitBackgroundStateUI() {
423         if (mOnInitBackgroundStateUICallback != null) {
424             mOnInitBackgroundStateUICallback.run();
425             mOnInitBackgroundStateUICallback = null;
426         }
427     }
428 }
429