1 /*
2  * Copyright (C) 2019 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.launcher3.LauncherState.BACKGROUND_APP;
19 import static com.android.launcher3.LauncherState.NORMAL;
20 import static com.android.launcher3.LauncherState.OVERVIEW;
21 import static com.android.launcher3.LauncherState.QUICK_SWITCH;
22 import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
23 import static com.android.launcher3.anim.Interpolators.LINEAR;
24 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
25 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
26 
27 import android.animation.Animator;
28 import android.animation.AnimatorSet;
29 import android.content.Context;
30 import android.graphics.Rect;
31 import android.view.MotionEvent;
32 
33 import androidx.annotation.Nullable;
34 import androidx.annotation.UiThread;
35 
36 import com.android.launcher3.BaseQuickstepLauncher;
37 import com.android.launcher3.DeviceProfile;
38 import com.android.launcher3.Launcher;
39 import com.android.launcher3.LauncherInitListener;
40 import com.android.launcher3.LauncherState;
41 import com.android.launcher3.anim.PendingAnimation;
42 import com.android.launcher3.statehandlers.DepthController;
43 import com.android.launcher3.statehandlers.DepthController.ClampedDepthProperty;
44 import com.android.launcher3.statemanager.StateManager;
45 import com.android.launcher3.taskbar.LauncherTaskbarUIController;
46 import com.android.launcher3.touch.PagedOrientationHandler;
47 import com.android.quickstep.GestureState.GestureEndTarget;
48 import com.android.quickstep.SysUINavigationMode.Mode;
49 import com.android.quickstep.util.ActivityInitListener;
50 import com.android.quickstep.util.AnimatorControllerWithResistance;
51 import com.android.quickstep.util.LayoutUtils;
52 import com.android.quickstep.views.RecentsView;
53 import com.android.systemui.plugins.shared.LauncherOverlayManager;
54 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
55 
56 import java.util.function.Consumer;
57 import java.util.function.Predicate;
58 
59 /**
60  * {@link BaseActivityInterface} for the in-launcher recents.
61  */
62 public final class LauncherActivityInterface extends
63         BaseActivityInterface<LauncherState, BaseQuickstepLauncher> {
64 
65     public static final LauncherActivityInterface INSTANCE = new LauncherActivityInterface();
66 
LauncherActivityInterface()67     private LauncherActivityInterface() {
68         super(true, OVERVIEW, BACKGROUND_APP);
69     }
70 
71     @Override
getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect, PagedOrientationHandler orientationHandler)72     public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect,
73             PagedOrientationHandler orientationHandler) {
74         calculateTaskSize(context, dp, outRect);
75         if (dp.isVerticalBarLayout() && SysUINavigationMode.getMode(context) != Mode.NO_BUTTON) {
76             return dp.isSeascape() ? outRect.left : (dp.widthPx - outRect.right);
77         } else {
78             return LayoutUtils.getShelfTrackingDistance(context, dp, orientationHandler);
79         }
80     }
81 
82     @Override
onSwipeUpToHomeComplete(RecentsAnimationDeviceState deviceState)83     public void onSwipeUpToHomeComplete(RecentsAnimationDeviceState deviceState) {
84         Launcher launcher = getCreatedActivity();
85         if (launcher == null) {
86             return;
87         }
88         // When going to home, the state animator we use has SKIP_OVERVIEW because we assume that
89         // setRecentsAttachedToAppWindow() will handle animating Overview instead. Thus, at the end
90         // of the animation, we should ensure recents is at the correct position for NORMAL state.
91         // For example, when doing a long swipe to home, RecentsView may be scaled down. This is
92         // relatively expensive, so do it on the next frame instead of critical path.
93         MAIN_EXECUTOR.getHandler().post(launcher.getStateManager()::reapplyState);
94 
95         launcher.getRootView().setForceHideBackArrow(false);
96         notifyRecentsOfOrientation(deviceState.getRotationTouchHelper());
97     }
98 
99     @Override
onAssistantVisibilityChanged(float visibility)100     public void onAssistantVisibilityChanged(float visibility) {
101         Launcher launcher = getCreatedActivity();
102         if (launcher == null) {
103             return;
104         }
105         launcher.onAssistantVisibilityChanged(visibility);
106     }
107 
108     @Override
onOneHandedModeStateChanged(boolean activated)109     public void onOneHandedModeStateChanged(boolean activated) {
110         Launcher launcher = getCreatedActivity();
111         if (launcher == null) {
112             return;
113         }
114         launcher.onOneHandedStateChanged(activated);
115     }
116 
117     @Override
prepareRecentsUI(RecentsAnimationDeviceState deviceState, boolean activityVisible, Consumer<AnimatorControllerWithResistance> callback)118     public AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState deviceState,
119             boolean activityVisible, Consumer<AnimatorControllerWithResistance> callback) {
120         notifyRecentsOfOrientation(deviceState.getRotationTouchHelper());
121         DefaultAnimationFactory factory = new DefaultAnimationFactory(callback) {
122             @Override
123             protected void createBackgroundToOverviewAnim(BaseQuickstepLauncher activity,
124                     PendingAnimation pa) {
125                 super.createBackgroundToOverviewAnim(activity, pa);
126 
127                 // Animate the blur and wallpaper zoom
128                 float fromDepthRatio = BACKGROUND_APP.getDepth(activity);
129                 float toDepthRatio = OVERVIEW.getDepth(activity);
130                 pa.addFloat(getDepthController(),
131                         new ClampedDepthProperty(fromDepthRatio, toDepthRatio),
132                         fromDepthRatio, toDepthRatio, LINEAR);
133             }
134         };
135 
136         BaseQuickstepLauncher launcher = factory.initUI();
137         // Since all apps is not visible, we can safely reset the scroll position.
138         // This ensures then the next swipe up to all-apps starts from scroll 0.
139         launcher.getAppsView().reset(false /* animate */);
140         return factory;
141     }
142 
143     @Override
createActivityInitListener(Predicate<Boolean> onInitListener)144     public ActivityInitListener createActivityInitListener(Predicate<Boolean> onInitListener) {
145         return new LauncherInitListener((activity, alreadyOnHome) ->
146                 onInitListener.test(alreadyOnHome));
147     }
148 
149     @Override
setOnDeferredActivityLaunchCallback(Runnable r)150     public void setOnDeferredActivityLaunchCallback(Runnable r) {
151         Launcher launcher = getCreatedActivity();
152         if (launcher == null) {
153             return;
154         }
155         launcher.setOnDeferredActivityLaunchCallback(r);
156     }
157 
158     @Nullable
159     @Override
getCreatedActivity()160     public BaseQuickstepLauncher getCreatedActivity() {
161         return BaseQuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity();
162     }
163 
164     @Nullable
165     @Override
getDepthController()166     public DepthController getDepthController() {
167         BaseQuickstepLauncher launcher = getCreatedActivity();
168         if (launcher == null) {
169             return null;
170         }
171         return launcher.getDepthController();
172     }
173 
174     @Nullable
175     @Override
getTaskbarController()176     public LauncherTaskbarUIController getTaskbarController() {
177         BaseQuickstepLauncher launcher = getCreatedActivity();
178         if (launcher == null) {
179             return null;
180         }
181         return launcher.getTaskbarUIController();
182     }
183 
184     @Nullable
185     @Override
getVisibleRecentsView()186     public RecentsView getVisibleRecentsView() {
187         Launcher launcher = getVisibleLauncher();
188         RecentsView recentsView =
189                 launcher != null && launcher.getStateManager().getState().overviewUi
190                         ? launcher.getOverviewPanel() : null;
191         if (recentsView == null || (!launcher.hasBeenResumed()
192                 && recentsView.getRunningTaskViewId() == -1)) {
193             // If live tile has ended, return null.
194             return null;
195         }
196         return recentsView;
197     }
198 
199     @Nullable
200     @UiThread
getVisibleLauncher()201     private Launcher getVisibleLauncher() {
202         Launcher launcher = getCreatedActivity();
203         return (launcher != null) && launcher.isStarted()
204                 && ((ENABLE_QUICKSTEP_LIVE_TILE.get() && isInLiveTileMode())
205                 || launcher.hasBeenResumed()) ? launcher : null;
206     }
207 
208     @Override
switchToRecentsIfVisible(Runnable onCompleteCallback)209     public boolean switchToRecentsIfVisible(Runnable onCompleteCallback) {
210         Launcher launcher = getVisibleLauncher();
211         if (launcher == null) {
212             return false;
213         }
214         if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isInLiveTileMode()) {
215             RecentsView recentsView = getVisibleRecentsView();
216             if (recentsView == null) {
217                 return false;
218             }
219         }
220 
221         closeOverlay();
222         launcher.getStateManager().goToState(OVERVIEW,
223                 launcher.getStateManager().shouldAnimateStateChange(),
224                 onCompleteCallback == null ? null : forEndCallback(onCompleteCallback));
225         return true;
226     }
227 
228 
229     @Override
onExitOverview(RotationTouchHelper deviceState, Runnable exitRunnable)230     public void onExitOverview(RotationTouchHelper deviceState, Runnable exitRunnable) {
231         final StateManager<LauncherState> stateManager = getCreatedActivity().getStateManager();
232         stateManager.addStateListener(
233                 new StateManager.StateListener<LauncherState>() {
234                     @Override
235                     public void onStateTransitionComplete(LauncherState toState) {
236                         // Are we going from Recents to Workspace?
237                         if (toState == LauncherState.NORMAL) {
238                             exitRunnable.run();
239                             notifyRecentsOfOrientation(deviceState);
240                             stateManager.removeStateListener(this);
241                         }
242                     }
243                 });
244     }
245 
notifyRecentsOfOrientation(RotationTouchHelper rotationTouchHelper)246     private void notifyRecentsOfOrientation(RotationTouchHelper rotationTouchHelper) {
247         // reset layout on swipe to home
248         RecentsView recentsView = getCreatedActivity().getOverviewPanel();
249         recentsView.setLayoutRotation(rotationTouchHelper.getCurrentActiveRotation(),
250                 rotationTouchHelper.getDisplayRotation());
251     }
252 
253     @Override
getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target)254     public Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target) {
255         return homeBounds;
256     }
257 
258     @Override
allowMinimizeSplitScreen()259     public boolean allowMinimizeSplitScreen() {
260         return true;
261     }
262 
263     @Override
isInLiveTileMode()264     public boolean isInLiveTileMode() {
265         Launcher launcher = getCreatedActivity();
266         return launcher != null && launcher.getStateManager().getState() == OVERVIEW &&
267                 launcher.isStarted();
268     }
269 
270     @Override
onLaunchTaskFailed()271     public void onLaunchTaskFailed() {
272         Launcher launcher = getCreatedActivity();
273         if (launcher == null) {
274             return;
275         }
276         launcher.getStateManager().goToState(OVERVIEW);
277     }
278 
279     @Override
closeOverlay()280     public void closeOverlay() {
281         Launcher launcher = getCreatedActivity();
282         if (launcher == null) {
283             return;
284         }
285         LauncherOverlayManager om = launcher.getOverlayManager();
286         if (!launcher.isStarted() || launcher.isForceInvisible()) {
287             om.hideOverlay(false /* animate */);
288         } else {
289             om.hideOverlay(150);
290         }
291         LauncherTaskbarUIController taskbarController = getTaskbarController();
292         if (taskbarController != null) {
293             taskbarController.hideEdu();
294         }
295     }
296 
297     @Override
getParallelAnimationToLauncher(GestureEndTarget endTarget, long duration, RecentsAnimationCallbacks callbacks)298     public @Nullable Animator getParallelAnimationToLauncher(GestureEndTarget endTarget,
299             long duration, RecentsAnimationCallbacks callbacks) {
300         LauncherTaskbarUIController uiController = getTaskbarController();
301         Animator superAnimator = super.getParallelAnimationToLauncher(
302                 endTarget, duration, callbacks);
303         if (uiController == null || callbacks == null) {
304             return superAnimator;
305         }
306         LauncherState toState = stateFromGestureEndTarget(endTarget);
307         Animator taskbarAnimator = uiController.createAnimToLauncher(toState, callbacks, duration);
308         if (superAnimator == null) {
309             return taskbarAnimator;
310         } else {
311             AnimatorSet animatorSet = new AnimatorSet();
312             animatorSet.playTogether(superAnimator, taskbarAnimator);
313             return animatorSet;
314         }
315     }
316 
317     @Override
getOverviewScrimColorForState(BaseQuickstepLauncher launcher, LauncherState state)318     protected int getOverviewScrimColorForState(BaseQuickstepLauncher launcher,
319             LauncherState state) {
320         return state.getWorkspaceScrimColor(launcher);
321     }
322 
323     @Override
deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev)324     public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) {
325         LauncherTaskbarUIController uiController = getTaskbarController();
326         if (uiController == null) {
327             return super.deferStartingActivity(deviceState, ev);
328         }
329         return uiController.isEventOverAnyTaskbarItem(ev);
330     }
331 
332     @Override
shouldCancelCurrentGesture()333     public boolean shouldCancelCurrentGesture() {
334         LauncherTaskbarUIController uiController = getTaskbarController();
335         if (uiController == null) {
336             return super.shouldCancelCurrentGesture();
337         }
338         return uiController.isDraggingItem();
339     }
340 
341     @Override
stateFromGestureEndTarget(GestureEndTarget endTarget)342     public LauncherState stateFromGestureEndTarget(GestureEndTarget endTarget) {
343         switch (endTarget) {
344             case RECENTS:
345                 return OVERVIEW;
346             case NEW_TASK:
347             case LAST_TASK:
348                 return QUICK_SWITCH;
349             case HOME:
350             default:
351                 return NORMAL;
352         }
353     }
354 }
355