1 /*
2  * Copyright (C) 2018 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.ACCELERATE_2;
19 import static com.android.app.animation.Interpolators.INSTANT;
20 import static com.android.app.animation.Interpolators.LINEAR;
21 import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
22 import static com.android.quickstep.AbsSwipeUpHandler.RECENTS_ATTACH_DURATION;
23 import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
24 import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM;
25 import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_TRANSLATE_X_ANIM;
26 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
27 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
28 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
29 import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
30 
31 import android.animation.Animator;
32 import android.animation.AnimatorListenerAdapter;
33 import android.animation.AnimatorSet;
34 import android.graphics.Color;
35 import android.view.MotionEvent;
36 
37 import androidx.annotation.Nullable;
38 import androidx.annotation.UiThread;
39 
40 import com.android.launcher3.anim.AnimatorPlaybackController;
41 import com.android.launcher3.anim.PendingAnimation;
42 import com.android.launcher3.statehandlers.DepthController;
43 import com.android.launcher3.statehandlers.DesktopVisibilityController;
44 import com.android.launcher3.statemanager.BaseState;
45 import com.android.launcher3.statemanager.StatefulActivity;
46 import com.android.launcher3.taskbar.TaskbarUIController;
47 import com.android.launcher3.util.DisplayController;
48 import com.android.launcher3.util.NavigationMode;
49 import com.android.quickstep.util.AnimatorControllerWithResistance;
50 import com.android.quickstep.views.RecentsView;
51 import com.android.quickstep.views.RecentsViewContainer;
52 import com.android.systemui.shared.recents.model.ThumbnailData;
53 
54 import java.util.HashMap;
55 import java.util.Optional;
56 import java.util.function.Consumer;
57 
58 /**
59  * Utility class which abstracts out the logical differences between Launcher and RecentsActivity.
60  */
61 public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_TYPE>,
62         ACTIVITY_TYPE extends StatefulActivity<STATE_TYPE> & RecentsViewContainer> extends
63         BaseContainerInterface<STATE_TYPE, ACTIVITY_TYPE> {
64     private final STATE_TYPE mBackgroundState;
65 
66     private STATE_TYPE mTargetState;
67 
68     @Nullable private Runnable mOnInitBackgroundStateUICallback = null;
69 
BaseActivityInterface(boolean rotationSupportedByActivity, STATE_TYPE overviewState, STATE_TYPE backgroundState)70     protected BaseActivityInterface(boolean rotationSupportedByActivity,
71             STATE_TYPE overviewState, STATE_TYPE backgroundState) {
72         this.rotationSupportedByActivity = rotationSupportedByActivity;
73         mTargetState = overviewState;
74         mBackgroundState = backgroundState;
75     }
76 
77     /**
78      * Called when the current gesture transition is cancelled.
79      * @param activityVisible Whether the user can see the changes we make here, so try to animate.
80      * @param endTarget If the gesture ended before we got cancelled, where we were headed.
81      */
onTransitionCancelled(boolean activityVisible, @Nullable GestureState.GestureEndTarget endTarget)82     public void onTransitionCancelled(boolean activityVisible,
83             @Nullable GestureState.GestureEndTarget endTarget) {
84         ACTIVITY_TYPE activity = getCreatedContainer();
85         if (activity == null) {
86             return;
87         }
88         STATE_TYPE startState = activity.getStateManager().getRestState();
89         if (endTarget != null) {
90             // We were on our way to this state when we got canceled, end there instead.
91             startState = stateFromGestureEndTarget(endTarget);
92             DesktopVisibilityController controller = getDesktopVisibilityController();
93             if (controller != null && controller.areDesktopTasksVisible()
94                     && endTarget == LAST_TASK) {
95                 // When we are cancelling the transition and going back to last task, move to
96                 // rest state instead when desktop tasks are visible.
97                 // If a fullscreen task is visible, launcher goes to normal state when the
98                 // activity is stopped. This does not happen when desktop tasks are visible
99                 // on top of launcher. Force the launcher state to rest state here.
100                 startState = activity.getStateManager().getRestState();
101                 // Do not animate the transition
102                 activityVisible = false;
103             }
104         }
105         activity.getStateManager().goToState(startState, activityVisible);
106     }
107 
108     @Nullable
getCreatedContainer()109     public abstract ACTIVITY_TYPE getCreatedContainer();
110 
111     @Nullable
getDepthController()112     public DepthController getDepthController() {
113         return null;
114     }
115 
isResumed()116     public final boolean isResumed() {
117         ACTIVITY_TYPE activity = getCreatedContainer();
118         return activity != null && activity.hasBeenResumed();
119     }
120 
isStarted()121     public final boolean isStarted() {
122         ACTIVITY_TYPE activity = getCreatedContainer();
123         return activity != null && activity.isStarted();
124     }
125 
126     @UiThread
127     @Nullable
getVisibleRecentsView()128     public abstract <T extends RecentsView> T getVisibleRecentsView();
129 
130     @UiThread
switchToRecentsIfVisible(Animator.AnimatorListener animatorListener)131     public abstract boolean switchToRecentsIfVisible(Animator.AnimatorListener animatorListener);
132 
deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev)133     public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) {
134         TaskbarUIController controller = getTaskbarController();
135         boolean isEventOverBubbleBarStashHandle =
136                 controller != null && controller.isEventOverBubbleBarStashHandle(ev);
137         return deviceState.isInDeferredGestureRegion(ev) || deviceState.isImeRenderingNavButtons()
138                 || isTrackpadMultiFingerSwipe(ev) || isEventOverBubbleBarStashHandle;
139     }
140 
141     /**
142      * Closes any overlays.
143      */
closeOverlay()144     public void closeOverlay() {
145         Optional.ofNullable(getTaskbarController()).ifPresent(
146                 TaskbarUIController::hideOverlayWindow);
147     }
148 
switchRunningTaskViewToScreenshot(HashMap<Integer, ThumbnailData> thumbnailDatas, Runnable runnable)149     public void switchRunningTaskViewToScreenshot(HashMap<Integer, ThumbnailData> thumbnailDatas,
150             Runnable runnable) {
151         ACTIVITY_TYPE activity = getCreatedContainer();
152         if (activity == null) {
153             return;
154         }
155         RecentsView recentsView = activity.getOverviewPanel();
156         if (recentsView == null) {
157             if (runnable != null) {
158                 runnable.run();
159             }
160             return;
161         }
162         recentsView.switchToScreenshot(thumbnailDatas, runnable);
163     }
164 
165 
runOnInitBackgroundStateUI(Runnable callback)166     protected void runOnInitBackgroundStateUI(Runnable callback) {
167         ACTIVITY_TYPE activity = getCreatedContainer();
168         if (activity != null && activity.getStateManager().getState() == mBackgroundState) {
169             callback.run();
170             onInitBackgroundStateUI();
171             return;
172         }
173         mOnInitBackgroundStateUICallback = callback;
174     }
175 
onInitBackgroundStateUI()176     private void onInitBackgroundStateUI() {
177         if (mOnInitBackgroundStateUICallback != null) {
178             mOnInitBackgroundStateUICallback.run();
179             mOnInitBackgroundStateUICallback = null;
180         }
181     }
182 
183     public interface AnimationFactory {
184 
createActivityInterface(long transitionLength)185         void createActivityInterface(long transitionLength);
186 
187         /**
188          * @param attached Whether to show RecentsView alongside the app window. If false, recents
189          *                 will be hidden by some property we can animate, e.g. alpha.
190          * @param animate Whether to animate recents to/from its new attached state.
191          */
setRecentsAttachedToAppWindow(boolean attached, boolean animate)192         default void setRecentsAttachedToAppWindow(boolean attached, boolean animate) { }
193 
isRecentsAttachedToAppWindow()194         default boolean isRecentsAttachedToAppWindow() {
195             return false;
196         }
197 
hasRecentsEverAttachedToAppWindow()198         default boolean hasRecentsEverAttachedToAppWindow() {
199             return false;
200         }
201 
202         /** Called when the gesture ends and we know what state it is going towards */
setEndTarget(GestureState.GestureEndTarget endTarget)203         default void setEndTarget(GestureState.GestureEndTarget endTarget) { }
204     }
205 
206     class DefaultAnimationFactory implements AnimationFactory {
207 
208         protected final ACTIVITY_TYPE mActivity;
209         private final STATE_TYPE mStartState;
210         private final Consumer<AnimatorControllerWithResistance> mCallback;
211 
212         private boolean mIsAttachedToWindow;
213         private boolean mHasEverAttachedToWindow;
214 
DefaultAnimationFactory(Consumer<AnimatorControllerWithResistance> callback)215         DefaultAnimationFactory(Consumer<AnimatorControllerWithResistance> callback) {
216             mCallback = callback;
217 
218             mActivity = getCreatedContainer();
219             mStartState = mActivity.getStateManager().getState();
220         }
221 
initBackgroundStateUI()222         protected ACTIVITY_TYPE initBackgroundStateUI() {
223             STATE_TYPE resetState = mStartState;
224             if (mStartState.shouldDisableRestore()) {
225                 resetState = mActivity.getStateManager().getRestState();
226             }
227             mActivity.getStateManager().setRestState(resetState);
228             mActivity.getStateManager().goToState(mBackgroundState, false);
229             onInitBackgroundStateUI();
230             return mActivity;
231         }
232 
233         @Override
createActivityInterface(long transitionLength)234         public void createActivityInterface(long transitionLength) {
235             PendingAnimation pa = new PendingAnimation(transitionLength * 2);
236             createBackgroundToOverviewAnim(mActivity, pa);
237             AnimatorPlaybackController controller = pa.createPlaybackController();
238             mActivity.getStateManager().setCurrentUserControlledAnimation(controller);
239 
240             // Since we are changing the start position of the UI, reapply the state, at the end
241             controller.setEndAction(() -> mActivity.getStateManager().goToState(
242                     controller.getInterpolatedProgress() > 0.5 ? mTargetState : mBackgroundState,
243                     /* animated= */ false));
244 
245             RecentsView recentsView = mActivity.getOverviewPanel();
246             AnimatorControllerWithResistance controllerWithResistance =
247                     AnimatorControllerWithResistance.createForRecents(controller, mActivity,
248                             recentsView.getPagedViewOrientedState(), mActivity.getDeviceProfile(),
249                             recentsView, RECENTS_SCALE_PROPERTY, recentsView,
250                             TASK_SECONDARY_TRANSLATION);
251             mCallback.accept(controllerWithResistance);
252 
253             // Creating the activity controller animation sometimes reapplies the launcher state
254             // (because we set the animation as the current state animation), so we reapply the
255             // attached state here as well to ensure recents is shown/hidden appropriately.
256             if (DisplayController.getNavigationMode(mActivity) == NavigationMode.NO_BUTTON) {
257                 setRecentsAttachedToAppWindow(mIsAttachedToWindow, false);
258             }
259         }
260 
261         @Override
setRecentsAttachedToAppWindow(boolean attached, boolean animate)262         public void setRecentsAttachedToAppWindow(boolean attached, boolean animate) {
263             if (mIsAttachedToWindow == attached && animate) {
264                 return;
265             }
266             mActivity.getStateManager()
267                     .cancelStateElementAnimation(INDEX_RECENTS_FADE_ANIM);
268             mActivity.getStateManager()
269                     .cancelStateElementAnimation(INDEX_RECENTS_TRANSLATE_X_ANIM);
270 
271             AnimatorSet animatorSet = new AnimatorSet();
272             animatorSet.addListener(new AnimatorListenerAdapter() {
273                 @Override
274                 public void onAnimationStart(Animator animation) {
275                     super.onAnimationStart(animation);
276                     mIsAttachedToWindow = attached;
277                     if (attached) {
278                         mHasEverAttachedToWindow = true;
279                     }
280                 }});
281 
282             long animationDuration = animate ? RECENTS_ATTACH_DURATION : 0;
283             Animator fadeAnim = mActivity.getStateManager()
284                     .createStateElementAnimation(INDEX_RECENTS_FADE_ANIM, attached ? 1 : 0);
285             fadeAnim.setInterpolator(attached ? INSTANT : ACCELERATE_2);
286             fadeAnim.setDuration(animationDuration);
287             animatorSet.play(fadeAnim);
288 
289             float fromTranslation = ADJACENT_PAGE_HORIZONTAL_OFFSET.get(
290                     mActivity.getOverviewPanel());
291             float toTranslation = attached ? 0 : 1;
292 
293             Animator translationAnimator = mActivity.getStateManager().createStateElementAnimation(
294                     INDEX_RECENTS_TRANSLATE_X_ANIM, fromTranslation, toTranslation);
295             translationAnimator.setDuration(animationDuration);
296             animatorSet.play(translationAnimator);
297             animatorSet.start();
298         }
299 
300         @Override
isRecentsAttachedToAppWindow()301         public boolean isRecentsAttachedToAppWindow() {
302             return mIsAttachedToWindow;
303         }
304 
305         @Override
hasRecentsEverAttachedToAppWindow()306         public boolean hasRecentsEverAttachedToAppWindow() {
307             return mHasEverAttachedToWindow;
308         }
309 
310         @Override
setEndTarget(GestureState.GestureEndTarget endTarget)311         public void setEndTarget(GestureState.GestureEndTarget endTarget) {
312             mTargetState = stateFromGestureEndTarget(endTarget);
313         }
314 
createBackgroundToOverviewAnim(ACTIVITY_TYPE activity, PendingAnimation pa)315         protected void createBackgroundToOverviewAnim(ACTIVITY_TYPE activity, PendingAnimation pa) {
316             //  Scale down recents from being full screen to being in overview.
317             RecentsView recentsView = activity.getOverviewPanel();
318             pa.addFloat(recentsView, RECENTS_SCALE_PROPERTY,
319                     recentsView.getMaxScaleForFullScreen(), 1, LINEAR);
320             pa.addFloat(recentsView, FULLSCREEN_PROGRESS, 1, 0, LINEAR);
321 
322             pa.addListener(new AnimatorListenerAdapter() {
323                 @Override
324                 public void onAnimationStart(Animator animation) {
325                     TaskbarUIController taskbarUIController = getTaskbarController();
326                     if (taskbarUIController != null) {
327                         taskbarUIController.setSystemGestureInProgress(true);
328                     }
329                 }
330             });
331         }
332     }
333 }
334