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