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.os.Trace.TRACE_TAG_APP; 19 import static android.view.RemoteAnimationTarget.MODE_CLOSING; 20 import static android.view.RemoteAnimationTarget.MODE_OPENING; 21 22 import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION; 23 import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_DURATION; 24 import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_PRE_DELAY; 25 import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL; 26 import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely; 27 import static com.android.quickstep.TaskUtils.taskIsATargetWithMode; 28 import static com.android.quickstep.TaskViewUtils.createRecentsWindowAnimator; 29 30 import android.animation.Animator; 31 import android.animation.AnimatorListenerAdapter; 32 import android.animation.AnimatorSet; 33 import android.app.ActivityOptions; 34 import android.content.Intent; 35 import android.content.res.Configuration; 36 import android.os.Bundle; 37 import android.os.Handler; 38 import android.os.Looper; 39 import android.os.Trace; 40 import android.util.Log; 41 import android.view.Display; 42 import android.view.RemoteAnimationAdapter; 43 import android.view.RemoteAnimationTarget; 44 import android.view.SurfaceControl.Transaction; 45 import android.view.View; 46 import android.window.RemoteTransition; 47 import android.window.SplashScreen; 48 49 import androidx.annotation.NonNull; 50 import androidx.annotation.Nullable; 51 52 import com.android.app.animation.Interpolators; 53 import com.android.launcher3.AbstractFloatingView; 54 import com.android.launcher3.DeviceProfile; 55 import com.android.launcher3.InvariantDeviceProfile; 56 import com.android.launcher3.LauncherAnimationRunner; 57 import com.android.launcher3.LauncherAnimationRunner.AnimationResult; 58 import com.android.launcher3.LauncherAnimationRunner.RemoteAnimationFactory; 59 import com.android.launcher3.R; 60 import com.android.launcher3.anim.AnimatorPlaybackController; 61 import com.android.launcher3.anim.PendingAnimation; 62 import com.android.launcher3.compat.AccessibilityManagerCompat; 63 import com.android.launcher3.model.data.ItemInfo; 64 import com.android.launcher3.statemanager.StateManager; 65 import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory; 66 import com.android.launcher3.statemanager.StateManager.StateHandler; 67 import com.android.launcher3.statemanager.StatefulActivity; 68 import com.android.launcher3.taskbar.FallbackTaskbarUIController; 69 import com.android.launcher3.taskbar.TaskbarManager; 70 import com.android.launcher3.testing.shared.TestProtocol; 71 import com.android.launcher3.util.ActivityOptionsWrapper; 72 import com.android.launcher3.util.ActivityTracker; 73 import com.android.launcher3.util.RunnableList; 74 import com.android.launcher3.util.SystemUiController; 75 import com.android.launcher3.util.Themes; 76 import com.android.launcher3.views.BaseDragLayer; 77 import com.android.launcher3.views.ScrimView; 78 import com.android.quickstep.fallback.FallbackRecentsStateController; 79 import com.android.quickstep.fallback.FallbackRecentsView; 80 import com.android.quickstep.fallback.RecentsDragLayer; 81 import com.android.quickstep.fallback.RecentsState; 82 import com.android.quickstep.util.RecentsAtomicAnimationFactory; 83 import com.android.quickstep.util.SplitSelectStateController; 84 import com.android.quickstep.util.TISBindHelper; 85 import com.android.quickstep.views.OverviewActionsView; 86 import com.android.quickstep.views.RecentsView; 87 import com.android.quickstep.views.TaskView; 88 89 import java.io.FileDescriptor; 90 import java.io.PrintWriter; 91 import java.util.List; 92 93 /** 94 * A recents activity that shows the recently launched tasks as swipable task cards. 95 * See {@link com.android.quickstep.views.RecentsView}. 96 */ 97 public final class RecentsActivity extends StatefulActivity<RecentsState> { 98 99 public static final ActivityTracker<RecentsActivity> ACTIVITY_TRACKER = 100 new ActivityTracker<>(); 101 102 private Handler mUiHandler = new Handler(Looper.getMainLooper()); 103 104 private static final long HOME_APPEAR_DURATION = 250; 105 private static final long RECENTS_ANIMATION_TIMEOUT = 1000; 106 107 private RecentsDragLayer mDragLayer; 108 private ScrimView mScrimView; 109 private FallbackRecentsView mFallbackRecentsView; 110 private OverviewActionsView mActionsView; 111 private TISBindHelper mTISBindHelper; 112 private @Nullable FallbackTaskbarUIController mTaskbarUIController; 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 private SplitSelectStateController mSplitSelectStateController; 124 125 /** 126 * Init drag layer and overview panel views. 127 */ setupViews()128 protected void setupViews() { 129 inflateRootView(R.layout.fallback_recents_activity); 130 setContentView(getRootView()); 131 mDragLayer = findViewById(R.id.drag_layer); 132 mScrimView = findViewById(R.id.scrim_view); 133 mFallbackRecentsView = findViewById(R.id.overview_panel); 134 mActionsView = findViewById(R.id.overview_actions_view); 135 getRootView().getSysUiScrim().getSysUIProgress().updateValue(0); 136 mSplitSelectStateController = 137 new SplitSelectStateController(this, mHandler, getStateManager(), 138 null /* depthController */, getStatsLogManager(), 139 SystemUiProxy.INSTANCE.get(this), RecentsModel.INSTANCE.get(this)); 140 mDragLayer.recreateControllers(); 141 mFallbackRecentsView.init(mActionsView, mSplitSelectStateController); 142 143 mTISBindHelper = new TISBindHelper(this, this::onTISConnected); 144 } 145 onTISConnected(TouchInteractionService.TISBinder binder)146 private void onTISConnected(TouchInteractionService.TISBinder binder) { 147 TaskbarManager taskbarManager = binder.getTaskbarManager(); 148 if (taskbarManager != null) { 149 taskbarManager.setActivity(this); 150 } 151 } 152 153 @Override runOnBindToTouchInteractionService(Runnable r)154 public void runOnBindToTouchInteractionService(Runnable r) { 155 mTISBindHelper.runOnBindToTouchInteractionService(r); 156 } 157 setTaskbarUIController(FallbackTaskbarUIController taskbarUIController)158 public void setTaskbarUIController(FallbackTaskbarUIController taskbarUIController) { 159 mTaskbarUIController = taskbarUIController; 160 } 161 getTaskbarUIController()162 public FallbackTaskbarUIController getTaskbarUIController() { 163 return mTaskbarUIController; 164 } 165 166 @Override onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig)167 public void onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig) { 168 onHandleConfigurationChanged(); 169 super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig); 170 } 171 172 @Override onNewIntent(Intent intent)173 protected void onNewIntent(Intent intent) { 174 super.onNewIntent(intent); 175 ACTIVITY_TRACKER.handleNewIntent(this); 176 } 177 178 @Override onHandleConfigurationChanged()179 protected void onHandleConfigurationChanged() { 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 final RecentsView recentsView = taskView.getRecentsView(); 246 if (recentsView == null) { 247 return super.getActivityLaunchOptions(v, item); 248 } 249 250 RunnableList onEndCallback = new RunnableList(); 251 252 mActivityLaunchAnimationRunner = new RemoteAnimationFactory() { 253 @Override 254 public void onAnimationStart(int transit, RemoteAnimationTarget[] appTargets, 255 RemoteAnimationTarget[] wallpaperTargets, 256 RemoteAnimationTarget[] nonAppTargets, AnimationResult result) { 257 mHandler.removeCallbacks(mAnimationStartTimeoutRunnable); 258 AnimatorSet anim = composeRecentsLaunchAnimator(recentsView, taskView, appTargets, 259 wallpaperTargets, nonAppTargets); 260 anim.addListener(resetStateListener()); 261 result.setAnimation(anim, RecentsActivity.this, onEndCallback::executeAllAndDestroy, 262 true /* skipFirstFrame */); 263 } 264 265 @Override 266 public void onAnimationCancelled() { 267 mHandler.removeCallbacks(mAnimationStartTimeoutRunnable); 268 onEndCallback.executeAllAndDestroy(); 269 } 270 }; 271 272 final LauncherAnimationRunner wrapper = new LauncherAnimationRunner( 273 mUiHandler, mActivityLaunchAnimationRunner, true /* startAtFrontOfQueue */); 274 final ActivityOptions options = ActivityOptions.makeRemoteAnimation( 275 new RemoteAnimationAdapter(wrapper, RECENTS_LAUNCH_DURATION, 276 RECENTS_LAUNCH_DURATION - STATUS_BAR_TRANSITION_DURATION 277 - STATUS_BAR_TRANSITION_PRE_DELAY), 278 new RemoteTransition(wrapper.toRemoteTransition(), getIApplicationThread(), 279 "LaunchFromRecents")); 280 final ActivityOptionsWrapper activityOptions = new ActivityOptionsWrapper(options, 281 onEndCallback); 282 activityOptions.options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON); 283 activityOptions.options.setLaunchDisplayId( 284 (v != null && v.getDisplay() != null) ? v.getDisplay().getDisplayId() 285 : Display.DEFAULT_DISPLAY); 286 activityOptions.options.setPendingIntentBackgroundActivityStartMode( 287 ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); 288 mHandler.postDelayed(mAnimationStartTimeoutRunnable, RECENTS_ANIMATION_TIMEOUT); 289 return activityOptions; 290 } 291 292 /** 293 * Composes the animations for a launch from the recents list if possible. 294 */ composeRecentsLaunchAnimator( @onNull RecentsView recentsView, @NonNull TaskView taskView, RemoteAnimationTarget[] appTargets, RemoteAnimationTarget[] wallpaperTargets, RemoteAnimationTarget[] nonAppTargets)295 private AnimatorSet composeRecentsLaunchAnimator( 296 @NonNull RecentsView recentsView, 297 @NonNull TaskView taskView, 298 RemoteAnimationTarget[] appTargets, 299 RemoteAnimationTarget[] wallpaperTargets, 300 RemoteAnimationTarget[] nonAppTargets) { 301 AnimatorSet target = new AnimatorSet(); 302 boolean activityClosing = taskIsATargetWithMode(appTargets, getTaskId(), MODE_CLOSING); 303 PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION); 304 createRecentsWindowAnimator(recentsView, taskView, !activityClosing, appTargets, 305 wallpaperTargets, nonAppTargets, null /* depthController */, pa); 306 target.play(pa.buildAnim()); 307 308 // Found a visible recents task that matches the opening app, lets launch the app from there 309 if (activityClosing) { 310 Animator adjacentAnimation = mFallbackRecentsView 311 .createAdjacentPageAnimForTaskLaunch(taskView); 312 adjacentAnimation.setInterpolator(Interpolators.TOUCH_RESPONSE); 313 adjacentAnimation.setDuration(RECENTS_LAUNCH_DURATION); 314 adjacentAnimation.addListener(resetStateListener()); 315 target.play(adjacentAnimation); 316 } 317 return target; 318 } 319 320 @Override onStart()321 protected void onStart() { 322 // Set the alpha to 1 before calling super, as it may get set back to 0 due to 323 // onActivityStart callback. 324 mFallbackRecentsView.setContentAlpha(1); 325 super.onStart(); 326 mFallbackRecentsView.updateLocusId(); 327 } 328 329 @Override onStop()330 protected void onStop() { 331 super.onStop(); 332 333 // Workaround for b/78520668, explicitly trim memory once UI is hidden 334 onTrimMemory(TRIM_MEMORY_UI_HIDDEN); 335 mFallbackRecentsView.updateLocusId(); 336 } 337 338 @Override onResume()339 protected void onResume() { 340 super.onResume(); 341 AccessibilityManagerCompat.sendStateEventToTest(getBaseContext(), OVERVIEW_STATE_ORDINAL); 342 } 343 344 @Override onCreate(Bundle savedInstanceState)345 protected void onCreate(Bundle savedInstanceState) { 346 super.onCreate(savedInstanceState); 347 348 mStateManager = new StateManager<>(this, RecentsState.BG_LAUNCHER); 349 350 initDeviceProfile(); 351 setupViews(); 352 353 getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW, 354 Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText)); 355 ACTIVITY_TRACKER.handleCreate(this); 356 } 357 358 @Override onStateSetEnd(RecentsState state)359 public void onStateSetEnd(RecentsState state) { 360 super.onStateSetEnd(state); 361 362 if (state == RecentsState.DEFAULT) { 363 AccessibilityManagerCompat.sendStateEventToTest(getBaseContext(), 364 OVERVIEW_STATE_ORDINAL); 365 } 366 } 367 368 /** 369 * Initialize/update the device profile. 370 */ initDeviceProfile()371 private void initDeviceProfile() { 372 mDeviceProfile = createDeviceProfile(); 373 onDeviceProfileInitiated(); 374 } 375 376 @Override onEnterAnimationComplete()377 public void onEnterAnimationComplete() { 378 super.onEnterAnimationComplete(); 379 // After the transition to home, enable the high-res thumbnail loader if it wasn't enabled 380 // as a part of quickstep, so that high-res thumbnails can load the next time we enter 381 // overview 382 RecentsModel.INSTANCE.get(this).getThumbnailCache() 383 .getHighResLoadingState().setVisible(true); 384 } 385 386 @Override onTrimMemory(int level)387 public void onTrimMemory(int level) { 388 super.onTrimMemory(level); 389 RecentsModel.INSTANCE.get(this).onTrimMemory(level); 390 } 391 392 @Override onDestroy()393 protected void onDestroy() { 394 super.onDestroy(); 395 ACTIVITY_TRACKER.onActivityDestroyed(this); 396 mActivityLaunchAnimationRunner = null; 397 mSplitSelectStateController.onDestroy(); 398 mTISBindHelper.onDestroy(); 399 } 400 401 @Override onBackPressed()402 public void onBackPressed() { 403 // TODO: Launch the task we came from 404 startHome(); 405 } 406 startHome()407 public void startHome() { 408 Log.d(TestProtocol.INCORRECT_HOME_STATE, "start home from recents activity"); 409 RecentsView recentsView = getOverviewPanel(); 410 recentsView.switchToScreenshot(() -> recentsView.finishRecentsAnimation(true, 411 this::startHomeInternal)); 412 } 413 startHomeInternal()414 private void startHomeInternal() { 415 LauncherAnimationRunner runner = new LauncherAnimationRunner( 416 getMainThreadHandler(), mAnimationToHomeFactory, true); 417 ActivityOptions options = ActivityOptions.makeRemoteAnimation( 418 new RemoteAnimationAdapter(runner, HOME_APPEAR_DURATION, 0), 419 new RemoteTransition(runner.toRemoteTransition(), getIApplicationThread(), 420 "StartHomeFromRecents")); 421 startHomeIntentSafely(this, options.toBundle()); 422 } 423 424 private final RemoteAnimationFactory mAnimationToHomeFactory = 425 (transit, appTargets, wallpaperTargets, nonAppTargets, result) -> { 426 AnimatorPlaybackController controller = 427 getStateManager().createAnimationToNewWorkspace( 428 RecentsState.BG_LAUNCHER, HOME_APPEAR_DURATION); 429 controller.dispatchOnStart(); 430 431 RemoteAnimationTargets targets = new RemoteAnimationTargets( 432 appTargets, wallpaperTargets, nonAppTargets, MODE_OPENING); 433 for (RemoteAnimationTarget app : targets.apps) { 434 new Transaction().setAlpha(app.leash, 1).apply(); 435 } 436 AnimatorSet anim = new AnimatorSet(); 437 anim.play(controller.getAnimationPlayer()); 438 anim.setDuration(HOME_APPEAR_DURATION); 439 result.setAnimation(anim, RecentsActivity.this, 440 () -> getStateManager().goToState(RecentsState.HOME, false), 441 true /* skipFirstFrame */); 442 }; 443 444 @Override collectStateHandlers(List<StateHandler> out)445 protected void collectStateHandlers(List<StateHandler> out) { 446 out.add(new FallbackRecentsStateController(this)); 447 } 448 449 @Override getStateManager()450 public StateManager<RecentsState> getStateManager() { 451 return mStateManager; 452 } 453 454 @Override dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)455 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 456 super.dump(prefix, fd, writer, args); 457 writer.println(prefix + "Misc:"); 458 dumpMisc(prefix + "\t", writer); 459 } 460 461 @Override createAtomicAnimationFactory()462 public AtomicAnimationFactory<RecentsState> createAtomicAnimationFactory() { 463 return new RecentsAtomicAnimationFactory<>(this); 464 } 465 466 @Override dispatchDeviceProfileChanged()467 public void dispatchDeviceProfileChanged() { 468 super.dispatchDeviceProfileChanged(); 469 Trace.instantForTrack(TRACE_TAG_APP, "RecentsActivity#DeviceProfileChanged", 470 getDeviceProfile().toSmallString()); 471 } 472 resetStateListener()473 private AnimatorListenerAdapter resetStateListener() { 474 return new AnimatorListenerAdapter() { 475 @Override 476 public void onAnimationEnd(Animator animation) { 477 mFallbackRecentsView.resetTaskVisuals(); 478 mStateManager.reapplyState(); 479 } 480 }; 481 } 482 483 public boolean canStartHomeSafely() { 484 OverviewCommandHelper overviewCommandHelper = mTISBindHelper.getOverviewCommandHelper(); 485 return overviewCommandHelper == null || overviewCommandHelper.canStartHomeSafely(); 486 } 487 } 488