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; 17 18 import static com.android.launcher3.AbstractFloatingView.TYPE_ALL; 19 import static com.android.launcher3.AbstractFloatingView.TYPE_HIDE_BACK_BUTTON; 20 import static com.android.launcher3.LauncherState.FLAG_HIDE_BACK_BUTTON; 21 import static com.android.launcher3.LauncherState.NORMAL; 22 import static com.android.launcher3.LauncherState.NO_OFFSET; 23 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; 24 import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID; 25 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; 26 import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS; 27 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY; 28 29 import android.animation.AnimatorSet; 30 import android.animation.ValueAnimator; 31 import android.app.ActivityOptions; 32 import android.content.ComponentName; 33 import android.content.Intent; 34 import android.content.IntentSender; 35 import android.content.ServiceConnection; 36 import android.os.Bundle; 37 import android.os.CancellationSignal; 38 import android.os.IBinder; 39 import android.view.View; 40 import android.window.SplashScreen; 41 42 import androidx.annotation.Nullable; 43 44 import com.android.launcher3.config.FeatureFlags; 45 import com.android.launcher3.dragndrop.DragOptions; 46 import com.android.launcher3.model.WellbeingModel; 47 import com.android.launcher3.model.data.ItemInfo; 48 import com.android.launcher3.popup.SystemShortcut; 49 import com.android.launcher3.proxy.ProxyActivityStarter; 50 import com.android.launcher3.proxy.StartActivityParams; 51 import com.android.launcher3.statehandlers.BackButtonAlphaHandler; 52 import com.android.launcher3.statehandlers.DepthController; 53 import com.android.launcher3.statemanager.StateManager.StateHandler; 54 import com.android.launcher3.taskbar.LauncherTaskbarUIController; 55 import com.android.launcher3.taskbar.TaskbarManager; 56 import com.android.launcher3.taskbar.TaskbarStateHandler; 57 import com.android.launcher3.uioverrides.RecentsViewStateController; 58 import com.android.launcher3.util.ActivityOptionsWrapper; 59 import com.android.launcher3.util.ObjectWrapper; 60 import com.android.launcher3.util.UiThreadHelper; 61 import com.android.quickstep.OverviewCommandHelper; 62 import com.android.quickstep.RecentsModel; 63 import com.android.quickstep.SysUINavigationMode; 64 import com.android.quickstep.SysUINavigationMode.Mode; 65 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener; 66 import com.android.quickstep.SystemUiProxy; 67 import com.android.quickstep.TaskUtils; 68 import com.android.quickstep.TouchInteractionService; 69 import com.android.quickstep.TouchInteractionService.TISBinder; 70 import com.android.quickstep.util.RemoteAnimationProvider; 71 import com.android.quickstep.util.RemoteFadeOutAnimationListener; 72 import com.android.quickstep.util.SplitSelectStateController; 73 import com.android.quickstep.views.OverviewActionsView; 74 import com.android.quickstep.views.RecentsView; 75 import com.android.quickstep.views.SplitPlaceholderView; 76 import com.android.systemui.shared.system.ActivityManagerWrapper; 77 import com.android.systemui.shared.system.ActivityOptionsCompat; 78 import com.android.systemui.shared.system.RemoteAnimationTargetCompat; 79 80 import java.util.List; 81 import java.util.stream.Stream; 82 83 /** 84 * Extension of Launcher activity to provide quickstep specific functionality 85 */ 86 public abstract class BaseQuickstepLauncher extends Launcher 87 implements NavigationModeChangeListener { 88 89 private DepthController mDepthController = new DepthController(this); 90 private QuickstepTransitionManager mAppTransitionManager; 91 92 /** 93 * Reusable command for applying the back button alpha on the background thread. 94 */ 95 public static final UiThreadHelper.AsyncCommand SET_BACK_BUTTON_ALPHA = 96 (context, arg1, arg2) -> SystemUiProxy.INSTANCE.get(context).setNavBarButtonAlpha( 97 Float.intBitsToFloat(arg1), arg2 != 0); 98 99 private OverviewActionsView mActionsView; 100 101 private @Nullable TaskbarManager mTaskbarManager; 102 private @Nullable OverviewCommandHelper mOverviewCommandHelper; 103 private @Nullable LauncherTaskbarUIController mTaskbarUIController; 104 private final ServiceConnection mTisBinderConnection = new ServiceConnection() { 105 @Override 106 public void onServiceConnected(ComponentName componentName, IBinder iBinder) { 107 mTaskbarManager = ((TISBinder) iBinder).getTaskbarManager(); 108 mTaskbarManager.setLauncher(BaseQuickstepLauncher.this); 109 110 mOverviewCommandHelper = ((TISBinder) iBinder).getOverviewCommandHelper(); 111 } 112 113 @Override 114 public void onServiceDisconnected(ComponentName componentName) { } 115 }; 116 private final TaskbarStateHandler mTaskbarStateHandler = new TaskbarStateHandler(this); 117 118 // Will be updated when dragging from taskbar. 119 private @Nullable DragOptions mNextWorkspaceDragOptions = null; 120 private SplitPlaceholderView mSplitPlaceholderView; 121 122 @Override onCreate(Bundle savedInstanceState)123 protected void onCreate(Bundle savedInstanceState) { 124 super.onCreate(savedInstanceState); 125 SysUINavigationMode.INSTANCE.get(this).addModeChangeListener(this); 126 addMultiWindowModeChangedListener(mDepthController); 127 } 128 129 @Override onDestroy()130 public void onDestroy() { 131 mAppTransitionManager.onActivityDestroyed(); 132 133 SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this); 134 135 136 unbindService(mTisBinderConnection); 137 if (mTaskbarManager != null) { 138 mTaskbarManager.setLauncher(null); 139 } 140 super.onDestroy(); 141 } 142 143 @Override onNewIntent(Intent intent)144 protected void onNewIntent(Intent intent) { 145 super.onNewIntent(intent); 146 147 if (mOverviewCommandHelper != null) { 148 mOverviewCommandHelper.clearPendingCommands(); 149 } 150 } 151 getAppTransitionManager()152 public QuickstepTransitionManager getAppTransitionManager() { 153 return mAppTransitionManager; 154 } 155 156 @Override onNavigationModeChanged(Mode newMode)157 public void onNavigationModeChanged(Mode newMode) { 158 getDragLayer().recreateControllers(); 159 if (mActionsView != null) { 160 mActionsView.updateVerticalMargin(newMode); 161 } 162 } 163 164 @Override onEnterAnimationComplete()165 public void onEnterAnimationComplete() { 166 super.onEnterAnimationComplete(); 167 // After the transition to home, enable the high-res thumbnail loader if it wasn't enabled 168 // as a part of quickstep, so that high-res thumbnails can load the next time we enter 169 // overview 170 RecentsModel.INSTANCE.get(this).getThumbnailCache() 171 .getHighResLoadingState().setVisible(true); 172 } 173 174 @Override handleGestureContract(Intent intent)175 protected void handleGestureContract(Intent intent) { 176 if (FeatureFlags.SEPARATE_RECENTS_ACTIVITY.get()) { 177 super.handleGestureContract(intent); 178 } 179 } 180 181 @Override onTrimMemory(int level)182 public void onTrimMemory(int level) { 183 super.onTrimMemory(level); 184 RecentsModel.INSTANCE.get(this).onTrimMemory(level); 185 } 186 187 @Override onUiChangedWhileSleeping()188 public void onUiChangedWhileSleeping() { 189 // Remove the snapshot because the content view may have obvious changes. 190 UI_HELPER_EXECUTOR.execute( 191 () -> ActivityManagerWrapper.getInstance().invalidateHomeTaskSnapshot(this)); 192 } 193 194 @Override onScreenOff()195 protected void onScreenOff() { 196 super.onScreenOff(); 197 if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { 198 RecentsView recentsView = getOverviewPanel(); 199 recentsView.finishRecentsAnimation(true /* toRecents */, null); 200 } 201 } 202 203 @Override startIntentSenderForResult(IntentSender intent, int requestCode, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options)204 public void startIntentSenderForResult(IntentSender intent, int requestCode, 205 Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) { 206 if (requestCode != -1) { 207 mPendingActivityRequestCode = requestCode; 208 StartActivityParams params = new StartActivityParams(this, requestCode); 209 params.intentSender = intent; 210 params.fillInIntent = fillInIntent; 211 params.flagsMask = flagsMask; 212 params.flagsValues = flagsValues; 213 params.extraFlags = extraFlags; 214 params.options = options; 215 startActivity(ProxyActivityStarter.getLaunchIntent(this, params)); 216 } else { 217 super.startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask, 218 flagsValues, extraFlags, options); 219 } 220 } 221 222 @Override startActivityForResult(Intent intent, int requestCode, Bundle options)223 public void startActivityForResult(Intent intent, int requestCode, Bundle options) { 224 if (requestCode != -1) { 225 mPendingActivityRequestCode = requestCode; 226 StartActivityParams params = new StartActivityParams(this, requestCode); 227 params.intent = intent; 228 params.options = options; 229 startActivity(ProxyActivityStarter.getLaunchIntent(this, params)); 230 } else { 231 super.startActivityForResult(intent, requestCode, options); 232 } 233 } 234 235 @Override onDeferredResumed()236 protected void onDeferredResumed() { 237 super.onDeferredResumed(); 238 handlePendingActivityRequest(); 239 } 240 241 @Override onStateSetEnd(LauncherState state)242 public void onStateSetEnd(LauncherState state) { 243 super.onStateSetEnd(state); 244 handlePendingActivityRequest(); 245 } 246 handlePendingActivityRequest()247 private void handlePendingActivityRequest() { 248 if (mPendingActivityRequestCode != -1 && isInState(NORMAL) 249 && ((getActivityFlags() & ACTIVITY_STATE_DEFERRED_RESUMED) != 0)) { 250 // Remove any active ProxyActivityStarter task and send RESULT_CANCELED to Launcher. 251 onActivityResult(mPendingActivityRequestCode, RESULT_CANCELED, null); 252 // ProxyActivityStarter is started with clear task to reset the task after which it 253 // removes the task itself. 254 startActivity(ProxyActivityStarter.getLaunchIntent(this, null)); 255 } 256 } 257 258 @Override setupViews()259 protected void setupViews() { 260 super.setupViews(); 261 262 SysUINavigationMode.INSTANCE.get(this).updateMode(); 263 mActionsView = findViewById(R.id.overview_actions_view); 264 mSplitPlaceholderView = findViewById(R.id.split_placeholder); 265 RecentsView overviewPanel = (RecentsView) getOverviewPanel(); 266 mSplitPlaceholderView.init( 267 new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this)) 268 ); 269 overviewPanel.init(mActionsView, mSplitPlaceholderView); 270 mActionsView.setDp(getDeviceProfile()); 271 mActionsView.updateVerticalMargin(SysUINavigationMode.getMode(this)); 272 273 mAppTransitionManager = new QuickstepTransitionManager(this); 274 mAppTransitionManager.registerRemoteAnimations(); 275 276 bindService(new Intent(this, TouchInteractionService.class), mTisBinderConnection, 0); 277 278 } 279 setTaskbarUIController(LauncherTaskbarUIController taskbarUIController)280 public void setTaskbarUIController(LauncherTaskbarUIController taskbarUIController) { 281 mTaskbarUIController = taskbarUIController; 282 } 283 getActionsView()284 public <T extends OverviewActionsView> T getActionsView() { 285 return (T) mActionsView; 286 } 287 getSplitPlaceholderView()288 public SplitPlaceholderView getSplitPlaceholderView() { 289 return mSplitPlaceholderView; 290 } 291 292 @Override closeOpenViews(boolean animate)293 protected void closeOpenViews(boolean animate) { 294 super.closeOpenViews(animate); 295 TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY); 296 } 297 298 @Override collectStateHandlers(List<StateHandler> out)299 protected void collectStateHandlers(List<StateHandler> out) { 300 super.collectStateHandlers(out); 301 out.add(getDepthController()); 302 out.add(new RecentsViewStateController(this)); 303 out.add(new BackButtonAlphaHandler(this)); 304 out.add(getTaskbarStateHandler()); 305 } 306 getDepthController()307 public DepthController getDepthController() { 308 return mDepthController; 309 } 310 getTaskbarUIController()311 public @Nullable LauncherTaskbarUIController getTaskbarUIController() { 312 return mTaskbarUIController; 313 } 314 getTaskbarStateHandler()315 public TaskbarStateHandler getTaskbarStateHandler() { 316 return mTaskbarStateHandler; 317 } 318 319 @Override supportsAdaptiveIconAnimation(View clickedView)320 public boolean supportsAdaptiveIconAnimation(View clickedView) { 321 return mAppTransitionManager.hasControlRemoteAppTransitionPermission() 322 && FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM.get(); 323 } 324 325 @Override getDefaultWorkspaceDragOptions()326 public DragOptions getDefaultWorkspaceDragOptions() { 327 if (mNextWorkspaceDragOptions != null) { 328 DragOptions options = mNextWorkspaceDragOptions; 329 mNextWorkspaceDragOptions = null; 330 return options; 331 } 332 return super.getDefaultWorkspaceDragOptions(); 333 } 334 setNextWorkspaceDragOptions(DragOptions dragOptions)335 public void setNextWorkspaceDragOptions(DragOptions dragOptions) { 336 mNextWorkspaceDragOptions = dragOptions; 337 } 338 339 @Override useFadeOutAnimationForLauncherStart(CancellationSignal signal)340 public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) { 341 QuickstepTransitionManager appTransitionManager = getAppTransitionManager(); 342 appTransitionManager.setRemoteAnimationProvider(new RemoteAnimationProvider() { 343 @Override 344 public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets, 345 RemoteAnimationTargetCompat[] wallpaperTargets) { 346 347 // On the first call clear the reference. 348 signal.cancel(); 349 350 ValueAnimator fadeAnimation = ValueAnimator.ofFloat(1, 0); 351 fadeAnimation.addUpdateListener(new RemoteFadeOutAnimationListener(appTargets, 352 wallpaperTargets)); 353 AnimatorSet anim = new AnimatorSet(); 354 anim.play(fadeAnimation); 355 return anim; 356 } 357 }, signal); 358 } 359 360 @Override getNormalOverviewScaleAndOffset()361 public float[] getNormalOverviewScaleAndOffset() { 362 return SysUINavigationMode.getMode(this).hasGestures 363 ? new float[] {1, 1} : new float[] {1.1f, NO_OFFSET}; 364 } 365 366 @Override getNormalTaskbarScale()367 public float getNormalTaskbarScale() { 368 if (mTaskbarUIController != null) { 369 return mTaskbarUIController.getTaskbarScaleOnHome(); 370 } 371 return super.getNormalTaskbarScale(); 372 } 373 374 @Override onDragLayerHierarchyChanged()375 public void onDragLayerHierarchyChanged() { 376 onLauncherStateOrFocusChanged(); 377 } 378 379 @Override onActivityFlagsChanged(int changeBits)380 protected void onActivityFlagsChanged(int changeBits) { 381 if ((changeBits 382 & (ACTIVITY_STATE_WINDOW_FOCUSED | ACTIVITY_STATE_TRANSITION_ACTIVE)) != 0) { 383 onLauncherStateOrFocusChanged(); 384 } 385 386 if ((changeBits & ACTIVITY_STATE_STARTED) != 0) { 387 mDepthController.setActivityStarted(isStarted()); 388 } 389 390 if ((changeBits & ACTIVITY_STATE_RESUMED) != 0) { 391 if (mTaskbarUIController != null) { 392 mTaskbarUIController.onLauncherResumedOrPaused(hasBeenResumed()); 393 } 394 } 395 396 super.onActivityFlagsChanged(changeBits); 397 } 398 shouldBackButtonBeHidden(LauncherState toState)399 public boolean shouldBackButtonBeHidden(LauncherState toState) { 400 Mode mode = SysUINavigationMode.getMode(this); 401 boolean shouldBackButtonBeHidden = mode.hasGestures 402 && toState.hasFlag(FLAG_HIDE_BACK_BUTTON) 403 && hasWindowFocus() 404 && (getActivityFlags() & ACTIVITY_STATE_TRANSITION_ACTIVE) == 0; 405 if (shouldBackButtonBeHidden) { 406 // Show the back button if there is a floating view visible. 407 shouldBackButtonBeHidden = AbstractFloatingView.getTopOpenViewWithType(this, 408 TYPE_ALL & ~TYPE_HIDE_BACK_BUTTON) == null; 409 } 410 return shouldBackButtonBeHidden; 411 } 412 413 /** 414 * Sets the back button visibility based on the current state/window focus. 415 */ onLauncherStateOrFocusChanged()416 private void onLauncherStateOrFocusChanged() { 417 boolean shouldBackButtonBeHidden = shouldBackButtonBeHidden(getStateManager().getState()); 418 if (SysUINavigationMode.getMode(this) == TWO_BUTTONS) { 419 UiThreadHelper.setBackButtonAlphaAsync(this, SET_BACK_BUTTON_ALPHA, 420 shouldBackButtonBeHidden ? 0f : 1f, true /* animate */); 421 } 422 if (getDragLayer() != null) { 423 getRootView().setDisallowBackGesture(shouldBackButtonBeHidden); 424 } 425 } 426 427 @Override finishBindingItems(int pageBoundFirst)428 public void finishBindingItems(int pageBoundFirst) { 429 super.finishBindingItems(pageBoundFirst); 430 // Instantiate and initialize WellbeingModel now that its loading won't interfere with 431 // populating workspace. 432 // TODO: Find a better place for this 433 WellbeingModel.INSTANCE.get(this); 434 } 435 436 @Override getSupportedShortcuts()437 public Stream<SystemShortcut.Factory> getSupportedShortcuts() { 438 return Stream.concat(Stream.of(WellbeingModel.SHORTCUT_FACTORY), 439 super.getSupportedShortcuts()); 440 } 441 442 @Override getActivityLaunchOptions(View v, @Nullable ItemInfo item)443 public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) { 444 ActivityOptionsWrapper activityOptions = 445 mAppTransitionManager.hasControlRemoteAppTransitionPermission() 446 ? mAppTransitionManager.getActivityLaunchOptions(v) 447 : super.getActivityLaunchOptions(v, item); 448 if (mLastTouchUpTime > 0) { 449 ActivityOptionsCompat.setLauncherSourceInfo( 450 activityOptions.options, mLastTouchUpTime); 451 } 452 activityOptions.options.setSplashscreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON); 453 addLaunchCookie(item, activityOptions.options); 454 return activityOptions; 455 } 456 457 /** 458 * Adds a new launch cookie for the activity launch of the given {@param info} if supported. 459 */ addLaunchCookie(ItemInfo info, ActivityOptions opts)460 public void addLaunchCookie(ItemInfo info, ActivityOptions opts) { 461 if (info == null) { 462 return; 463 } 464 switch (info.container) { 465 case LauncherSettings.Favorites.CONTAINER_DESKTOP: 466 case LauncherSettings.Favorites.CONTAINER_HOTSEAT: 467 // Fall through and continue it's on the workspace (we don't support swiping back 468 // to other containers like all apps or the hotseat predictions (which can change) 469 break; 470 default: 471 if (info.container >= 0) { 472 // Also allow swiping to folders 473 break; 474 } 475 // Reset any existing launch cookies associated with the cookie 476 opts.setLaunchCookie(ObjectWrapper.wrap(NO_MATCHING_ID)); 477 return; 478 } 479 switch (info.itemType) { 480 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: 481 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: 482 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: 483 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: 484 // Fall through and continue if it's an app, shortcut, or widget 485 break; 486 default: 487 // Reset any existing launch cookies associated with the cookie 488 opts.setLaunchCookie(ObjectWrapper.wrap(NO_MATCHING_ID)); 489 return; 490 } 491 opts.setLaunchCookie(ObjectWrapper.wrap(new Integer(info.id))); 492 } 493 setHintUserWillBeActive()494 public void setHintUserWillBeActive() { 495 addActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE); 496 } 497 } 498