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