Home | History | Annotate | Download | only in quickstep
      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 
     78     LayoutListener createLayoutListener(T activity);
     79 
     80     /**
     81      * Updates the UI to indicate quick interaction.
     82      */
     83     void onQuickInteractionStart(T activity, @Nullable RunningTaskInfo taskInfo,
     84             boolean activityVisible);
     85 
     86     float getTranslationYForQuickScrub(TransformedRect targetRect, DeviceProfile dp,
     87             Context context);
     88 
     89     void executeOnWindowAvailable(T activity, Runnable action);
     90 
     91     void onTransitionCancelled(T activity, boolean activityVisible);
     92 
     93     int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context,
     94             @InteractionType int interactionType, TransformedRect outRect);
     95 
     96     void onSwipeUpComplete(T activity);
     97 
     98     AnimationFactory prepareRecentsUI(T activity, boolean activityVisible,
     99             Consumer<AnimatorPlaybackController> callback);
    100 
    101     ActivityInitListener createActivityInitListener(BiPredicate<T, Boolean> onInitListener);
    102 
    103     @Nullable
    104     T getCreatedActivity();
    105 
    106     @UiThread
    107     @Nullable
    108     RecentsView getVisibleRecentsView();
    109 
    110     @UiThread
    111     boolean switchToRecentsIfVisible(boolean fromRecentsButton);
    112 
    113     Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target);
    114 
    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      */
    121     boolean deferStartingActivity(int downHitTarget);
    122 
    123     boolean supportsLongSwipe(T activity);
    124 
    125     AlphaProperty getAlphaProperty(T activity);
    126 
    127     /**
    128      * Must return a non-null controller is supportsLongSwipe was true.
    129      */
    130     LongSwipeHelper getLongSwipeController(T activity, RemoteAnimationTargetSet targetSet);
    131 
    132     /**
    133      * Used for containerType in {@link com.android.launcher3.logging.UserEventDispatcher}
    134      */
    135     int getContainerType();
    136 
    137     class LauncherActivityControllerHelper implements ActivityControlHelper<Launcher> {
    138 
    139         @Override
    140         public LayoutListener createLayoutListener(Launcher activity) {
    141             return new LauncherLayoutListener(activity);
    142         }
    143 
    144         @Override
    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
    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
    168         public void executeOnWindowAvailable(Launcher activity, Runnable action) {
    169             activity.getWorkspace().runOnOverlayHidden(action);
    170         }
    171 
    172         @Override
    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
    189         public void onTransitionCancelled(Launcher activity, boolean activityVisible) {
    190             LauncherState startState = activity.getStateManager().getRestState();
    191             activity.getStateManager().goToState(startState, activityVisible);
    192         }
    193 
    194         @Override
    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
    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 
    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
    275         public ActivityInitListener createActivityInitListener(
    276                 BiPredicate<Launcher, Boolean> onInitListener) {
    277             return new LauncherInitListener(onInitListener);
    278         }
    279 
    280         @Nullable
    281         @Override
    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
    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
    300         public RecentsView getVisibleRecentsView() {
    301             Launcher launcher = getVisibleLaucher();
    302             return launcher != null && launcher.getStateManager().getState().overviewUi
    303                     ? launcher.getOverviewPanel() : null;
    304         }
    305 
    306         @Override
    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
    323         public boolean deferStartingActivity(int downHitTarget) {
    324             return downHitTarget == HIT_TARGET_BACK || downHitTarget == HIT_TARGET_ROTATION;
    325         }
    326 
    327         @Override
    328         public Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target) {
    329             return homeBounds;
    330         }
    331 
    332         @Override
    333         public boolean shouldMinimizeSplitScreen() {
    334             return true;
    335         }
    336 
    337         @Override
    338         public boolean supportsLongSwipe(Launcher activity) {
    339             return !activity.getDeviceProfile().isVerticalBarLayout();
    340         }
    341 
    342         @Override
    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
    352         public AlphaProperty getAlphaProperty(Launcher activity) {
    353             return activity.getDragLayer().getAlphaProperty(DragLayer.ALPHA_INDEX_SWIPE_UP);
    354         }
    355 
    356         @Override
    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