1 /*
2  * Copyright (C) 2019 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.launcher3.uioverrides;
17 
18 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
19 
20 import static com.android.launcher3.LauncherState.NORMAL;
21 import static com.android.launcher3.LauncherState.OVERVIEW;
22 import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
23 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
24 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
25 import static com.android.launcher3.testing.TestProtocol.HINT_STATE_ORDINAL;
26 import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
27 import static com.android.launcher3.testing.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
28 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
29 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
30 
31 import android.content.Intent;
32 import android.content.res.Configuration;
33 import android.os.Bundle;
34 import android.util.Log;
35 import android.view.View;
36 
37 import androidx.annotation.Nullable;
38 
39 import com.android.launcher3.BaseQuickstepLauncher;
40 import com.android.launcher3.DeviceProfile;
41 import com.android.launcher3.Launcher;
42 import com.android.launcher3.LauncherState;
43 import com.android.launcher3.Workspace;
44 import com.android.launcher3.allapps.DiscoveryBounce;
45 import com.android.launcher3.anim.AnimatorPlaybackController;
46 import com.android.launcher3.appprediction.PredictionUiStateManager;
47 import com.android.launcher3.config.FeatureFlags;
48 import com.android.launcher3.folder.Folder;
49 import com.android.launcher3.hybridhotseat.HotseatPredictionController;
50 import com.android.launcher3.logging.InstanceId;
51 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
52 import com.android.launcher3.model.data.AppInfo;
53 import com.android.launcher3.model.data.ItemInfo;
54 import com.android.launcher3.model.data.WorkspaceItemInfo;
55 import com.android.launcher3.popup.SystemShortcut;
56 import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
57 import com.android.launcher3.testing.TestProtocol;
58 import com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory;
59 import com.android.launcher3.uioverrides.touchcontrollers.FlingAndHoldTouchController;
60 import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController;
61 import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController;
62 import com.android.launcher3.uioverrides.touchcontrollers.NoButtonNavbarToOverviewTouchController;
63 import com.android.launcher3.uioverrides.touchcontrollers.NoButtonQuickSwitchTouchController;
64 import com.android.launcher3.uioverrides.touchcontrollers.OverviewToAllAppsTouchController;
65 import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
66 import com.android.launcher3.uioverrides.touchcontrollers.QuickSwitchTouchController;
67 import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
68 import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
69 import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
70 import com.android.launcher3.util.IntArray;
71 import com.android.launcher3.util.TouchController;
72 import com.android.launcher3.util.UiThreadHelper;
73 import com.android.launcher3.util.UiThreadHelper.AsyncCommand;
74 import com.android.quickstep.SysUINavigationMode;
75 import com.android.quickstep.SysUINavigationMode.Mode;
76 import com.android.quickstep.SystemUiProxy;
77 import com.android.quickstep.views.RecentsView;
78 import com.android.quickstep.views.TaskView;
79 
80 import java.io.FileDescriptor;
81 import java.io.PrintWriter;
82 import java.util.ArrayList;
83 import java.util.List;
84 import java.util.OptionalInt;
85 import java.util.stream.Stream;
86 
87 public class QuickstepLauncher extends BaseQuickstepLauncher {
88 
89     public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false;
90     /**
91      * Reusable command for applying the shelf height on the background thread.
92      */
93     public static final AsyncCommand SET_SHELF_HEIGHT = (context, arg1, arg2) ->
94             SystemUiProxy.INSTANCE.get(context).setShelfHeight(arg1 != 0, arg2);
95 
96     @Override
onCreate(Bundle savedInstanceState)97     protected void onCreate(Bundle savedInstanceState) {
98         super.onCreate(savedInstanceState);
99         if (mHotseatPredictionController != null) {
100             mHotseatPredictionController.createPredictor();
101         }
102     }
103 
104     @Override
setupViews()105     protected void setupViews() {
106         super.setupViews();
107         if (FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) {
108             mHotseatPredictionController = new HotseatPredictionController(this);
109         }
110     }
111 
112     @Override
logAppLaunch(ItemInfo info, InstanceId instanceId)113     protected void logAppLaunch(ItemInfo info, InstanceId instanceId) {
114         StatsLogger logger = getStatsLogManager()
115                 .logger().withItemInfo(info).withInstanceId(instanceId);
116         OptionalInt allAppsRank = PredictionUiStateManager.INSTANCE.get(this).getAllAppsRank(info);
117         allAppsRank.ifPresent(logger::withRank);
118         logger.log(LAUNCHER_APP_LAUNCH_TAP);
119 
120         if (mHotseatPredictionController != null) {
121             mHotseatPredictionController.logLaunchedAppRankingInfo(info, instanceId);
122         }
123     }
124 
125     @Override
onConfigurationChanged(Configuration newConfig)126     public void onConfigurationChanged(Configuration newConfig) {
127         super.onConfigurationChanged(newConfig);
128         onStateOrResumeChanging(false /* inTransition */);
129     }
130 
131     @Override
startActivitySafely(View v, Intent intent, ItemInfo item, @Nullable String sourceContainer)132     public boolean startActivitySafely(View v, Intent intent, ItemInfo item,
133             @Nullable String sourceContainer) {
134         if (mHotseatPredictionController != null) {
135             mHotseatPredictionController.setPauseUIUpdate(true);
136         }
137         return super.startActivitySafely(v, intent, item, sourceContainer);
138     }
139 
140     @Override
onActivityFlagsChanged(int changeBits)141     protected void onActivityFlagsChanged(int changeBits) {
142         super.onActivityFlagsChanged(changeBits);
143         if ((changeBits & (ACTIVITY_STATE_DEFERRED_RESUMED | ACTIVITY_STATE_STARTED
144                 | ACTIVITY_STATE_USER_ACTIVE | ACTIVITY_STATE_TRANSITION_ACTIVE)) != 0) {
145             onStateOrResumeChanging((getActivityFlags() & ACTIVITY_STATE_TRANSITION_ACTIVE) == 0);
146         }
147 
148         if (mHotseatPredictionController != null && ((changeBits & ACTIVITY_STATE_STARTED) != 0
149                 || (changeBits & getActivityFlags() & ACTIVITY_STATE_DEFERRED_RESUMED) != 0)) {
150             mHotseatPredictionController.setPauseUIUpdate(false);
151         }
152     }
153 
154     @Override
folderCreatedFromItem(Folder folder, WorkspaceItemInfo itemInfo)155     public void folderCreatedFromItem(Folder folder, WorkspaceItemInfo itemInfo) {
156         super.folderCreatedFromItem(folder, itemInfo);
157         if (mHotseatPredictionController != null) {
158             mHotseatPredictionController.folderCreatedFromWorkspaceItem(itemInfo, folder.getInfo());
159         }
160     }
161 
162     @Override
folderConvertedToItem(Folder folder, WorkspaceItemInfo itemInfo)163     public void folderConvertedToItem(Folder folder, WorkspaceItemInfo itemInfo) {
164         super.folderConvertedToItem(folder, itemInfo);
165         if (mHotseatPredictionController != null) {
166             mHotseatPredictionController.folderConvertedToWorkspaceItem(itemInfo, folder.getInfo());
167         }
168     }
169 
170     @Override
getSupportedShortcuts()171     public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
172         if (mHotseatPredictionController != null) {
173             return Stream.concat(super.getSupportedShortcuts(),
174                     Stream.of(mHotseatPredictionController));
175         } else {
176             return super.getSupportedShortcuts();
177         }
178     }
179 
180     /**
181      * Recents logic that triggers when launcher state changes or launcher activity stops/resumes.
182      */
onStateOrResumeChanging(boolean inTransition)183     private void onStateOrResumeChanging(boolean inTransition) {
184         LauncherState state = getStateManager().getState();
185         DeviceProfile profile = getDeviceProfile();
186         boolean willUserBeActive = (getActivityFlags() & ACTIVITY_STATE_USER_WILL_BE_ACTIVE) != 0;
187         boolean visible = (state == NORMAL || state == OVERVIEW)
188                 && (willUserBeActive || isUserActive())
189                 && !profile.isVerticalBarLayout()
190                 && profile.isPhone && !profile.isLandscape;
191         UiThreadHelper.runAsyncCommand(this, SET_SHELF_HEIGHT, visible ? 1 : 0,
192                 profile.hotseatBarSizePx);
193         if (state == NORMAL && !inTransition) {
194             ((RecentsView) getOverviewPanel()).setSwipeDownShouldLaunchApp(false);
195         }
196     }
197 
198     @Override
bindPredictedItems(List<AppInfo> appInfos, IntArray ranks)199     public void bindPredictedItems(List<AppInfo> appInfos, IntArray ranks) {
200         super.bindPredictedItems(appInfos, ranks);
201         if (mHotseatPredictionController != null) {
202             mHotseatPredictionController.showCachedItems(appInfos, ranks);
203         }
204     }
205 
206     @Override
onDestroy()207     public void onDestroy() {
208         super.onDestroy();
209         if (mHotseatPredictionController != null) {
210             mHotseatPredictionController.destroy();
211             mHotseatPredictionController = null;
212         }
213     }
214 
215     @Override
onStateSetEnd(LauncherState state)216     public void onStateSetEnd(LauncherState state) {
217         super.onStateSetEnd(state);
218 
219         switch (state.ordinal) {
220             case HINT_STATE_ORDINAL: {
221                 Workspace workspace = getWorkspace();
222                 boolean willMoveScreens = workspace.getNextPage() != Workspace.DEFAULT_PAGE;
223                 getStateManager().goToState(NORMAL, true,
224                         willMoveScreens ? null : getScrimView()::startDragHandleEducationAnim);
225                 if (willMoveScreens) {
226                     workspace.post(workspace::moveToDefaultScreen);
227                 }
228                 break;
229             }
230             case OVERVIEW_STATE_ORDINAL: {
231                 RecentsView recentsView = getOverviewPanel();
232                 DiscoveryBounce.showForOverviewIfNeeded(this,
233                         recentsView.getPagedOrientationHandler());
234                 RecentsView rv = getOverviewPanel();
235                 sendCustomAccessibilityEvent(
236                         rv.getPageAt(rv.getCurrentPage()), TYPE_VIEW_FOCUSED, null);
237                 break;
238             }
239             case QUICK_SWITCH_STATE_ORDINAL: {
240                 RecentsView rv = getOverviewPanel();
241                 TaskView tasktolaunch = rv.getTaskViewAt(0);
242                 if (tasktolaunch != null) {
243                     tasktolaunch.launchTask(false, success -> {
244                         if (!success) {
245                             getStateManager().goToState(OVERVIEW);
246                             tasktolaunch.notifyTaskLaunchFailed(TAG);
247                         } else {
248                             getStateManager().moveToRestState();
249                         }
250                     }, MAIN_EXECUTOR.getHandler());
251                 } else {
252                     getStateManager().goToState(NORMAL);
253                 }
254                 break;
255             }
256 
257         }
258     }
259 
260     @Override
createTouchControllers()261     public TouchController[] createTouchControllers() {
262         if (TestProtocol.sDebugTracing) {
263             Log.d(TestProtocol.PAUSE_NOT_DETECTED, "createTouchControllers.1");
264         }
265         Mode mode = SysUINavigationMode.getMode(this);
266 
267         ArrayList<TouchController> list = new ArrayList<>();
268         list.add(getDragController());
269         if (mode == NO_BUTTON) {
270             list.add(new NoButtonQuickSwitchTouchController(this));
271             list.add(new NavBarToHomeTouchController(this));
272             if (TestProtocol.sDebugTracing) {
273                 Log.d(TestProtocol.PAUSE_NOT_DETECTED, "createTouchControllers.2");
274             }
275             if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
276                 if (TestProtocol.sDebugTracing) {
277                     Log.d(TestProtocol.PAUSE_NOT_DETECTED, "createTouchControllers.3");
278                 }
279                 list.add(new NoButtonNavbarToOverviewTouchController(this));
280             } else {
281                 list.add(new FlingAndHoldTouchController(this));
282             }
283         } else {
284             if (getDeviceProfile().isVerticalBarLayout()) {
285                 list.add(new OverviewToAllAppsTouchController(this));
286                 list.add(new LandscapeEdgeSwipeController(this));
287                 if (mode.hasGestures) {
288                     list.add(new TransposedQuickSwitchTouchController(this));
289                 }
290             } else {
291                 list.add(new PortraitStatesTouchController(this,
292                         mode.hasGestures /* allowDragToOverview */));
293                 if (mode.hasGestures) {
294                     list.add(new QuickSwitchTouchController(this));
295                 }
296             }
297         }
298 
299         if (!getDeviceProfile().isMultiWindowMode) {
300             list.add(new StatusBarTouchController(this));
301         }
302 
303         list.add(new LauncherTaskViewController(this));
304         return list.toArray(new TouchController[list.size()]);
305     }
306 
307     @Override
createAtomicAnimationFactory()308     public AtomicAnimationFactory createAtomicAnimationFactory() {
309         return new QuickstepAtomicAnimationFactory(this);
310     }
311 
312     private static final class LauncherTaskViewController extends
313             TaskViewTouchController<Launcher> {
314 
LauncherTaskViewController(Launcher activity)315         LauncherTaskViewController(Launcher activity) {
316             super(activity);
317         }
318 
319         @Override
isRecentsInteractive()320         protected boolean isRecentsInteractive() {
321             return mActivity.isInState(OVERVIEW) || mActivity.isInState(OVERVIEW_MODAL_TASK);
322         }
323 
324         @Override
isRecentsModal()325         protected boolean isRecentsModal() {
326             return mActivity.isInState(OVERVIEW_MODAL_TASK);
327         }
328 
329         @Override
onUserControlledAnimationCreated(AnimatorPlaybackController animController)330         protected void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
331             mActivity.getStateManager().setCurrentUserControlledAnimation(animController);
332         }
333     }
334 
335     @Override
dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)336     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
337         super.dump(prefix, fd, writer, args);
338         RecentsView recentsView = getOverviewPanel();
339         writer.println("\nQuickstepLauncher:");
340         writer.println(prefix + "\tmOrientationState: " + (recentsView == null ? "recentsNull" :
341                 recentsView.getPagedViewOrientedState()));
342     }
343 }
344