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 android.content.Intent.ACTION_PACKAGE_ADDED;
     19 import static android.content.Intent.ACTION_PACKAGE_CHANGED;
     20 import static android.content.Intent.ACTION_PACKAGE_REMOVED;
     21 
     22 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
     23 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
     24 import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
     25 import static com.android.systemui.shared.system.ActivityManagerWrapper
     26         .CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
     27 import static com.android.systemui.shared.system.PackageManagerWrapper
     28         .ACTION_PREFERRED_ACTIVITY_CHANGED;
     29 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
     30 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
     31 
     32 import android.animation.Animator;
     33 import android.animation.AnimatorSet;
     34 import android.animation.ValueAnimator;
     35 import android.annotation.TargetApi;
     36 import android.content.BroadcastReceiver;
     37 import android.content.ComponentName;
     38 import android.content.Context;
     39 import android.content.Intent;
     40 import android.content.IntentFilter;
     41 import android.content.pm.ResolveInfo;
     42 import android.graphics.Rect;
     43 import android.os.Build;
     44 import android.os.PatternMatcher;
     45 import android.os.SystemClock;
     46 import android.util.Log;
     47 import android.view.View;
     48 import android.view.ViewConfiguration;
     49 
     50 import com.android.launcher3.AbstractFloatingView;
     51 import com.android.launcher3.BaseDraggingActivity;
     52 import com.android.launcher3.InvariantDeviceProfile;
     53 import com.android.launcher3.MainThreadExecutor;
     54 import com.android.launcher3.anim.AnimationSuccessListener;
     55 import com.android.launcher3.logging.UserEventDispatcher;
     56 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
     57 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
     58 import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
     59 import com.android.quickstep.ActivityControlHelper.AnimationFactory;
     60 import com.android.quickstep.ActivityControlHelper.FallbackActivityControllerHelper;
     61 import com.android.quickstep.ActivityControlHelper.LauncherActivityControllerHelper;
     62 import com.android.quickstep.util.ClipAnimationHelper;
     63 import com.android.quickstep.util.TransformedRect;
     64 import com.android.quickstep.util.RemoteAnimationTargetSet;
     65 import com.android.quickstep.views.RecentsView;
     66 import com.android.systemui.shared.system.ActivityManagerWrapper;
     67 import com.android.systemui.shared.system.LatencyTrackerCompat;
     68 import com.android.systemui.shared.system.PackageManagerWrapper;
     69 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
     70 import com.android.systemui.shared.system.TransactionCompat;
     71 
     72 import java.util.ArrayList;
     73 
     74 /**
     75  * Helper class to handle various atomic commands for switching between Overview.
     76  */
     77 @TargetApi(Build.VERSION_CODES.P)
     78 public class OverviewCommandHelper {
     79 
     80     private static final long RECENTS_LAUNCH_DURATION = 250;
     81 
     82     private static final String TAG = "OverviewCommandHelper";
     83 
     84     private final Context mContext;
     85     private final ActivityManagerWrapper mAM;
     86     private final RecentsModel mRecentsModel;
     87     private final MainThreadExecutor mMainThreadExecutor;
     88     private final ComponentName mMyHomeComponent;
     89 
     90     private final BroadcastReceiver mUserPreferenceChangeReceiver = new BroadcastReceiver() {
     91         @Override
     92         public void onReceive(Context context, Intent intent) {
     93             initOverviewTargets();
     94         }
     95     };
     96     private final BroadcastReceiver mOtherHomeAppUpdateReceiver = new BroadcastReceiver() {
     97         @Override
     98         public void onReceive(Context context, Intent intent) {
     99             initOverviewTargets();
    100         }
    101     };
    102     private String mUpdateRegisteredPackage;
    103 
    104     public Intent overviewIntent;
    105     public ComponentName overviewComponent;
    106     private ActivityControlHelper mActivityControlHelper;
    107 
    108     private long mLastToggleTime;
    109 
    110     public OverviewCommandHelper(Context context) {
    111         mContext = context;
    112         mAM = ActivityManagerWrapper.getInstance();
    113         mMainThreadExecutor = new MainThreadExecutor();
    114         mRecentsModel = RecentsModel.getInstance(mContext);
    115 
    116         Intent myHomeIntent = new Intent(Intent.ACTION_MAIN)
    117                 .addCategory(Intent.CATEGORY_HOME)
    118                 .setPackage(mContext.getPackageName());
    119         ResolveInfo info = context.getPackageManager().resolveActivity(myHomeIntent, 0);
    120         mMyHomeComponent = new ComponentName(context.getPackageName(), info.activityInfo.name);
    121 
    122         mContext.registerReceiver(mUserPreferenceChangeReceiver,
    123                 new IntentFilter(ACTION_PREFERRED_ACTIVITY_CHANGED));
    124         initOverviewTargets();
    125     }
    126 
    127     private void initOverviewTargets() {
    128         ComponentName defaultHome = PackageManagerWrapper.getInstance()
    129                 .getHomeActivities(new ArrayList<>());
    130 
    131         final String overviewIntentCategory;
    132         if (defaultHome == null || mMyHomeComponent.equals(defaultHome)) {
    133             // User default home is same as out home app. Use Overview integrated in Launcher.
    134             overviewComponent = mMyHomeComponent;
    135             mActivityControlHelper = new LauncherActivityControllerHelper();
    136             overviewIntentCategory = Intent.CATEGORY_HOME;
    137 
    138             if (mUpdateRegisteredPackage != null) {
    139                 // Remove any update listener as we don't care about other packages.
    140                 mContext.unregisterReceiver(mOtherHomeAppUpdateReceiver);
    141                 mUpdateRegisteredPackage = null;
    142             }
    143         } else {
    144             // The default home app is a different launcher. Use the fallback Overview instead.
    145             overviewComponent = new ComponentName(mContext, RecentsActivity.class);
    146             mActivityControlHelper = new FallbackActivityControllerHelper(defaultHome);
    147             overviewIntentCategory = Intent.CATEGORY_DEFAULT;
    148 
    149             // User's default home app can change as a result of package updates of this app (such
    150             // as uninstalling the app or removing the "Launcher" feature in an update).
    151             // Listen for package updates of this app (and remove any previously attached
    152             // package listener).
    153             if (!defaultHome.getPackageName().equals(mUpdateRegisteredPackage)) {
    154                 if (mUpdateRegisteredPackage != null) {
    155                     mContext.unregisterReceiver(mOtherHomeAppUpdateReceiver);
    156                 }
    157 
    158                 mUpdateRegisteredPackage = defaultHome.getPackageName();
    159                 IntentFilter updateReceiver = new IntentFilter(ACTION_PACKAGE_ADDED);
    160                 updateReceiver.addAction(ACTION_PACKAGE_CHANGED);
    161                 updateReceiver.addAction(ACTION_PACKAGE_REMOVED);
    162                 updateReceiver.addDataScheme("package");
    163                 updateReceiver.addDataSchemeSpecificPart(mUpdateRegisteredPackage,
    164                         PatternMatcher.PATTERN_LITERAL);
    165                 mContext.registerReceiver(mOtherHomeAppUpdateReceiver, updateReceiver);
    166             }
    167         }
    168 
    169         overviewIntent = new Intent(Intent.ACTION_MAIN)
    170                 .addCategory(overviewIntentCategory)
    171                 .setComponent(overviewComponent)
    172                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    173     }
    174 
    175     public void onDestroy() {
    176         mContext.unregisterReceiver(mUserPreferenceChangeReceiver);
    177 
    178         if (mUpdateRegisteredPackage != null) {
    179             mContext.unregisterReceiver(mOtherHomeAppUpdateReceiver);
    180             mUpdateRegisteredPackage = null;
    181         }
    182     }
    183 
    184     public void onOverviewToggle() {
    185         // If currently screen pinning, do not enter overview
    186         if (mAM.isScreenPinningActive()) {
    187             return;
    188         }
    189 
    190         mAM.closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
    191         mMainThreadExecutor.execute(new RecentsActivityCommand<>());
    192     }
    193 
    194     public void onOverviewShown() {
    195         mMainThreadExecutor.execute(new ShowRecentsCommand());
    196     }
    197 
    198     public void onTip(int actionType, int viewType) {
    199         mMainThreadExecutor.execute(new Runnable() {
    200             @Override
    201             public void run() {
    202                 UserEventDispatcher.newInstance(mContext,
    203                         new InvariantDeviceProfile(mContext).getDeviceProfile(mContext))
    204                         .logActionTip(actionType, viewType);
    205             }
    206         });
    207     }
    208 
    209     public ActivityControlHelper getActivityControlHelper() {
    210         return mActivityControlHelper;
    211     }
    212 
    213     private class ShowRecentsCommand extends RecentsActivityCommand {
    214 
    215         @Override
    216         protected boolean handleCommand(long elapsedTime) {
    217             return mHelper.getVisibleRecentsView() != null;
    218         }
    219     }
    220 
    221     private class RecentsActivityCommand<T extends BaseDraggingActivity> implements Runnable {
    222 
    223         protected final ActivityControlHelper<T> mHelper;
    224         private final long mCreateTime;
    225         private final int mRunningTaskId;
    226 
    227         private ActivityInitListener mListener;
    228         private T mActivity;
    229         private RecentsView mRecentsView;
    230         private final long mToggleClickedTime = SystemClock.uptimeMillis();
    231         private boolean mUserEventLogged;
    232 
    233         public RecentsActivityCommand() {
    234             mHelper = getActivityControlHelper();
    235             mCreateTime = SystemClock.elapsedRealtime();
    236             mRunningTaskId = mAM.getRunningTask().id;
    237 
    238             // Preload the plan
    239             mRecentsModel.loadTasks(mRunningTaskId, null);
    240         }
    241 
    242         @Override
    243         public void run() {
    244             long elapsedTime = mCreateTime - mLastToggleTime;
    245             mLastToggleTime = mCreateTime;
    246 
    247             if (!handleCommand(elapsedTime)) {
    248                 // Start overview
    249                 if (!mHelper.switchToRecentsIfVisible(true)) {
    250                     mListener = mHelper.createActivityInitListener(this::onActivityReady);
    251                     mListener.registerAndStartActivity(overviewIntent, this::createWindowAnimation,
    252                             mContext, mMainThreadExecutor.getHandler(), RECENTS_LAUNCH_DURATION);
    253                 }
    254             }
    255         }
    256 
    257         protected boolean handleCommand(long elapsedTime) {
    258             // TODO: We need to fix this case with PIP, when an activity first enters PIP, it shows
    259             //       the menu activity which takes window focus, preventing the right condition from
    260             //       being run below
    261             RecentsView recents = mHelper.getVisibleRecentsView();
    262             if (recents != null) {
    263                 // Launch the next task
    264                 recents.showNextTask();
    265                 return true;
    266             } else if (elapsedTime < ViewConfiguration.getDoubleTapTimeout()) {
    267                 // The user tried to launch back into overview too quickly, either after
    268                 // launching an app, or before overview has actually shown, just ignore for now
    269                 return true;
    270             }
    271             return false;
    272         }
    273 
    274         private boolean onActivityReady(T activity, Boolean wasVisible) {
    275             activity.<RecentsView>getOverviewPanel().setCurrentTask(mRunningTaskId);
    276             AbstractFloatingView.closeAllOpenViews(activity, wasVisible);
    277             AnimationFactory factory = mHelper.prepareRecentsUI(activity, wasVisible,
    278                     (controller) -> {
    279                         controller.dispatchOnStart();
    280                         ValueAnimator anim = controller.getAnimationPlayer()
    281                                 .setDuration(RECENTS_LAUNCH_DURATION);
    282                         anim.setInterpolator(FAST_OUT_SLOW_IN);
    283                         anim.start();
    284                 });
    285             factory.onRemoteAnimationReceived(null);
    286             if (wasVisible) {
    287                 factory.createActivityController(RECENTS_LAUNCH_DURATION, INTERACTION_NORMAL);
    288             }
    289             mActivity = activity;
    290             mRecentsView = mActivity.getOverviewPanel();
    291             mRecentsView.setRunningTaskIconScaledDown(true /* isScaledDown */, false /* animate */);
    292             if (!mUserEventLogged) {
    293                 activity.getUserEventDispatcher().logActionCommand(Action.Command.RECENTS_BUTTON,
    294                         mHelper.getContainerType(), ContainerType.TASKSWITCHER);
    295                 mUserEventLogged = true;
    296             }
    297             return false;
    298         }
    299 
    300         private AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targetCompats) {
    301             if (LatencyTrackerCompat.isEnabled(mContext)) {
    302                 LatencyTrackerCompat.logToggleRecents(
    303                         (int) (SystemClock.uptimeMillis() - mToggleClickedTime));
    304             }
    305 
    306             if (mListener != null) {
    307                 mListener.unregister();
    308             }
    309             AnimatorSet anim = new AnimatorSet();
    310             anim.addListener(new AnimationSuccessListener() {
    311                 @Override
    312                 public void onAnimationSuccess(Animator animator) {
    313                     if (mRecentsView != null) {
    314                         mRecentsView.setRunningTaskIconScaledDown(false /* isScaledDown */,
    315                                 true /* animate */);
    316                     }
    317                 }
    318             });
    319             if (mActivity == null) {
    320                 Log.e(TAG, "Animation created, before activity");
    321                 anim.play(ValueAnimator.ofInt(0, 1).setDuration(100));
    322                 return anim;
    323             }
    324 
    325             RemoteAnimationTargetSet targetSet =
    326                     new RemoteAnimationTargetSet(targetCompats, MODE_CLOSING);
    327 
    328             // Use the top closing app to determine the insets for the animation
    329             RemoteAnimationTargetCompat runningTaskTarget = targetSet.findTask(mRunningTaskId);
    330             if (runningTaskTarget == null) {
    331                 Log.e(TAG, "No closing app");
    332                 anim.play(ValueAnimator.ofInt(0, 1).setDuration(100));
    333                 return anim;
    334             }
    335 
    336             final ClipAnimationHelper clipHelper = new ClipAnimationHelper();
    337 
    338             // At this point, the activity is already started and laid-out. Get the home-bounds
    339             // relative to the screen using the rootView of the activity.
    340             int loc[] = new int[2];
    341             View rootView = mActivity.getRootView();
    342             rootView.getLocationOnScreen(loc);
    343             Rect homeBounds = new Rect(loc[0], loc[1],
    344                     loc[0] + rootView.getWidth(), loc[1] + rootView.getHeight());
    345             clipHelper.updateSource(homeBounds, runningTaskTarget);
    346 
    347             TransformedRect targetRect = new TransformedRect();
    348             mHelper.getSwipeUpDestinationAndLength(mActivity.getDeviceProfile(), mActivity,
    349                     INTERACTION_NORMAL, targetRect);
    350             clipHelper.updateTargetRect(targetRect);
    351             clipHelper.prepareAnimation(false /* isOpening */);
    352 
    353             ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
    354             valueAnimator.setDuration(RECENTS_LAUNCH_DURATION);
    355             valueAnimator.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
    356             valueAnimator.addUpdateListener((v) ->
    357                     clipHelper.applyTransform(targetSet, (float) v.getAnimatedValue()));
    358 
    359             if (targetSet.isAnimatingHome()) {
    360                 // If we are animating home, fade in the opening targets
    361                 RemoteAnimationTargetSet openingSet =
    362                         new RemoteAnimationTargetSet(targetCompats, MODE_OPENING);
    363 
    364                 TransactionCompat transaction = new TransactionCompat();
    365                 valueAnimator.addUpdateListener((v) -> {
    366                     for (RemoteAnimationTargetCompat app : openingSet.apps) {
    367                         transaction.setAlpha(app.leash, (float) v.getAnimatedValue());
    368                     }
    369                     transaction.apply();
    370                 });
    371             }
    372             anim.play(valueAnimator);
    373             return anim;
    374         }
    375     }
    376 }
    377