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.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
21 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
22 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
23 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
24 import static com.android.launcher3.LauncherState.ALL_APPS;
25 import static com.android.launcher3.LauncherState.NORMAL;
26 import static com.android.launcher3.LauncherState.OVERVIEW;
27 import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
28 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
29 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_WIDGET_APP_START;
30 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
31 import static com.android.launcher3.testing.TestProtocol.HINT_STATE_ORDINAL;
32 import static com.android.launcher3.testing.TestProtocol.HINT_STATE_TWO_BUTTON_ORDINAL;
33 import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
34 import static com.android.launcher3.testing.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
35 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
36 
37 import android.content.Intent;
38 import android.content.SharedPreferences;
39 import android.content.res.Configuration;
40 import android.view.HapticFeedbackConstants;
41 import android.view.View;
42 
43 import com.android.launcher3.BaseQuickstepLauncher;
44 import com.android.launcher3.DeviceProfile;
45 import com.android.launcher3.Launcher;
46 import com.android.launcher3.LauncherSettings.Favorites;
47 import com.android.launcher3.LauncherState;
48 import com.android.launcher3.QuickstepAccessibilityDelegate;
49 import com.android.launcher3.Workspace;
50 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
51 import com.android.launcher3.anim.AnimatorPlaybackController;
52 import com.android.launcher3.appprediction.PredictionRowView;
53 import com.android.launcher3.hybridhotseat.HotseatPredictionController;
54 import com.android.launcher3.logging.InstanceId;
55 import com.android.launcher3.logging.StatsLogManager;
56 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
57 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
58 import com.android.launcher3.model.data.ItemInfo;
59 import com.android.launcher3.popup.SystemShortcut;
60 import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
61 import com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory;
62 import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController;
63 import com.android.launcher3.uioverrides.touchcontrollers.NoButtonNavbarToOverviewTouchController;
64 import com.android.launcher3.uioverrides.touchcontrollers.NoButtonQuickSwitchTouchController;
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.uioverrides.touchcontrollers.TwoButtonNavbarTouchController;
71 import com.android.launcher3.util.ItemInfoMatcher;
72 import com.android.launcher3.util.OnboardingPrefs;
73 import com.android.launcher3.util.PendingRequestArgs;
74 import com.android.launcher3.util.TouchController;
75 import com.android.launcher3.util.UiThreadHelper;
76 import com.android.launcher3.util.UiThreadHelper.AsyncCommand;
77 import com.android.launcher3.widget.LauncherAppWidgetHost;
78 import com.android.quickstep.SysUINavigationMode;
79 import com.android.quickstep.SysUINavigationMode.Mode;
80 import com.android.quickstep.SystemUiProxy;
81 import com.android.quickstep.TaskUtils;
82 import com.android.quickstep.util.QuickstepOnboardingPrefs;
83 import com.android.quickstep.views.RecentsView;
84 import com.android.quickstep.views.TaskView;
85 
86 import java.io.FileDescriptor;
87 import java.io.PrintWriter;
88 import java.util.ArrayList;
89 import java.util.Objects;
90 import java.util.stream.Stream;
91 
92 public class QuickstepLauncher extends BaseQuickstepLauncher {
93 
94     public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false;
95     /**
96      * Reusable command for applying the shelf height on the background thread.
97      */
98     public static final AsyncCommand SET_SHELF_HEIGHT = (context, arg1, arg2) ->
99             SystemUiProxy.INSTANCE.get(context).setShelfHeight(arg1 != 0, arg2);
100 
101     private FixedContainerItems mAllAppsPredictions;
102     private HotseatPredictionController mHotseatPredictionController;
103 
104     @Override
setupViews()105     protected void setupViews() {
106         super.setupViews();
107         mHotseatPredictionController = new HotseatPredictionController(this);
108     }
109 
110     @Override
logAppLaunch(StatsLogManager statsLogManager, ItemInfo info, InstanceId instanceId)111     public void logAppLaunch(StatsLogManager statsLogManager, ItemInfo info,
112             InstanceId instanceId) {
113         // If the app launch is from any of the surfaces in AllApps then add the InstanceId from
114         // LiveSearchManager to recreate the AllApps session on the server side.
115         if (mAllAppsSessionLogId != null && ALL_APPS.equals(
116                 getStateManager().getCurrentStableState())) {
117             instanceId = mAllAppsSessionLogId;
118         }
119 
120         StatsLogger logger = statsLogManager.logger().withItemInfo(info).withInstanceId(instanceId);
121 
122         if (mAllAppsPredictions != null
123                 && (info.itemType == ITEM_TYPE_APPLICATION
124                 || info.itemType == ITEM_TYPE_SHORTCUT
125                 || info.itemType == ITEM_TYPE_DEEP_SHORTCUT)) {
126             int count = mAllAppsPredictions.items.size();
127             for (int i = 0; i < count; i++) {
128                 ItemInfo targetInfo = mAllAppsPredictions.items.get(i);
129                 if (targetInfo.itemType == info.itemType
130                         && targetInfo.user.equals(info.user)
131                         && Objects.equals(targetInfo.getIntent(), info.getIntent())) {
132                     logger.withRank(i);
133                     break;
134                 }
135 
136             }
137         }
138         logger.log(LAUNCHER_APP_LAUNCH_TAP);
139 
140         mHotseatPredictionController.logLaunchedAppRankingInfo(info, instanceId);
141     }
142 
143     @Override
completeAddShortcut(Intent data, int container, int screenId, int cellX, int cellY, PendingRequestArgs args)144     protected void completeAddShortcut(Intent data, int container, int screenId, int cellX,
145             int cellY, PendingRequestArgs args) {
146         if (container == CONTAINER_HOTSEAT) {
147             mHotseatPredictionController.onDeferredDrop(cellX, cellY);
148         }
149         super.completeAddShortcut(data, container, screenId, cellX, cellY, args);
150     }
151 
152     @Override
createAccessibilityDelegate()153     protected LauncherAccessibilityDelegate createAccessibilityDelegate() {
154         return new QuickstepAccessibilityDelegate(this);
155     }
156 
157     /**
158      * Returns Prediction controller for hybrid hotseat
159      */
getHotseatPredictionController()160     public HotseatPredictionController getHotseatPredictionController() {
161         return mHotseatPredictionController;
162     }
163 
164     @Override
createOnboardingPrefs(SharedPreferences sharedPrefs)165     protected OnboardingPrefs createOnboardingPrefs(SharedPreferences sharedPrefs) {
166         return new QuickstepOnboardingPrefs(this, sharedPrefs);
167     }
168 
169     @Override
onConfigurationChanged(Configuration newConfig)170     public void onConfigurationChanged(Configuration newConfig) {
171         super.onConfigurationChanged(newConfig);
172         onStateOrResumeChanging(false /* inTransition */);
173     }
174 
175     @Override
startActivitySafely(View v, Intent intent, ItemInfo item)176     public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
177         // Only pause is taskbar controller is not present
178         mHotseatPredictionController.setPauseUIUpdate(getTaskbarUIController() == null);
179         boolean started = super.startActivitySafely(v, intent, item);
180         if (getTaskbarUIController() == null && !started) {
181             mHotseatPredictionController.setPauseUIUpdate(false);
182         }
183         return started;
184     }
185 
186     @Override
onActivityFlagsChanged(int changeBits)187     protected void onActivityFlagsChanged(int changeBits) {
188         super.onActivityFlagsChanged(changeBits);
189         if ((changeBits & (ACTIVITY_STATE_DEFERRED_RESUMED | ACTIVITY_STATE_STARTED
190                 | ACTIVITY_STATE_USER_ACTIVE | ACTIVITY_STATE_TRANSITION_ACTIVE)) != 0) {
191             onStateOrResumeChanging((getActivityFlags() & ACTIVITY_STATE_TRANSITION_ACTIVE) == 0);
192         }
193 
194         if (((changeBits & ACTIVITY_STATE_STARTED) != 0
195                 || (changeBits & getActivityFlags() & ACTIVITY_STATE_DEFERRED_RESUMED) != 0)) {
196             mHotseatPredictionController.setPauseUIUpdate(false);
197         }
198     }
199 
200     @Override
showAllAppsFromIntent(boolean alreadyOnHome)201     protected void showAllAppsFromIntent(boolean alreadyOnHome) {
202         TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY);
203         super.showAllAppsFromIntent(alreadyOnHome);
204     }
205 
206     @Override
getSupportedShortcuts()207     public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
208         return Stream.concat(
209                 Stream.of(mHotseatPredictionController), super.getSupportedShortcuts());
210     }
211 
212     /**
213      * Recents logic that triggers when launcher state changes or launcher activity stops/resumes.
214      */
onStateOrResumeChanging(boolean inTransition)215     private void onStateOrResumeChanging(boolean inTransition) {
216         LauncherState state = getStateManager().getState();
217         boolean started = ((getActivityFlags() & ACTIVITY_STATE_STARTED)) != 0;
218         if (started) {
219             DeviceProfile profile = getDeviceProfile();
220             boolean willUserBeActive =
221                     (getActivityFlags() & ACTIVITY_STATE_USER_WILL_BE_ACTIVE) != 0;
222             boolean visible = (state == NORMAL || state == OVERVIEW)
223                     && (willUserBeActive || isUserActive())
224                     && !profile.isVerticalBarLayout()
225                     && profile.isPhone && !profile.isLandscape;
226             UiThreadHelper.runAsyncCommand(this, SET_SHELF_HEIGHT, visible ? 1 : 0,
227                     profile.hotseatBarSizePx);
228         }
229         if (state == NORMAL && !inTransition) {
230             ((RecentsView) getOverviewPanel()).setSwipeDownShouldLaunchApp(false);
231         }
232     }
233 
234     @Override
bindExtraContainerItems(FixedContainerItems item)235     public void bindExtraContainerItems(FixedContainerItems item) {
236         if (item.containerId == Favorites.CONTAINER_PREDICTION) {
237             mAllAppsPredictions = item;
238             getAppsView().getFloatingHeaderView().findFixedRowByType(PredictionRowView.class)
239                     .setPredictedApps(item.items);
240         } else if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION) {
241             mHotseatPredictionController.setPredictedItems(item);
242         } else if (item.containerId == Favorites.CONTAINER_WIDGETS_PREDICTION) {
243             getPopupDataProvider().setRecommendedWidgets(item.items);
244         }
245     }
246 
247     @Override
bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher)248     public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher) {
249         super.bindWorkspaceComponentsRemoved(matcher);
250         mHotseatPredictionController.onModelItemsRemoved(matcher);
251     }
252 
253     @Override
onDestroy()254     public void onDestroy() {
255         super.onDestroy();
256         mHotseatPredictionController.destroy();
257     }
258 
259     @Override
onStateSetEnd(LauncherState state)260     public void onStateSetEnd(LauncherState state) {
261         super.onStateSetEnd(state);
262 
263         switch (state.ordinal) {
264             case HINT_STATE_ORDINAL: {
265                 Workspace workspace = getWorkspace();
266                 getStateManager().goToState(NORMAL);
267                 if (workspace.getNextPage() != Workspace.DEFAULT_PAGE) {
268                     workspace.post(workspace::moveToDefaultScreen);
269                 }
270                 break;
271             }
272             case HINT_STATE_TWO_BUTTON_ORDINAL: {
273                 getStateManager().goToState(OVERVIEW);
274                 getDragLayer().performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
275                 break;
276             }
277             case OVERVIEW_STATE_ORDINAL: {
278                 RecentsView rv = getOverviewPanel();
279                 sendCustomAccessibilityEvent(
280                         rv.getPageAt(rv.getCurrentPage()), TYPE_VIEW_FOCUSED, null);
281                 break;
282             }
283             case QUICK_SWITCH_STATE_ORDINAL: {
284                 RecentsView rv = getOverviewPanel();
285                 TaskView tasktolaunch = rv.getTaskViewAt(0);
286                 if (tasktolaunch != null) {
287                     tasktolaunch.launchTask(success -> {
288                         if (!success) {
289                             getStateManager().goToState(OVERVIEW);
290                         } else {
291                             getStateManager().moveToRestState();
292                         }
293                     });
294                 } else {
295                     getStateManager().goToState(NORMAL);
296                 }
297                 break;
298             }
299 
300         }
301     }
302 
303     @Override
createTouchControllers()304     public TouchController[] createTouchControllers() {
305         Mode mode = SysUINavigationMode.getMode(this);
306 
307         ArrayList<TouchController> list = new ArrayList<>();
308         list.add(getDragController());
309         switch (mode) {
310             case NO_BUTTON:
311                 list.add(new NoButtonQuickSwitchTouchController(this));
312                 list.add(new NavBarToHomeTouchController(this));
313                 list.add(new NoButtonNavbarToOverviewTouchController(this));
314                 break;
315             case TWO_BUTTONS:
316                 list.add(new TwoButtonNavbarTouchController(this));
317                 list.add(getDeviceProfile().isVerticalBarLayout()
318                         ? new TransposedQuickSwitchTouchController(this)
319                         : new QuickSwitchTouchController(this));
320                 list.add(new PortraitStatesTouchController(this));
321                 break;
322             case THREE_BUTTONS:
323             default:
324                 list.add(new PortraitStatesTouchController(this));
325         }
326 
327         if (!getDeviceProfile().isMultiWindowMode) {
328             list.add(new StatusBarTouchController(this));
329         }
330 
331         list.add(new LauncherTaskViewController(this));
332         return list.toArray(new TouchController[list.size()]);
333     }
334 
335     @Override
createAtomicAnimationFactory()336     public AtomicAnimationFactory createAtomicAnimationFactory() {
337         return new QuickstepAtomicAnimationFactory(this);
338     }
339 
createAppWidgetHost()340     protected LauncherAppWidgetHost createAppWidgetHost() {
341         LauncherAppWidgetHost appWidgetHost = super.createAppWidgetHost();
342         if (ENABLE_QUICKSTEP_WIDGET_APP_START.get()) {
343             appWidgetHost.setInteractionHandler(new QuickstepInteractionHandler(this));
344         }
345         return appWidgetHost;
346     }
347 
348     private static final class LauncherTaskViewController extends
349             TaskViewTouchController<Launcher> {
350 
LauncherTaskViewController(Launcher activity)351         LauncherTaskViewController(Launcher activity) {
352             super(activity);
353         }
354 
355         @Override
isRecentsInteractive()356         protected boolean isRecentsInteractive() {
357             return mActivity.isInState(OVERVIEW) || mActivity.isInState(OVERVIEW_MODAL_TASK);
358         }
359 
360         @Override
isRecentsModal()361         protected boolean isRecentsModal() {
362             return mActivity.isInState(OVERVIEW_MODAL_TASK);
363         }
364 
365         @Override
onUserControlledAnimationCreated(AnimatorPlaybackController animController)366         protected void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
367             mActivity.getStateManager().setCurrentUserControlledAnimation(animController);
368         }
369     }
370 
371     @Override
dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)372     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
373         super.dump(prefix, fd, writer, args);
374         RecentsView recentsView = getOverviewPanel();
375         writer.println("\nQuickstepLauncher:");
376         writer.println(prefix + "\tmOrientationState: " + (recentsView == null ? "recentsNull" :
377                 recentsView.getPagedViewOrientedState()));
378     }
379 }
380