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.quickstep; 17 18 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION; 19 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE; 20 21 import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION; 22 import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_DURATION; 23 import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_PRE_DELAY; 24 import static com.android.launcher3.Utilities.createHomeIntent; 25 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; 26 import static com.android.launcher3.graphics.SysUiScrim.SYSUI_PROGRESS; 27 import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL; 28 import static com.android.quickstep.TaskUtils.taskIsATargetWithMode; 29 import static com.android.quickstep.TaskViewUtils.createRecentsWindowAnimator; 30 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING; 31 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING; 32 33 import android.animation.Animator; 34 import android.animation.AnimatorListenerAdapter; 35 import android.animation.AnimatorSet; 36 import android.content.Intent; 37 import android.content.res.Configuration; 38 import android.os.Bundle; 39 import android.os.Handler; 40 import android.os.Looper; 41 import android.view.SurfaceControl.Transaction; 42 import android.view.View; 43 import android.window.SplashScreen; 44 45 import androidx.annotation.Nullable; 46 47 import com.android.launcher3.AbstractFloatingView; 48 import com.android.launcher3.DeviceProfile; 49 import com.android.launcher3.InvariantDeviceProfile; 50 import com.android.launcher3.LauncherAnimationRunner; 51 import com.android.launcher3.LauncherAnimationRunner.AnimationResult; 52 import com.android.launcher3.LauncherAnimationRunner.RemoteAnimationFactory; 53 import com.android.launcher3.R; 54 import com.android.launcher3.anim.AnimatorPlaybackController; 55 import com.android.launcher3.anim.Interpolators; 56 import com.android.launcher3.anim.PendingAnimation; 57 import com.android.launcher3.compat.AccessibilityManagerCompat; 58 import com.android.launcher3.model.data.ItemInfo; 59 import com.android.launcher3.statemanager.StateManager; 60 import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory; 61 import com.android.launcher3.statemanager.StateManager.StateHandler; 62 import com.android.launcher3.statemanager.StatefulActivity; 63 import com.android.launcher3.taskbar.FallbackTaskbarUIController; 64 import com.android.launcher3.taskbar.TaskbarManager; 65 import com.android.launcher3.util.ActivityOptionsWrapper; 66 import com.android.launcher3.util.ActivityTracker; 67 import com.android.launcher3.util.RunnableList; 68 import com.android.launcher3.util.SystemUiController; 69 import com.android.launcher3.util.Themes; 70 import com.android.launcher3.views.BaseDragLayer; 71 import com.android.launcher3.views.ScrimView; 72 import com.android.quickstep.fallback.FallbackRecentsStateController; 73 import com.android.quickstep.fallback.FallbackRecentsView; 74 import com.android.quickstep.fallback.RecentsDragLayer; 75 import com.android.quickstep.fallback.RecentsState; 76 import com.android.quickstep.util.RecentsAtomicAnimationFactory; 77 import com.android.quickstep.util.SplitSelectStateController; 78 import com.android.quickstep.util.TISBindHelper; 79 import com.android.quickstep.views.OverviewActionsView; 80 import com.android.quickstep.views.RecentsView; 81 import com.android.quickstep.views.TaskView; 82 import com.android.systemui.shared.system.ActivityOptionsCompat; 83 import com.android.systemui.shared.system.RemoteAnimationAdapterCompat; 84 import com.android.systemui.shared.system.RemoteAnimationTargetCompat; 85 86 import java.io.FileDescriptor; 87 import java.io.PrintWriter; 88 import java.util.List; 89 90 /** 91 * A recents activity that shows the recently launched tasks as swipable task cards. 92 * See {@link com.android.quickstep.views.RecentsView}. 93 */ 94 public final class RecentsActivity extends StatefulActivity<RecentsState> { 95 96 public static final ActivityTracker<RecentsActivity> ACTIVITY_TRACKER = 97 new ActivityTracker<>(); 98 99 private Handler mUiHandler = new Handler(Looper.getMainLooper()); 100 101 private static final long HOME_APPEAR_DURATION = 250; 102 private static final long RECENTS_ANIMATION_TIMEOUT = 1000; 103 104 private RecentsDragLayer mDragLayer; 105 private ScrimView mScrimView; 106 private FallbackRecentsView mFallbackRecentsView; 107 private OverviewActionsView mActionsView; 108 private TISBindHelper mTISBindHelper; 109 private @Nullable TaskbarManager mTaskbarManager; 110 private @Nullable FallbackTaskbarUIController mTaskbarUIController; 111 112 private Configuration mOldConfig; 113 114 private StateManager<RecentsState> mStateManager; 115 116 // Strong refs to runners which are cleared when the activity is destroyed 117 private RemoteAnimationFactory mActivityLaunchAnimationRunner; 118 119 // For handling degenerate cases where starting an activity doesn't actually trigger the remote 120 // animation callback 121 private final Handler mHandler = new Handler(); 122 private final Runnable mAnimationStartTimeoutRunnable = this::onAnimationStartTimeout; 123 124 /** 125 * Init drag layer and overview panel views. 126 */ setupViews()127 protected void setupViews() { 128 inflateRootView(R.layout.fallback_recents_activity); 129 setContentView(getRootView()); 130 mDragLayer = findViewById(R.id.drag_layer); 131 mScrimView = findViewById(R.id.scrim_view); 132 mFallbackRecentsView = findViewById(R.id.overview_panel); 133 mActionsView = findViewById(R.id.overview_actions_view); 134 SYSUI_PROGRESS.set(getRootView().getSysUiScrim(), 0f); 135 136 SplitSelectStateController controller = 137 new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this), 138 getStateManager(), null /*depthController*/); 139 mDragLayer.recreateControllers(); 140 mFallbackRecentsView.init(mActionsView, controller); 141 142 mTISBindHelper = new TISBindHelper(this, this::onTISConnected); 143 } 144 onTISConnected(TouchInteractionService.TISBinder binder)145 private void onTISConnected(TouchInteractionService.TISBinder binder) { 146 mTaskbarManager = binder.getTaskbarManager(); 147 mTaskbarManager.setActivity(this); 148 } 149 150 @Override runOnBindToTouchInteractionService(Runnable r)151 public void runOnBindToTouchInteractionService(Runnable r) { 152 mTISBindHelper.runOnBindToTouchInteractionService(r); 153 } 154 setTaskbarUIController(FallbackTaskbarUIController taskbarUIController)155 public void setTaskbarUIController(FallbackTaskbarUIController taskbarUIController) { 156 mTaskbarUIController = taskbarUIController; 157 } 158 getTaskbarUIController()159 public FallbackTaskbarUIController getTaskbarUIController() { 160 return mTaskbarUIController; 161 } 162 163 @Override onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig)164 public void onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig) { 165 onHandleConfigChanged(); 166 super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig); 167 } 168 169 @Override onNewIntent(Intent intent)170 protected void onNewIntent(Intent intent) { 171 super.onNewIntent(intent); 172 ACTIVITY_TRACKER.handleNewIntent(this); 173 } 174 175 /** 176 * Logic for when device configuration changes (rotation, screen size change, multi-window, 177 * etc.) 178 */ onHandleConfigChanged()179 protected void onHandleConfigChanged() { 180 initDeviceProfile(); 181 182 AbstractFloatingView.closeOpenViews(this, true, 183 AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE); 184 dispatchDeviceProfileChanged(); 185 186 reapplyUi(); 187 mDragLayer.recreateControllers(); 188 } 189 190 /** 191 * Generate the device profile to use in this activity. 192 * @return device profile 193 */ createDeviceProfile()194 protected DeviceProfile createDeviceProfile() { 195 DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(this).getDeviceProfile(this); 196 197 // In case we are reusing IDP, create a copy so that we don't conflict with Launcher 198 // activity. 199 return (mDragLayer != null) && isInMultiWindowMode() 200 ? dp.getMultiWindowProfile(this, getMultiWindowDisplaySize()) 201 : dp.copy(this); 202 } 203 204 @Override getDragLayer()205 public BaseDragLayer getDragLayer() { 206 return mDragLayer; 207 } 208 getScrimView()209 public ScrimView getScrimView() { 210 return mScrimView; 211 } 212 213 @Override getOverviewPanel()214 public <T extends View> T getOverviewPanel() { 215 return (T) mFallbackRecentsView; 216 } 217 getActionsView()218 public OverviewActionsView getActionsView() { 219 return mActionsView; 220 } 221 222 @Override returnToHomescreen()223 public void returnToHomescreen() { 224 super.returnToHomescreen(); 225 // TODO(b/137318995) This should go home, but doing so removes freeform windows 226 } 227 228 /** 229 * Called if the remote animation callback from #getActivityLaunchOptions() hasn't called back 230 * in a reasonable time due to a conflict with the recents animation. 231 */ onAnimationStartTimeout()232 private void onAnimationStartTimeout() { 233 if (mActivityLaunchAnimationRunner != null) { 234 mActivityLaunchAnimationRunner.onAnimationCancelled(); 235 } 236 } 237 238 @Override getActivityLaunchOptions(final View v, @Nullable ItemInfo item)239 public ActivityOptionsWrapper getActivityLaunchOptions(final View v, @Nullable ItemInfo item) { 240 if (!(v instanceof TaskView)) { 241 return super.getActivityLaunchOptions(v, item); 242 } 243 244 final TaskView taskView = (TaskView) v; 245 RunnableList onEndCallback = new RunnableList(); 246 247 mActivityLaunchAnimationRunner = new RemoteAnimationFactory() { 248 @Override 249 public void onCreateAnimation(int transit, RemoteAnimationTargetCompat[] appTargets, 250 RemoteAnimationTargetCompat[] wallpaperTargets, 251 RemoteAnimationTargetCompat[] nonAppTargets, AnimationResult result) { 252 mHandler.removeCallbacks(mAnimationStartTimeoutRunnable); 253 AnimatorSet anim = composeRecentsLaunchAnimator(taskView, appTargets, 254 wallpaperTargets, nonAppTargets); 255 anim.addListener(resetStateListener()); 256 result.setAnimation(anim, RecentsActivity.this, onEndCallback::executeAllAndDestroy, 257 true /* skipFirstFrame */); 258 } 259 260 @Override 261 public void onAnimationCancelled() { 262 mHandler.removeCallbacks(mAnimationStartTimeoutRunnable); 263 onEndCallback.executeAllAndDestroy(); 264 } 265 }; 266 267 final LauncherAnimationRunner wrapper = new LauncherAnimationRunner( 268 mUiHandler, mActivityLaunchAnimationRunner, true /* startAtFrontOfQueue */); 269 RemoteAnimationAdapterCompat adapterCompat = new RemoteAnimationAdapterCompat( 270 wrapper, RECENTS_LAUNCH_DURATION, 271 RECENTS_LAUNCH_DURATION - STATUS_BAR_TRANSITION_DURATION 272 - STATUS_BAR_TRANSITION_PRE_DELAY, getIApplicationThread()); 273 final ActivityOptionsWrapper activityOptions = new ActivityOptionsWrapper( 274 ActivityOptionsCompat.makeRemoteAnimation(adapterCompat), 275 onEndCallback); 276 activityOptions.options.setSplashscreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON); 277 mHandler.postDelayed(mAnimationStartTimeoutRunnable, RECENTS_ANIMATION_TIMEOUT); 278 return activityOptions; 279 } 280 281 /** 282 * Composes the animations for a launch from the recents list if possible. 283 */ composeRecentsLaunchAnimator(TaskView taskView, RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets, RemoteAnimationTargetCompat[] nonAppTargets)284 private AnimatorSet composeRecentsLaunchAnimator(TaskView taskView, 285 RemoteAnimationTargetCompat[] appTargets, 286 RemoteAnimationTargetCompat[] wallpaperTargets, 287 RemoteAnimationTargetCompat[] nonAppTargets) { 288 AnimatorSet target = new AnimatorSet(); 289 boolean activityClosing = taskIsATargetWithMode(appTargets, getTaskId(), MODE_CLOSING); 290 PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION); 291 createRecentsWindowAnimator(taskView, !activityClosing, appTargets, 292 wallpaperTargets, nonAppTargets, null /* depthController */, pa); 293 target.play(pa.buildAnim()); 294 295 // Found a visible recents task that matches the opening app, lets launch the app from there 296 if (activityClosing) { 297 Animator adjacentAnimation = mFallbackRecentsView 298 .createAdjacentPageAnimForTaskLaunch(taskView); 299 adjacentAnimation.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR); 300 adjacentAnimation.setDuration(RECENTS_LAUNCH_DURATION); 301 adjacentAnimation.addListener(resetStateListener()); 302 target.play(adjacentAnimation); 303 } 304 return target; 305 } 306 307 @Override onStart()308 protected void onStart() { 309 // Set the alpha to 1 before calling super, as it may get set back to 0 due to 310 // onActivityStart callback. 311 mFallbackRecentsView.setContentAlpha(1); 312 super.onStart(); 313 mFallbackRecentsView.updateLocusId(); 314 } 315 316 @Override onStop()317 protected void onStop() { 318 super.onStop(); 319 320 // Workaround for b/78520668, explicitly trim memory once UI is hidden 321 onTrimMemory(TRIM_MEMORY_UI_HIDDEN); 322 mFallbackRecentsView.updateLocusId(); 323 } 324 325 @Override onResume()326 protected void onResume() { 327 super.onResume(); 328 AccessibilityManagerCompat.sendStateEventToTest(getBaseContext(), OVERVIEW_STATE_ORDINAL); 329 } 330 331 @Override onCreate(Bundle savedInstanceState)332 protected void onCreate(Bundle savedInstanceState) { 333 super.onCreate(savedInstanceState); 334 335 mStateManager = new StateManager<>(this, RecentsState.BG_LAUNCHER); 336 337 mOldConfig = new Configuration(getResources().getConfiguration()); 338 initDeviceProfile(); 339 setupViews(); 340 341 getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW, 342 Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText)); 343 ACTIVITY_TRACKER.handleCreate(this); 344 } 345 346 @Override onConfigurationChanged(Configuration newConfig)347 public void onConfigurationChanged(Configuration newConfig) { 348 int diff = newConfig.diff(mOldConfig); 349 if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0) { 350 onHandleConfigChanged(); 351 } 352 mOldConfig.setTo(newConfig); 353 super.onConfigurationChanged(newConfig); 354 } 355 356 @Override onStateSetEnd(RecentsState state)357 public void onStateSetEnd(RecentsState state) { 358 super.onStateSetEnd(state); 359 360 if (state == RecentsState.DEFAULT) { 361 AccessibilityManagerCompat.sendStateEventToTest(getBaseContext(), 362 OVERVIEW_STATE_ORDINAL); 363 } 364 } 365 366 /** 367 * Initialize/update the device profile. 368 */ initDeviceProfile()369 private void initDeviceProfile() { 370 mDeviceProfile = createDeviceProfile(); 371 onDeviceProfileInitiated(); 372 } 373 374 @Override onEnterAnimationComplete()375 public void onEnterAnimationComplete() { 376 super.onEnterAnimationComplete(); 377 // After the transition to home, enable the high-res thumbnail loader if it wasn't enabled 378 // as a part of quickstep, so that high-res thumbnails can load the next time we enter 379 // overview 380 RecentsModel.INSTANCE.get(this).getThumbnailCache() 381 .getHighResLoadingState().setVisible(true); 382 } 383 384 @Override onTrimMemory(int level)385 public void onTrimMemory(int level) { 386 super.onTrimMemory(level); 387 RecentsModel.INSTANCE.get(this).onTrimMemory(level); 388 } 389 390 @Override onDestroy()391 protected void onDestroy() { 392 super.onDestroy(); 393 ACTIVITY_TRACKER.onActivityDestroyed(this); 394 mActivityLaunchAnimationRunner = null; 395 396 mTISBindHelper.onDestroy(); 397 if (mTaskbarManager != null) { 398 mTaskbarManager.clearActivity(this); 399 } 400 } 401 402 @Override onBackPressed()403 public void onBackPressed() { 404 // TODO: Launch the task we came from 405 startHome(); 406 } 407 startHome()408 public void startHome() { 409 if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { 410 RecentsView recentsView = getOverviewPanel(); 411 recentsView.switchToScreenshot(() -> recentsView.finishRecentsAnimation(true, 412 this::startHomeInternal)); 413 } else { 414 startHomeInternal(); 415 } 416 } 417 startHomeInternal()418 private void startHomeInternal() { 419 LauncherAnimationRunner runner = new LauncherAnimationRunner( 420 getMainThreadHandler(), mAnimationToHomeFactory, true); 421 RemoteAnimationAdapterCompat adapterCompat = 422 new RemoteAnimationAdapterCompat(runner, HOME_APPEAR_DURATION, 0, 423 getIApplicationThread()); 424 startActivity(createHomeIntent(), 425 ActivityOptionsCompat.makeRemoteAnimation(adapterCompat).toBundle()); 426 } 427 428 private final RemoteAnimationFactory mAnimationToHomeFactory = 429 new RemoteAnimationFactory() { 430 @Override 431 public void onCreateAnimation(int transit, RemoteAnimationTargetCompat[] appTargets, 432 RemoteAnimationTargetCompat[] wallpaperTargets, 433 RemoteAnimationTargetCompat[] nonAppTargets, AnimationResult result) { 434 AnimatorPlaybackController controller = getStateManager() 435 .createAnimationToNewWorkspace(RecentsState.BG_LAUNCHER, HOME_APPEAR_DURATION); 436 controller.dispatchOnStart(); 437 438 RemoteAnimationTargets targets = new RemoteAnimationTargets( 439 appTargets, wallpaperTargets, nonAppTargets, MODE_OPENING); 440 for (RemoteAnimationTargetCompat app : targets.apps) { 441 new Transaction().setAlpha(app.leash.getSurfaceControl(), 1).apply(); 442 } 443 AnimatorSet anim = new AnimatorSet(); 444 anim.play(controller.getAnimationPlayer()); 445 anim.setDuration(HOME_APPEAR_DURATION); 446 result.setAnimation(anim, RecentsActivity.this, 447 () -> getStateManager().goToState(RecentsState.HOME, false), 448 true /* skipFirstFrame */); 449 } 450 }; 451 452 @Override collectStateHandlers(List<StateHandler> out)453 protected void collectStateHandlers(List<StateHandler> out) { 454 out.add(new FallbackRecentsStateController(this)); 455 } 456 457 @Override getStateManager()458 public StateManager<RecentsState> getStateManager() { 459 return mStateManager; 460 } 461 462 @Override dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)463 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 464 super.dump(prefix, fd, writer, args); 465 writer.println(prefix + "Misc:"); 466 dumpMisc(prefix + "\t", writer); 467 } 468 469 @Override createAtomicAnimationFactory()470 public AtomicAnimationFactory<RecentsState> createAtomicAnimationFactory() { 471 return new RecentsAtomicAnimationFactory<>(this); 472 } 473 resetStateListener()474 private AnimatorListenerAdapter resetStateListener() { 475 return new AnimatorListenerAdapter() { 476 @Override 477 public void onAnimationEnd(Animator animation) { 478 mFallbackRecentsView.resetTaskVisuals(); 479 mStateManager.reapplyState(); 480 } 481 }; 482 } 483 } 484