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