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.graphics.SysUiScrim.SYSUI_PROGRESS; 26 import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL; 27 import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely; 28 import static com.android.quickstep.TaskUtils.taskIsATargetWithMode; 29 import static com.android.quickstep.TaskViewUtils.createRecentsWindowAnimator; 30 31 import android.animation.Animator; 32 import android.animation.AnimatorListenerAdapter; 33 import android.animation.AnimatorSet; 34 import android.app.ActivityOptions; 35 import android.content.Intent; 36 import android.content.res.Configuration; 37 import android.os.Bundle; 38 import android.os.Handler; 39 import android.os.Looper; 40 import android.os.Trace; 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.Nullable; 50 51 import com.android.launcher3.AbstractFloatingView; 52 import com.android.launcher3.DeviceProfile; 53 import com.android.launcher3.InvariantDeviceProfile; 54 import com.android.launcher3.LauncherAnimationRunner; 55 import com.android.launcher3.LauncherAnimationRunner.AnimationResult; 56 import com.android.launcher3.LauncherAnimationRunner.RemoteAnimationFactory; 57 import com.android.launcher3.R; 58 import com.android.launcher3.anim.AnimatorPlaybackController; 59 import com.android.launcher3.anim.Interpolators; 60 import com.android.launcher3.anim.PendingAnimation; 61 import com.android.launcher3.compat.AccessibilityManagerCompat; 62 import com.android.launcher3.model.data.ItemInfo; 63 import com.android.launcher3.statemanager.StateManager; 64 import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory; 65 import com.android.launcher3.statemanager.StateManager.StateHandler; 66 import com.android.launcher3.statemanager.StatefulActivity; 67 import com.android.launcher3.taskbar.FallbackTaskbarUIController; 68 import com.android.launcher3.taskbar.TaskbarManager; 69 import com.android.launcher3.util.ActivityOptionsWrapper; 70 import com.android.launcher3.util.ActivityTracker; 71 import com.android.launcher3.util.RunnableList; 72 import com.android.launcher3.util.SystemUiController; 73 import com.android.launcher3.util.Themes; 74 import com.android.launcher3.views.BaseDragLayer; 75 import com.android.launcher3.views.ScrimView; 76 import com.android.quickstep.fallback.FallbackRecentsStateController; 77 import com.android.quickstep.fallback.FallbackRecentsView; 78 import com.android.quickstep.fallback.RecentsDragLayer; 79 import com.android.quickstep.fallback.RecentsState; 80 import com.android.quickstep.util.RecentsAtomicAnimationFactory; 81 import com.android.quickstep.util.SplitSelectStateController; 82 import com.android.quickstep.util.TISBindHelper; 83 import com.android.quickstep.views.OverviewActionsView; 84 import com.android.quickstep.views.RecentsView; 85 import com.android.quickstep.views.TaskView; 86 87 import java.io.FileDescriptor; 88 import java.io.PrintWriter; 89 import java.util.List; 90 91 /** 92 * A recents activity that shows the recently launched tasks as swipable task cards. 93 * See {@link com.android.quickstep.views.RecentsView}. 94 */ 95 public final class RecentsActivity extends StatefulActivity<RecentsState> { 96 97 public static final ActivityTracker<RecentsActivity> ACTIVITY_TRACKER = 98 new ActivityTracker<>(); 99 100 private Handler mUiHandler = new Handler(Looper.getMainLooper()); 101 102 private static final long HOME_APPEAR_DURATION = 250; 103 private static final long RECENTS_ANIMATION_TIMEOUT = 1000; 104 105 private RecentsDragLayer mDragLayer; 106 private ScrimView mScrimView; 107 private FallbackRecentsView mFallbackRecentsView; 108 private OverviewActionsView mActionsView; 109 private TISBindHelper mTISBindHelper; 110 private @Nullable TaskbarManager mTaskbarManager; 111 private @Nullable FallbackTaskbarUIController mTaskbarUIController; 112 113 private StateManager<RecentsState> mStateManager; 114 115 // Strong refs to runners which are cleared when the activity is destroyed 116 private RemoteAnimationFactory mActivityLaunchAnimationRunner; 117 118 // For handling degenerate cases where starting an activity doesn't actually trigger the remote 119 // animation callback 120 private final Handler mHandler = new Handler(); 121 private final Runnable mAnimationStartTimeoutRunnable = this::onAnimationStartTimeout; 122 123 /** 124 * Init drag layer and overview panel views. 125 */ setupViews()126 protected void setupViews() { 127 inflateRootView(R.layout.fallback_recents_activity); 128 setContentView(getRootView()); 129 mDragLayer = findViewById(R.id.drag_layer); 130 mScrimView = findViewById(R.id.scrim_view); 131 mFallbackRecentsView = findViewById(R.id.overview_panel); 132 mActionsView = findViewById(R.id.overview_actions_view); 133 SYSUI_PROGRESS.set(getRootView().getSysUiScrim(), 0f); 134 135 SplitSelectStateController controller = 136 new SplitSelectStateController(this, mHandler, getStateManager(), 137 null /* depthController */, getStatsLogManager(), 138 SystemUiProxy.INSTANCE.get(this), RecentsModel.INSTANCE.get(this)); 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 onHandleConfigurationChanged(); 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 @Override onHandleConfigurationChanged()176 protected void onHandleConfigurationChanged() { 177 initDeviceProfile(); 178 179 AbstractFloatingView.closeOpenViews(this, true, 180 AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE); 181 dispatchDeviceProfileChanged(); 182 183 reapplyUi(); 184 mDragLayer.recreateControllers(); 185 } 186 187 /** 188 * Generate the device profile to use in this activity. 189 * @return device profile 190 */ createDeviceProfile()191 protected DeviceProfile createDeviceProfile() { 192 DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(this).getDeviceProfile(this); 193 194 // In case we are reusing IDP, create a copy so that we don't conflict with Launcher 195 // activity. 196 return (mDragLayer != null) && isInMultiWindowMode() 197 ? dp.getMultiWindowProfile(this, getMultiWindowDisplaySize()) 198 : dp.copy(this); 199 } 200 201 @Override getDragLayer()202 public BaseDragLayer getDragLayer() { 203 return mDragLayer; 204 } 205 getScrimView()206 public ScrimView getScrimView() { 207 return mScrimView; 208 } 209 210 @Override getOverviewPanel()211 public <T extends View> T getOverviewPanel() { 212 return (T) mFallbackRecentsView; 213 } 214 getActionsView()215 public OverviewActionsView getActionsView() { 216 return mActionsView; 217 } 218 219 @Override returnToHomescreen()220 public void returnToHomescreen() { 221 super.returnToHomescreen(); 222 // TODO(b/137318995) This should go home, but doing so removes freeform windows 223 } 224 225 /** 226 * Called if the remote animation callback from #getActivityLaunchOptions() hasn't called back 227 * in a reasonable time due to a conflict with the recents animation. 228 */ onAnimationStartTimeout()229 private void onAnimationStartTimeout() { 230 if (mActivityLaunchAnimationRunner != null) { 231 mActivityLaunchAnimationRunner.onAnimationCancelled(); 232 } 233 } 234 235 @Override getActivityLaunchOptions(final View v, @Nullable ItemInfo item)236 public ActivityOptionsWrapper getActivityLaunchOptions(final View v, @Nullable ItemInfo item) { 237 if (!(v instanceof TaskView)) { 238 return super.getActivityLaunchOptions(v, item); 239 } 240 241 final TaskView taskView = (TaskView) v; 242 RunnableList onEndCallback = new RunnableList(); 243 244 mActivityLaunchAnimationRunner = new RemoteAnimationFactory() { 245 @Override 246 public void onAnimationStart(int transit, RemoteAnimationTarget[] appTargets, 247 RemoteAnimationTarget[] wallpaperTargets, 248 RemoteAnimationTarget[] nonAppTargets, AnimationResult result) { 249 mHandler.removeCallbacks(mAnimationStartTimeoutRunnable); 250 AnimatorSet anim = composeRecentsLaunchAnimator(taskView, appTargets, 251 wallpaperTargets, nonAppTargets); 252 anim.addListener(resetStateListener()); 253 result.setAnimation(anim, RecentsActivity.this, onEndCallback::executeAllAndDestroy, 254 true /* skipFirstFrame */); 255 } 256 257 @Override 258 public void onAnimationCancelled() { 259 mHandler.removeCallbacks(mAnimationStartTimeoutRunnable); 260 onEndCallback.executeAllAndDestroy(); 261 } 262 }; 263 264 final LauncherAnimationRunner wrapper = new LauncherAnimationRunner( 265 mUiHandler, mActivityLaunchAnimationRunner, true /* startAtFrontOfQueue */); 266 final ActivityOptions options = ActivityOptions.makeRemoteAnimation( 267 new RemoteAnimationAdapter(wrapper, RECENTS_LAUNCH_DURATION, 268 RECENTS_LAUNCH_DURATION - STATUS_BAR_TRANSITION_DURATION 269 - STATUS_BAR_TRANSITION_PRE_DELAY), 270 new RemoteTransition(wrapper.toRemoteTransition(), getIApplicationThread())); 271 final ActivityOptionsWrapper activityOptions = new ActivityOptionsWrapper(options, 272 onEndCallback); 273 activityOptions.options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON); 274 activityOptions.options.setLaunchDisplayId( 275 (v != null && v.getDisplay() != null) ? v.getDisplay().getDisplayId() 276 : Display.DEFAULT_DISPLAY); 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, RemoteAnimationTarget[] appTargets, RemoteAnimationTarget[] wallpaperTargets, RemoteAnimationTarget[] nonAppTargets)284 private AnimatorSet composeRecentsLaunchAnimator(TaskView taskView, 285 RemoteAnimationTarget[] appTargets, 286 RemoteAnimationTarget[] wallpaperTargets, 287 RemoteAnimationTarget[] 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 initDeviceProfile(); 338 setupViews(); 339 340 getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW, 341 Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText)); 342 ACTIVITY_TRACKER.handleCreate(this); 343 } 344 345 @Override onStateSetEnd(RecentsState state)346 public void onStateSetEnd(RecentsState state) { 347 super.onStateSetEnd(state); 348 349 if (state == RecentsState.DEFAULT) { 350 AccessibilityManagerCompat.sendStateEventToTest(getBaseContext(), 351 OVERVIEW_STATE_ORDINAL); 352 } 353 } 354 355 /** 356 * Initialize/update the device profile. 357 */ initDeviceProfile()358 private void initDeviceProfile() { 359 mDeviceProfile = createDeviceProfile(); 360 onDeviceProfileInitiated(); 361 } 362 363 @Override onEnterAnimationComplete()364 public void onEnterAnimationComplete() { 365 super.onEnterAnimationComplete(); 366 // After the transition to home, enable the high-res thumbnail loader if it wasn't enabled 367 // as a part of quickstep, so that high-res thumbnails can load the next time we enter 368 // overview 369 RecentsModel.INSTANCE.get(this).getThumbnailCache() 370 .getHighResLoadingState().setVisible(true); 371 } 372 373 @Override onTrimMemory(int level)374 public void onTrimMemory(int level) { 375 super.onTrimMemory(level); 376 RecentsModel.INSTANCE.get(this).onTrimMemory(level); 377 } 378 379 @Override onDestroy()380 protected void onDestroy() { 381 super.onDestroy(); 382 ACTIVITY_TRACKER.onActivityDestroyed(this); 383 mActivityLaunchAnimationRunner = null; 384 385 mTISBindHelper.onDestroy(); 386 if (mTaskbarManager != null) { 387 mTaskbarManager.clearActivity(this); 388 } 389 } 390 391 @Override onBackPressed()392 public void onBackPressed() { 393 // TODO: Launch the task we came from 394 startHome(); 395 } 396 startHome()397 public void startHome() { 398 RecentsView recentsView = getOverviewPanel(); 399 recentsView.switchToScreenshot(() -> recentsView.finishRecentsAnimation(true, 400 this::startHomeInternal)); 401 } 402 startHomeInternal()403 private void startHomeInternal() { 404 LauncherAnimationRunner runner = new LauncherAnimationRunner( 405 getMainThreadHandler(), mAnimationToHomeFactory, true); 406 ActivityOptions options = ActivityOptions.makeRemoteAnimation( 407 new RemoteAnimationAdapter(runner, HOME_APPEAR_DURATION, 0), 408 new RemoteTransition(runner.toRemoteTransition(), getIApplicationThread())); 409 startHomeIntentSafely(this, options.toBundle()); 410 } 411 412 private final RemoteAnimationFactory mAnimationToHomeFactory = 413 (transit, appTargets, wallpaperTargets, nonAppTargets, result) -> { 414 AnimatorPlaybackController controller = 415 getStateManager().createAnimationToNewWorkspace( 416 RecentsState.BG_LAUNCHER, HOME_APPEAR_DURATION); 417 controller.dispatchOnStart(); 418 419 RemoteAnimationTargets targets = new RemoteAnimationTargets( 420 appTargets, wallpaperTargets, nonAppTargets, MODE_OPENING); 421 for (RemoteAnimationTarget app : targets.apps) { 422 new Transaction().setAlpha(app.leash, 1).apply(); 423 } 424 AnimatorSet anim = new AnimatorSet(); 425 anim.play(controller.getAnimationPlayer()); 426 anim.setDuration(HOME_APPEAR_DURATION); 427 result.setAnimation(anim, RecentsActivity.this, 428 () -> getStateManager().goToState(RecentsState.HOME, false), 429 true /* skipFirstFrame */); 430 }; 431 432 @Override collectStateHandlers(List<StateHandler> out)433 protected void collectStateHandlers(List<StateHandler> out) { 434 out.add(new FallbackRecentsStateController(this)); 435 } 436 437 @Override getStateManager()438 public StateManager<RecentsState> getStateManager() { 439 return mStateManager; 440 } 441 442 @Override dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)443 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 444 super.dump(prefix, fd, writer, args); 445 writer.println(prefix + "Misc:"); 446 dumpMisc(prefix + "\t", writer); 447 } 448 449 @Override createAtomicAnimationFactory()450 public AtomicAnimationFactory<RecentsState> createAtomicAnimationFactory() { 451 return new RecentsAtomicAnimationFactory<>(this); 452 } 453 454 @Override dispatchDeviceProfileChanged()455 public void dispatchDeviceProfileChanged() { 456 super.dispatchDeviceProfileChanged(); 457 Trace.instantForTrack(TRACE_TAG_APP, "RecentsActivity#DeviceProfileChanged", 458 getDeviceProfile().toSmallString()); 459 } 460 resetStateListener()461 private AnimatorListenerAdapter resetStateListener() { 462 return new AnimatorListenerAdapter() { 463 @Override 464 public void onAnimationEnd(Animator animation) { 465 mFallbackRecentsView.resetTaskVisuals(); 466 mStateManager.reapplyState(); 467 } 468 }; 469 } 470 } 471