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.util.Executors.MAIN_EXECUTOR; 19 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; 20 21 import android.animation.Animator; 22 import android.animation.AnimatorListenerAdapter; 23 import android.animation.AnimatorSet; 24 import android.annotation.TargetApi; 25 import android.content.Context; 26 import android.os.Build; 27 import android.os.SystemClock; 28 import android.view.ViewConfiguration; 29 30 import androidx.annotation.BinderThread; 31 32 import com.android.launcher3.appprediction.PredictionUiStateManager; 33 import com.android.launcher3.logging.UserEventDispatcher; 34 import com.android.launcher3.statemanager.StatefulActivity; 35 import com.android.launcher3.userevent.nano.LauncherLogProto; 36 import com.android.quickstep.util.ActivityInitListener; 37 import com.android.quickstep.util.RemoteAnimationProvider; 38 import com.android.quickstep.views.RecentsView; 39 import com.android.quickstep.views.TaskView; 40 import com.android.systemui.shared.system.ActivityManagerWrapper; 41 import com.android.systemui.shared.system.LatencyTrackerCompat; 42 import com.android.systemui.shared.system.RemoteAnimationTargetCompat; 43 44 /** 45 * Helper class to handle various atomic commands for switching between Overview. 46 */ 47 @TargetApi(Build.VERSION_CODES.P) 48 public class OverviewCommandHelper { 49 50 private final Context mContext; 51 private final RecentsAnimationDeviceState mDeviceState; 52 private final RecentsModel mRecentsModel; 53 private final OverviewComponentObserver mOverviewComponentObserver; 54 55 private long mLastToggleTime; 56 OverviewCommandHelper(Context context, RecentsAnimationDeviceState deviceState, OverviewComponentObserver observer)57 public OverviewCommandHelper(Context context, RecentsAnimationDeviceState deviceState, 58 OverviewComponentObserver observer) { 59 mContext = context; 60 mDeviceState = deviceState; 61 mRecentsModel = RecentsModel.INSTANCE.get(mContext); 62 mOverviewComponentObserver = observer; 63 } 64 65 @BinderThread onOverviewToggle()66 public void onOverviewToggle() { 67 // If currently screen pinning, do not enter overview 68 if (mDeviceState.isScreenPinningActive()) { 69 return; 70 } 71 72 ActivityManagerWrapper.getInstance() 73 .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); 74 MAIN_EXECUTOR.execute(new RecentsActivityCommand<>()); 75 } 76 77 @BinderThread onOverviewShown(boolean triggeredFromAltTab)78 public void onOverviewShown(boolean triggeredFromAltTab) { 79 if (triggeredFromAltTab) { 80 ActivityManagerWrapper.getInstance() 81 .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); 82 } 83 MAIN_EXECUTOR.execute(new ShowRecentsCommand(triggeredFromAltTab)); 84 } 85 86 @BinderThread onOverviewHidden()87 public void onOverviewHidden() { 88 MAIN_EXECUTOR.execute(new HideRecentsCommand()); 89 } 90 91 @BinderThread onTip(int actionType, int viewType)92 public void onTip(int actionType, int viewType) { 93 MAIN_EXECUTOR.execute(() -> 94 UserEventDispatcher.newInstance(mContext).logActionTip(actionType, viewType)); 95 } 96 97 private class ShowRecentsCommand extends RecentsActivityCommand { 98 99 private final boolean mTriggeredFromAltTab; 100 ShowRecentsCommand(boolean triggeredFromAltTab)101 ShowRecentsCommand(boolean triggeredFromAltTab) { 102 mTriggeredFromAltTab = triggeredFromAltTab; 103 } 104 105 @Override handleCommand(long elapsedTime)106 protected boolean handleCommand(long elapsedTime) { 107 // TODO: Go to the next page if started from alt-tab. 108 return mActivityInterface.getVisibleRecentsView() != null; 109 } 110 111 @Override onTransitionComplete()112 protected void onTransitionComplete() { 113 // TODO(b/138729100) This doesn't execute first time launcher is run 114 if (mTriggeredFromAltTab) { 115 RecentsView rv = mActivityInterface.getVisibleRecentsView(); 116 if (rv == null) { 117 return; 118 } 119 120 // Ensure that recents view has focus so that it receives the followup key inputs 121 TaskView taskView = rv.getNextTaskView(); 122 if (taskView == null) { 123 if (rv.getTaskViewCount() > 0) { 124 taskView = rv.getTaskViewAt(0); 125 taskView.requestFocus(); 126 } else { 127 rv.requestFocus(); 128 } 129 } else { 130 taskView.requestFocus(); 131 } 132 } 133 } 134 } 135 136 private class HideRecentsCommand extends RecentsActivityCommand { 137 138 @Override handleCommand(long elapsedTime)139 protected boolean handleCommand(long elapsedTime) { 140 RecentsView recents = mActivityInterface.getVisibleRecentsView(); 141 if (recents == null) { 142 return false; 143 } 144 int currentPage = recents.getNextPage(); 145 if (currentPage >= 0 && currentPage < recents.getTaskViewCount()) { 146 ((TaskView) recents.getPageAt(currentPage)).launchTask(true); 147 } else { 148 recents.startHome(); 149 } 150 return true; 151 } 152 } 153 154 private class RecentsActivityCommand<T extends StatefulActivity<?>> implements Runnable { 155 156 protected final BaseActivityInterface<?, T> mActivityInterface; 157 private final long mCreateTime; 158 private final AppToOverviewAnimationProvider<T> mAnimationProvider; 159 160 private final long mToggleClickedTime = SystemClock.uptimeMillis(); 161 private boolean mUserEventLogged; 162 private ActivityInitListener mListener; 163 RecentsActivityCommand()164 public RecentsActivityCommand() { 165 mActivityInterface = mOverviewComponentObserver.getActivityInterface(); 166 mCreateTime = SystemClock.elapsedRealtime(); 167 mAnimationProvider = new AppToOverviewAnimationProvider<>(mActivityInterface, 168 RecentsModel.getRunningTaskId(), mDeviceState); 169 170 // Preload the plan 171 mRecentsModel.getTasks(null); 172 } 173 174 @Override run()175 public void run() { 176 long elapsedTime = mCreateTime - mLastToggleTime; 177 mLastToggleTime = mCreateTime; 178 179 if (handleCommand(elapsedTime)) { 180 // Command already handled. 181 return; 182 } 183 184 if (mActivityInterface.switchToRecentsIfVisible(this::onTransitionComplete)) { 185 // If successfully switched, then return 186 return; 187 } 188 189 // Otherwise, start overview. 190 mListener = mActivityInterface.createActivityInitListener(this::onActivityReady); 191 mListener.registerAndStartActivity(mOverviewComponentObserver.getOverviewIntent(), 192 new RemoteAnimationProvider() { 193 @Override 194 public AnimatorSet createWindowAnimation( 195 RemoteAnimationTargetCompat[] appTargets, 196 RemoteAnimationTargetCompat[] wallpaperTargets) { 197 return RecentsActivityCommand.this.createWindowAnimation(appTargets, 198 wallpaperTargets); 199 } 200 }, mContext, MAIN_EXECUTOR.getHandler(), 201 mAnimationProvider.getRecentsLaunchDuration()); 202 } 203 handleCommand(long elapsedTime)204 protected boolean handleCommand(long elapsedTime) { 205 // TODO: We need to fix this case with PIP, when an activity first enters PIP, it shows 206 // the menu activity which takes window focus, preventing the right condition from 207 // being run below 208 RecentsView recents = mActivityInterface.getVisibleRecentsView(); 209 if (recents != null) { 210 // Launch the next task 211 recents.showNextTask(); 212 return true; 213 } else if (elapsedTime < ViewConfiguration.getDoubleTapTimeout()) { 214 // The user tried to launch back into overview too quickly, either after 215 // launching an app, or before overview has actually shown, just ignore for now 216 return true; 217 } 218 return false; 219 } 220 onActivityReady(Boolean wasVisible)221 private boolean onActivityReady(Boolean wasVisible) { 222 final T activity = mActivityInterface.getCreatedActivity(); 223 if (!mUserEventLogged) { 224 activity.getUserEventDispatcher().logActionCommand( 225 LauncherLogProto.Action.Command.RECENTS_BUTTON, 226 mActivityInterface.getContainerType(), 227 LauncherLogProto.ContainerType.TASKSWITCHER); 228 mUserEventLogged = true; 229 } 230 231 // Switch prediction client to overview 232 PredictionUiStateManager.INSTANCE.get(activity).switchClient( 233 PredictionUiStateManager.Client.OVERVIEW); 234 return mAnimationProvider.onActivityReady(activity, wasVisible); 235 } 236 createWindowAnimation(RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets)237 private AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets, 238 RemoteAnimationTargetCompat[] wallpaperTargets) { 239 if (LatencyTrackerCompat.isEnabled(mContext)) { 240 LatencyTrackerCompat.logToggleRecents( 241 (int) (SystemClock.uptimeMillis() - mToggleClickedTime)); 242 } 243 244 mListener.unregister(); 245 246 AnimatorSet animatorSet = mAnimationProvider.createWindowAnimation(appTargets, 247 wallpaperTargets); 248 animatorSet.addListener(new AnimatorListenerAdapter() { 249 @Override 250 public void onAnimationEnd(Animator animation) { 251 onTransitionComplete(); 252 } 253 }); 254 return animatorSet; 255 } 256 onTransitionComplete()257 protected void onTransitionComplete() { } 258 } 259 } 260