• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
19 import static com.android.launcher3.LauncherState.FAST_OVERVIEW;
20 import static com.android.launcher3.LauncherState.OVERVIEW;
21 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
22 import static com.android.launcher3.anim.Interpolators.LINEAR;
23 import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
24 import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB;
25 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_BACK;
26 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_ROTATION;
27 
28 import android.animation.AnimatorSet;
29 import android.animation.ObjectAnimator;
30 import android.annotation.TargetApi;
31 import android.app.ActivityManager.RunningTaskInfo;
32 import android.content.ComponentName;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.graphics.Rect;
36 import android.os.Build;
37 import android.os.Handler;
38 import android.os.Looper;
39 import android.support.annotation.Nullable;
40 import android.support.annotation.UiThread;
41 import android.view.View;
42 
43 import com.android.launcher3.BaseDraggingActivity;
44 import com.android.launcher3.DeviceProfile;
45 import com.android.launcher3.Launcher;
46 import com.android.launcher3.LauncherAppState;
47 import com.android.launcher3.LauncherInitListener;
48 import com.android.launcher3.LauncherState;
49 import com.android.launcher3.R;
50 import com.android.launcher3.allapps.AllAppsTransitionController;
51 import com.android.launcher3.allapps.DiscoveryBounce;
52 import com.android.launcher3.anim.AnimatorPlaybackController;
53 import com.android.launcher3.dragndrop.DragLayer;
54 import com.android.launcher3.uioverrides.FastOverviewState;
55 import com.android.launcher3.userevent.nano.LauncherLogProto;
56 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
57 import com.android.quickstep.TouchConsumer.InteractionType;
58 import com.android.quickstep.util.LayoutUtils;
59 import com.android.quickstep.util.TransformedRect;
60 import com.android.quickstep.util.RemoteAnimationProvider;
61 import com.android.quickstep.util.RemoteAnimationTargetSet;
62 import com.android.quickstep.views.LauncherLayoutListener;
63 import com.android.quickstep.views.LauncherRecentsView;
64 import com.android.quickstep.views.RecentsView;
65 import com.android.quickstep.views.RecentsViewContainer;
66 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
67 
68 import java.util.Objects;
69 import java.util.function.BiPredicate;
70 import java.util.function.Consumer;
71 
72 /**
73  * Utility class which abstracts out the logical differences between Launcher and RecentsActivity.
74  */
75 @TargetApi(Build.VERSION_CODES.P)
76 public interface ActivityControlHelper<T extends BaseDraggingActivity> {
77 
createLayoutListener(T activity)78     LayoutListener createLayoutListener(T activity);
79 
80     /**
81      * Updates the UI to indicate quick interaction.
82      */
onQuickInteractionStart(T activity, @Nullable RunningTaskInfo taskInfo, boolean activityVisible)83     void onQuickInteractionStart(T activity, @Nullable RunningTaskInfo taskInfo,
84             boolean activityVisible);
85 
getTranslationYForQuickScrub(TransformedRect targetRect, DeviceProfile dp, Context context)86     float getTranslationYForQuickScrub(TransformedRect targetRect, DeviceProfile dp,
87             Context context);
88 
executeOnWindowAvailable(T activity, Runnable action)89     void executeOnWindowAvailable(T activity, Runnable action);
90 
onTransitionCancelled(T activity, boolean activityVisible)91     void onTransitionCancelled(T activity, boolean activityVisible);
92 
getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, @InteractionType int interactionType, TransformedRect outRect)93     int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context,
94             @InteractionType int interactionType, TransformedRect outRect);
95 
onSwipeUpComplete(T activity)96     void onSwipeUpComplete(T activity);
97 
prepareRecentsUI(T activity, boolean activityVisible, Consumer<AnimatorPlaybackController> callback)98     AnimationFactory prepareRecentsUI(T activity, boolean activityVisible,
99             Consumer<AnimatorPlaybackController> callback);
100 
createActivityInitListener(BiPredicate<T, Boolean> onInitListener)101     ActivityInitListener createActivityInitListener(BiPredicate<T, Boolean> onInitListener);
102 
103     @Nullable
getCreatedActivity()104     T getCreatedActivity();
105 
106     @UiThread
107     @Nullable
getVisibleRecentsView()108     RecentsView getVisibleRecentsView();
109 
110     @UiThread
switchToRecentsIfVisible(boolean fromRecentsButton)111     boolean switchToRecentsIfVisible(boolean fromRecentsButton);
112 
getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target)113     Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target);
114 
shouldMinimizeSplitScreen()115     boolean shouldMinimizeSplitScreen();
116 
117     /**
118      * @return {@code true} if recents activity should be started immediately on touchDown,
119      *         {@code false} if it should deferred until some threshold is crossed.
120      */
deferStartingActivity(int downHitTarget)121     boolean deferStartingActivity(int downHitTarget);
122 
supportsLongSwipe(T activity)123     boolean supportsLongSwipe(T activity);
124 
getAlphaProperty(T activity)125     AlphaProperty getAlphaProperty(T activity);
126 
127     /**
128      * Must return a non-null controller is supportsLongSwipe was true.
129      */
getLongSwipeController(T activity, RemoteAnimationTargetSet targetSet)130     LongSwipeHelper getLongSwipeController(T activity, RemoteAnimationTargetSet targetSet);
131 
132     /**
133      * Used for containerType in {@link com.android.launcher3.logging.UserEventDispatcher}
134      */
getContainerType()135     int getContainerType();
136 
137     class LauncherActivityControllerHelper implements ActivityControlHelper<Launcher> {
138 
139         @Override
createLayoutListener(Launcher activity)140         public LayoutListener createLayoutListener(Launcher activity) {
141             return new LauncherLayoutListener(activity);
142         }
143 
144         @Override
onQuickInteractionStart(Launcher activity, RunningTaskInfo taskInfo, boolean activityVisible)145         public void onQuickInteractionStart(Launcher activity, RunningTaskInfo taskInfo,
146                 boolean activityVisible) {
147             LauncherState fromState = activity.getStateManager().getState();
148             activity.getStateManager().goToState(FAST_OVERVIEW, activityVisible);
149 
150             QuickScrubController controller = activity.<RecentsView>getOverviewPanel()
151                     .getQuickScrubController();
152             controller.onQuickScrubStart(activityVisible && !fromState.overviewUi, this);
153         }
154 
155         @Override
getTranslationYForQuickScrub(TransformedRect targetRect, DeviceProfile dp, Context context)156         public float getTranslationYForQuickScrub(TransformedRect targetRect, DeviceProfile dp,
157                 Context context) {
158             // The padding calculations are exactly same as that of RecentsView.setInsets
159             int topMargin = context.getResources()
160                     .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
161             int paddingTop = targetRect.rect.top - topMargin - dp.getInsets().top;
162             int paddingBottom = dp.availableHeightPx + dp.getInsets().top - targetRect.rect.bottom;
163 
164             return FastOverviewState.OVERVIEW_TRANSLATION_FACTOR * (paddingBottom - paddingTop);
165         }
166 
167         @Override
executeOnWindowAvailable(Launcher activity, Runnable action)168         public void executeOnWindowAvailable(Launcher activity, Runnable action) {
169             activity.getWorkspace().runOnOverlayHidden(action);
170         }
171 
172         @Override
getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, @InteractionType int interactionType, TransformedRect outRect)173         public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context,
174                 @InteractionType int interactionType, TransformedRect outRect) {
175             LayoutUtils.calculateLauncherTaskSize(context, dp, outRect.rect);
176             if (interactionType == INTERACTION_QUICK_SCRUB) {
177                 outRect.scale = FastOverviewState.getOverviewScale(dp, outRect.rect, context);
178             }
179             if (dp.isVerticalBarLayout()) {
180                 Rect targetInsets = dp.getInsets();
181                 int hotseatInset = dp.isSeascape() ? targetInsets.left : targetInsets.right;
182                 return dp.hotseatBarSizePx + dp.hotseatBarSidePaddingPx + hotseatInset;
183             } else {
184                 return dp.heightPx - outRect.rect.bottom;
185             }
186         }
187 
188         @Override
onTransitionCancelled(Launcher activity, boolean activityVisible)189         public void onTransitionCancelled(Launcher activity, boolean activityVisible) {
190             LauncherState startState = activity.getStateManager().getRestState();
191             activity.getStateManager().goToState(startState, activityVisible);
192         }
193 
194         @Override
onSwipeUpComplete(Launcher activity)195         public void onSwipeUpComplete(Launcher activity) {
196             // Re apply state in case we did something funky during the transition.
197             activity.getStateManager().reapplyState();
198             DiscoveryBounce.showForOverviewIfNeeded(activity);
199         }
200 
201         @Override
prepareRecentsUI(Launcher activity, boolean activityVisible, Consumer<AnimatorPlaybackController> callback)202         public AnimationFactory prepareRecentsUI(Launcher activity, boolean activityVisible,
203                 Consumer<AnimatorPlaybackController> callback) {
204             final LauncherState startState = activity.getStateManager().getState();
205 
206             LauncherState resetState = startState;
207             if (startState.disableRestore) {
208                 resetState = activity.getStateManager().getRestState();
209             }
210             activity.getStateManager().setRestState(resetState);
211 
212             if (!activityVisible) {
213                 // Since the launcher is not visible, we can safely reset the scroll position.
214                 // This ensures then the next swipe up to all-apps starts from scroll 0.
215                 activity.getAppsView().reset(false /* animate */);
216                 activity.getStateManager().goToState(OVERVIEW, false);
217 
218                 // Optimization, hide the all apps view to prevent layout while initializing
219                 activity.getAppsView().getContentView().setVisibility(View.GONE);
220             }
221 
222             return new AnimationFactory() {
223                 @Override
224                 public void createActivityController(long transitionLength,
225                         @InteractionType int interactionType) {
226                     createActivityControllerInternal(activity, activityVisible, startState,
227                             transitionLength, interactionType, callback);
228                 }
229 
230                 @Override
231                 public void onTransitionCancelled() {
232                     activity.getStateManager().goToState(startState, false /* animate */);
233                 }
234             };
235         }
236 
createActivityControllerInternal(Launcher activity, boolean wasVisible, LauncherState startState, long transitionLength, @InteractionType int interactionType, Consumer<AnimatorPlaybackController> callback)237         private void createActivityControllerInternal(Launcher activity, boolean wasVisible,
238                 LauncherState startState, long transitionLength,
239                 @InteractionType int interactionType,
240                 Consumer<AnimatorPlaybackController> callback) {
241             LauncherState endState = interactionType == INTERACTION_QUICK_SCRUB
242                     ? FAST_OVERVIEW : OVERVIEW;
243             if (wasVisible) {
244                 DeviceProfile dp = activity.getDeviceProfile();
245                 long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
246                 activity.getStateManager().goToState(startState, false);
247                 callback.accept(activity.getStateManager()
248                         .createAnimationToNewWorkspace(endState, accuracy));
249                 return;
250             }
251 
252             if (activity.getDeviceProfile().isVerticalBarLayout()) {
253                 return;
254             }
255 
256             AllAppsTransitionController controller = activity.getAllAppsController();
257             AnimatorSet anim = new AnimatorSet();
258 
259             float scrollRange = Math.max(controller.getShiftRange(), 1);
260             float progressDelta = (transitionLength / scrollRange);
261 
262             float endProgress = endState.getVerticalProgress(activity);
263             float startProgress = endProgress + progressDelta;
264             ObjectAnimator shiftAnim = ObjectAnimator.ofFloat(
265                     controller, ALL_APPS_PROGRESS, startProgress, endProgress);
266             shiftAnim.setInterpolator(LINEAR);
267             anim.play(shiftAnim);
268 
269             anim.setDuration(transitionLength * 2);
270             activity.getStateManager().setCurrentAnimation(anim);
271             callback.accept(AnimatorPlaybackController.wrap(anim, transitionLength * 2));
272         }
273 
274         @Override
createActivityInitListener( BiPredicate<Launcher, Boolean> onInitListener)275         public ActivityInitListener createActivityInitListener(
276                 BiPredicate<Launcher, Boolean> onInitListener) {
277             return new LauncherInitListener(onInitListener);
278         }
279 
280         @Nullable
281         @Override
getCreatedActivity()282         public Launcher getCreatedActivity() {
283             LauncherAppState app = LauncherAppState.getInstanceNoCreate();
284             if (app == null) {
285                 return null;
286             }
287             return (Launcher) app.getModel().getCallback();
288         }
289 
290         @Nullable
291         @UiThread
getVisibleLaucher()292         private Launcher getVisibleLaucher() {
293             Launcher launcher = getCreatedActivity();
294             return (launcher != null) && launcher.isStarted() && launcher.hasWindowFocus() ?
295                     launcher : null;
296         }
297 
298         @Nullable
299         @Override
getVisibleRecentsView()300         public RecentsView getVisibleRecentsView() {
301             Launcher launcher = getVisibleLaucher();
302             return launcher != null && launcher.getStateManager().getState().overviewUi
303                     ? launcher.getOverviewPanel() : null;
304         }
305 
306         @Override
switchToRecentsIfVisible(boolean fromRecentsButton)307         public boolean switchToRecentsIfVisible(boolean fromRecentsButton) {
308             Launcher launcher = getVisibleLaucher();
309             if (launcher != null) {
310                 if (fromRecentsButton) {
311                     launcher.getUserEventDispatcher().logActionCommand(
312                             LauncherLogProto.Action.Command.RECENTS_BUTTON,
313                             getContainerType(),
314                             LauncherLogProto.ContainerType.TASKSWITCHER);
315                 }
316                 launcher.getStateManager().goToState(OVERVIEW);
317                 return true;
318             }
319             return false;
320         }
321 
322         @Override
deferStartingActivity(int downHitTarget)323         public boolean deferStartingActivity(int downHitTarget) {
324             return downHitTarget == HIT_TARGET_BACK || downHitTarget == HIT_TARGET_ROTATION;
325         }
326 
327         @Override
getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target)328         public Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target) {
329             return homeBounds;
330         }
331 
332         @Override
shouldMinimizeSplitScreen()333         public boolean shouldMinimizeSplitScreen() {
334             return true;
335         }
336 
337         @Override
supportsLongSwipe(Launcher activity)338         public boolean supportsLongSwipe(Launcher activity) {
339             return !activity.getDeviceProfile().isVerticalBarLayout();
340         }
341 
342         @Override
getLongSwipeController(Launcher activity, RemoteAnimationTargetSet targetSet)343         public LongSwipeHelper getLongSwipeController(Launcher activity,
344                 RemoteAnimationTargetSet targetSet) {
345             if (activity.getDeviceProfile().isVerticalBarLayout()) {
346                 return null;
347             }
348             return new LongSwipeHelper(activity, targetSet);
349         }
350 
351         @Override
getAlphaProperty(Launcher activity)352         public AlphaProperty getAlphaProperty(Launcher activity) {
353             return activity.getDragLayer().getAlphaProperty(DragLayer.ALPHA_INDEX_SWIPE_UP);
354         }
355 
356         @Override
getContainerType()357         public int getContainerType() {
358             final Launcher launcher = getVisibleLaucher();
359             return launcher != null ? launcher.getStateManager().getState().containerType
360                     : LauncherLogProto.ContainerType.APP;
361         }
362     }
363 
364     class FallbackActivityControllerHelper implements ActivityControlHelper<RecentsActivity> {
365 
366         private final ComponentName mHomeComponent;
367         private final Handler mUiHandler = new Handler(Looper.getMainLooper());
368 
369         public FallbackActivityControllerHelper(ComponentName homeComponent) {
370             mHomeComponent = homeComponent;
371         }
372 
373         @Override
374         public void onQuickInteractionStart(RecentsActivity activity, RunningTaskInfo taskInfo,
375                 boolean activityVisible) {
376             QuickScrubController controller = activity.<RecentsView>getOverviewPanel()
377                     .getQuickScrubController();
378 
379             // TODO: match user is as well
380             boolean startingFromHome = !activityVisible &&
381                     (taskInfo == null || Objects.equals(taskInfo.topActivity, mHomeComponent));
382             controller.onQuickScrubStart(startingFromHome, this);
383             if (activityVisible) {
384                 mUiHandler.postDelayed(controller::onFinishedTransitionToQuickScrub,
385                         OVERVIEW_TRANSITION_MS);
386             }
387         }
388 
389         @Override
390         public float getTranslationYForQuickScrub(TransformedRect targetRect, DeviceProfile dp,
391                 Context context) {
392             return 0;
393         }
394 
395         @Override
396         public void executeOnWindowAvailable(RecentsActivity activity, Runnable action) {
397             action.run();
398         }
399 
400         @Override
401         public void onTransitionCancelled(RecentsActivity activity, boolean activityVisible) {
402             // TODO:
403         }
404 
405         @Override
406         public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context,
407                 @InteractionType int interactionType, TransformedRect outRect) {
408             LayoutUtils.calculateFallbackTaskSize(context, dp, outRect.rect);
409             if (dp.isVerticalBarLayout()) {
410                 Rect targetInsets = dp.getInsets();
411                 int hotseatInset = dp.isSeascape() ? targetInsets.left : targetInsets.right;
412                 return dp.hotseatBarSizePx + dp.hotseatBarSidePaddingPx + hotseatInset;
413             } else {
414                 return dp.heightPx - outRect.rect.bottom;
415             }
416         }
417 
418         @Override
419         public void onSwipeUpComplete(RecentsActivity activity) {
420             // TODO:
421         }
422 
423         @Override
424         public AnimationFactory prepareRecentsUI(RecentsActivity activity, boolean activityVisible,
425                 Consumer<AnimatorPlaybackController> callback) {
426             if (activityVisible) {
427                 return (transitionLength, interactionType) -> { };
428             }
429 
430             RecentsViewContainer rv = activity.getOverviewPanelContainer();
431             rv.setContentAlpha(0);
432 
433             return new AnimationFactory() {
434 
435                 boolean isAnimatingHome = false;
436 
437                 @Override
438                 public void onRemoteAnimationReceived(RemoteAnimationTargetSet targets) {
439                     isAnimatingHome = targets != null && targets.isAnimatingHome();
440                     if (!isAnimatingHome) {
441                         rv.setContentAlpha(1);
442                     }
443                     createActivityController(getSwipeUpDestinationAndLength(
444                             activity.getDeviceProfile(), activity, INTERACTION_NORMAL,
445                             new TransformedRect()), INTERACTION_NORMAL);
446                 }
447 
448                 @Override
449                 public void createActivityController(long transitionLength, int interactionType) {
450                     if (!isAnimatingHome) {
451                         return;
452                     }
453 
454                     ObjectAnimator anim = ObjectAnimator
455                             .ofFloat(rv, RecentsViewContainer.CONTENT_ALPHA, 0, 1);
456                     anim.setDuration(transitionLength).setInterpolator(LINEAR);
457                     AnimatorSet animatorSet = new AnimatorSet();
458                     animatorSet.play(anim);
459                     callback.accept(AnimatorPlaybackController.wrap(animatorSet, transitionLength));
460                 }
461             };
462         }
463 
464         @Override
465         public LayoutListener createLayoutListener(RecentsActivity activity) {
466             // We do not change anything as part of layout changes in fallback activity. Return a
467             // default layout listener.
468             return new LayoutListener() {
469                 @Override
470                 public void open() { }
471 
472                 @Override
473                 public void setHandler(WindowTransformSwipeHandler handler) { }
474 
475                 @Override
476                 public void finish() { }
477             };
478         }
479 
480         @Override
481         public ActivityInitListener createActivityInitListener(
482                 BiPredicate<RecentsActivity, Boolean> onInitListener) {
483             return new RecentsActivityTracker(onInitListener);
484         }
485 
486         @Nullable
487         @Override
488         public RecentsActivity getCreatedActivity() {
489             return RecentsActivityTracker.getCurrentActivity();
490         }
491 
492         @Nullable
493         @Override
494         public RecentsView getVisibleRecentsView() {
495             RecentsActivity activity = getCreatedActivity();
496             if (activity != null && activity.hasWindowFocus()) {
497                 return activity.getOverviewPanel();
498             }
499             return null;
500         }
501 
502         @Override
503         public boolean switchToRecentsIfVisible(boolean fromRecentsButton) {
504             return false;
505         }
506 
507         @Override
508         public boolean deferStartingActivity(int downHitTarget) {
509             // Always defer starting the activity when using fallback
510             return true;
511         }
512 
513         @Override
514         public Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target) {
515             // TODO: Remove this once b/77875376 is fixed
516             return target.sourceContainerBounds;
517         }
518 
519         @Override
520         public boolean shouldMinimizeSplitScreen() {
521             // TODO: Remove this once b/77875376 is fixed
522             return false;
523         }
524 
525         @Override
526         public boolean supportsLongSwipe(RecentsActivity activity) {
527             return false;
528         }
529 
530         @Override
531         public LongSwipeHelper getLongSwipeController(RecentsActivity activity,
532                 RemoteAnimationTargetSet targetSet) {
533             return null;
534         }
535 
536         @Override
537         public AlphaProperty getAlphaProperty(RecentsActivity activity) {
538             return activity.getDragLayer().getAlphaProperty(0);
539         }
540 
541         @Override
542         public int getContainerType() {
543             return LauncherLogProto.ContainerType.SIDELOADED_LAUNCHER;
544         }
545     }
546 
547     interface LayoutListener {
548 
549         void open();
550 
551         void setHandler(WindowTransformSwipeHandler handler);
552 
553         void finish();
554     }
555 
556     interface ActivityInitListener {
557 
558         void register();
559 
560         void unregister();
561 
562         void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider,
563                 Context context, Handler handler, long duration);
564     }
565 
566     interface AnimationFactory {
567 
568         default void onRemoteAnimationReceived(RemoteAnimationTargetSet targets) { }
569 
570         void createActivityController(long transitionLength, @InteractionType int interactionType);
571 
572         default void onTransitionCancelled() { }
573     }
574 }
575