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