1 /* 2 * Copyright (C) 2014 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 17 package com.android.systemui.recents; 18 19 import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY; 20 21 import android.app.Activity; 22 import android.app.ActivityOptions; 23 import android.app.TaskStackBuilder; 24 import android.app.WallpaperManager; 25 import android.content.BroadcastReceiver; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.IntentFilter; 29 import android.content.res.Configuration; 30 import android.net.Uri; 31 import android.os.Bundle; 32 import android.os.Handler; 33 import android.os.Looper; 34 import android.os.SystemClock; 35 import android.os.UserHandle; 36 import android.provider.Settings; 37 import android.util.Log; 38 import android.view.KeyEvent; 39 import android.view.View; 40 import android.view.ViewTreeObserver; 41 import android.view.ViewTreeObserver.OnPreDrawListener; 42 import android.view.WindowManager; 43 import android.view.WindowManager.LayoutParams; 44 45 import com.android.internal.colorextraction.ColorExtractor; 46 import com.android.internal.content.PackageMonitor; 47 import com.android.internal.logging.MetricsLogger; 48 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 49 import com.android.internal.util.LatencyTracker; 50 import com.android.systemui.DejankUtils; 51 import com.android.systemui.Dependency; 52 import com.android.systemui.Interpolators; 53 import com.android.systemui.R; 54 import com.android.systemui.colorextraction.SysuiColorExtractor; 55 import com.android.systemui.recents.events.EventBus; 56 import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent; 57 import com.android.systemui.recents.events.activity.ConfigurationChangedEvent; 58 import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted; 59 import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent; 60 import com.android.systemui.recents.events.activity.DockedTopTaskEvent; 61 import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent; 62 import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent; 63 import com.android.systemui.recents.events.activity.ExitRecentsWindowFirstAnimationFrameEvent; 64 import com.android.systemui.recents.events.activity.HideRecentsEvent; 65 import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent; 66 import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent; 67 import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent; 68 import com.android.systemui.recents.events.activity.PackagesChangedEvent; 69 import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent; 70 import com.android.systemui.recents.events.activity.ToggleRecentsEvent; 71 import com.android.systemui.recents.events.component.ActivityUnpinnedEvent; 72 import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent; 73 import com.android.systemui.recents.events.component.ScreenPinningRequestEvent; 74 import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent; 75 import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent; 76 import com.android.systemui.recents.events.ui.DeleteTaskDataEvent; 77 import com.android.systemui.recents.events.ui.HideIncompatibleAppOverlayEvent; 78 import com.android.systemui.recents.events.ui.RecentsDrawnEvent; 79 import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent; 80 import com.android.systemui.recents.events.ui.ShowIncompatibleAppOverlayEvent; 81 import com.android.systemui.recents.events.ui.StackViewScrolledEvent; 82 import com.android.systemui.recents.events.ui.TaskViewDismissedEvent; 83 import com.android.systemui.recents.events.ui.UserInteractionEvent; 84 import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent; 85 import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent; 86 import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent; 87 import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent; 88 import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent.Direction; 89 import com.android.systemui.recents.misc.SystemServicesProxy; 90 import com.android.systemui.recents.model.RecentsTaskLoadPlan; 91 import com.android.systemui.recents.model.RecentsTaskLoader; 92 import com.android.systemui.recents.model.TaskStack; 93 import com.android.systemui.recents.utilities.Utilities; 94 import com.android.systemui.recents.views.RecentsView; 95 import com.android.systemui.recents.views.SystemBarScrimViews; 96 import com.android.systemui.shared.recents.model.Task; 97 import com.android.systemui.shared.system.ActivityManagerWrapper; 98 99 import java.io.FileDescriptor; 100 import java.io.PrintWriter; 101 102 /** 103 * The main Recents activity that is started from RecentsComponent. 104 */ 105 public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreDrawListener, 106 ColorExtractor.OnColorsChangedListener { 107 108 private final static String TAG = "RecentsActivity"; 109 private final static boolean DEBUG = false; 110 111 public final static int EVENT_BUS_PRIORITY = LegacyRecentsImpl.EVENT_BUS_PRIORITY + 1; 112 public final static int INCOMPATIBLE_APP_ALPHA_DURATION = 150; 113 114 private PackageMonitor mPackageMonitor = new PackageMonitor() { 115 @Override 116 public void onPackageRemoved(String packageName, int uid) { 117 RecentsActivity.this.onPackageChanged(packageName, getChangingUserId()); 118 } 119 120 @Override 121 public boolean onPackageChanged(String packageName, int uid, String[] components) { 122 RecentsActivity.this.onPackageChanged(packageName, getChangingUserId()); 123 return true; 124 } 125 126 @Override 127 public void onPackageModified(String packageName) { 128 RecentsActivity.this.onPackageChanged(packageName, getChangingUserId()); 129 } 130 }; 131 private Handler mHandler = new Handler(); 132 private long mLastTabKeyEventTime; 133 private boolean mFinishedOnStartup; 134 private boolean mIgnoreAltTabRelease; 135 private boolean mIsVisible; 136 private boolean mRecentsStartRequested; 137 private Configuration mLastConfig; 138 139 // Top level views 140 private RecentsView mRecentsView; 141 private SystemBarScrimViews mScrimViews; 142 private View mIncompatibleAppOverlay; 143 144 // Runnables to finish the Recents activity 145 private Intent mHomeIntent; 146 147 // The trigger to automatically launch the current task 148 private int mFocusTimerDuration; 149 private final UserInteractionEvent mUserInteractionEvent = new UserInteractionEvent(); 150 151 // Theme and colors 152 private SysuiColorExtractor mColorExtractor; 153 private boolean mUsingDarkText; 154 155 /** 156 * A common Runnable to finish Recents by launching Home with an animation depending on the 157 * last activity launch state. Generally we always launch home when we exit Recents rather than 158 * just finishing the activity since we don't know what is behind Recents in the task stack. 159 */ 160 class LaunchHomeRunnable implements Runnable { 161 162 Intent mLaunchIntent; 163 ActivityOptions mOpts; 164 165 /** 166 * Creates a finish runnable that starts the specified intent. 167 */ LaunchHomeRunnable(Intent launchIntent, ActivityOptions opts)168 public LaunchHomeRunnable(Intent launchIntent, ActivityOptions opts) { 169 mLaunchIntent = launchIntent; 170 mOpts = opts; 171 } 172 173 @Override run()174 public void run() { 175 try { 176 mHandler.post(() -> { 177 ActivityOptions opts = mOpts; 178 if (opts == null) { 179 opts = ActivityOptions.makeCustomAnimation(RecentsActivity.this, 180 R.anim.recents_to_launcher_enter, R.anim.recents_to_launcher_exit); 181 } 182 startActivityAsUser(mLaunchIntent, opts.toBundle(), UserHandle.CURRENT); 183 }); 184 } catch (Exception e) { 185 Log.e(TAG, getString(R.string.recents_launch_error_message, "Home"), e); 186 } 187 } 188 } 189 190 /** 191 * Broadcast receiver to handle messages from the system 192 */ 193 final BroadcastReceiver mSystemBroadcastReceiver = new BroadcastReceiver() { 194 @Override 195 public void onReceive(Context ctx, Intent intent) { 196 String action = intent.getAction(); 197 if (action.equals(Intent.ACTION_SCREEN_OFF)) { 198 // When the screen turns off, dismiss Recents to Home 199 dismissRecentsToHomeIfVisible(false); 200 } else if (action.equals(Intent.ACTION_USER_SWITCHED)) { 201 // When switching users, dismiss Recents to Home similar to screen off 202 finish(); 203 } 204 } 205 }; 206 207 private final OnPreDrawListener mRecentsDrawnEventListener = 208 new ViewTreeObserver.OnPreDrawListener() { 209 @Override 210 public boolean onPreDraw() { 211 mRecentsView.getViewTreeObserver().removeOnPreDrawListener(this); 212 EventBus.getDefault().post(new RecentsDrawnEvent()); 213 if (LatencyTracker.isEnabled(getApplicationContext())) { 214 DejankUtils.postAfterTraversal(() -> LatencyTracker.getInstance( 215 getApplicationContext()).onActionEnd( 216 LatencyTracker.ACTION_TOGGLE_RECENTS)); 217 } 218 DejankUtils.postAfterTraversal(() -> { 219 LegacyRecentsImpl.getTaskLoader().startLoader(RecentsActivity.this); 220 LegacyRecentsImpl.getTaskLoader().getHighResThumbnailLoader().setVisible(true); 221 }); 222 return true; 223 } 224 }; 225 226 /** 227 * Dismisses recents if we are already visible and the intent is to toggle the recents view. 228 */ dismissRecentsToFocusedTask(int logCategory)229 boolean dismissRecentsToFocusedTask(int logCategory) { 230 SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices(); 231 if (ssp.isRecentsActivityVisible()) { 232 // If we have a focused Task, launch that Task now 233 if (mRecentsView.launchFocusedTask(logCategory)) return true; 234 } 235 return false; 236 } 237 238 /** 239 * Dismisses recents back to the launch target task. 240 */ dismissRecentsToLaunchTargetTaskOrHome()241 boolean dismissRecentsToLaunchTargetTaskOrHome() { 242 SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices(); 243 if (ssp.isRecentsActivityVisible()) { 244 // If we have a focused Task, launch that Task now 245 if (mRecentsView.launchPreviousTask()) return true; 246 // If none of the other cases apply, then just go Home 247 dismissRecentsToHome(true /* animateTaskViews */); 248 } 249 return false; 250 } 251 252 /** 253 * Dismisses recents if we are already visible and the intent is to toggle the recents view. 254 */ dismissRecentsToFocusedTaskOrHome()255 boolean dismissRecentsToFocusedTaskOrHome() { 256 SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices(); 257 if (ssp.isRecentsActivityVisible()) { 258 // If we have a focused Task, launch that Task now 259 if (mRecentsView.launchFocusedTask(0 /* logCategory */)) return true; 260 // If none of the other cases apply, then just go Home 261 dismissRecentsToHome(true /* animateTaskViews */); 262 return true; 263 } 264 return false; 265 } 266 267 /** 268 * Dismisses Recents directly to Home without checking whether it is currently visible. 269 */ dismissRecentsToHome(boolean animateTaskViews)270 void dismissRecentsToHome(boolean animateTaskViews) { 271 dismissRecentsToHome(animateTaskViews, null); 272 } 273 274 /** 275 * Dismisses Recents directly to Home without checking whether it is currently visible. 276 * 277 * @param overrideAnimation If not null, will override the default animation that is based on 278 * how Recents was launched. 279 */ dismissRecentsToHome(boolean animateTaskViews, ActivityOptions overrideAnimation)280 void dismissRecentsToHome(boolean animateTaskViews, ActivityOptions overrideAnimation) { 281 DismissRecentsToHomeAnimationStarted dismissEvent = 282 new DismissRecentsToHomeAnimationStarted(animateTaskViews); 283 dismissEvent.addPostAnimationCallback(new LaunchHomeRunnable(mHomeIntent, 284 overrideAnimation)); 285 ActivityManagerWrapper.getInstance().closeSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY); 286 EventBus.getDefault().send(dismissEvent); 287 } 288 289 /** Dismisses Recents directly to Home if we currently aren't transitioning. */ dismissRecentsToHomeIfVisible(boolean animated)290 boolean dismissRecentsToHomeIfVisible(boolean animated) { 291 SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices(); 292 if (ssp.isRecentsActivityVisible()) { 293 // Return to Home 294 dismissRecentsToHome(animated); 295 return true; 296 } 297 return false; 298 } 299 300 /** Called with the activity is first created. */ 301 @Override onCreate(Bundle savedInstanceState)302 public void onCreate(Bundle savedInstanceState) { 303 super.onCreate(savedInstanceState); 304 mFinishedOnStartup = false; 305 306 // In the case that the activity starts up before the Recents component has initialized 307 // (usually when debugging/pushing the SysUI apk), just finish this activity. 308 SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices(); 309 if (ssp == null) { 310 mFinishedOnStartup = true; 311 finish(); 312 return; 313 } 314 315 // Register this activity with the event bus 316 EventBus.getDefault().register(this, EVENT_BUS_PRIORITY); 317 318 // Initialize the package monitor 319 mPackageMonitor.register(this, Looper.getMainLooper(), UserHandle.ALL, 320 true /* externalStorage */); 321 322 // Select theme based on wallpaper colors 323 mColorExtractor = Dependency.get(SysuiColorExtractor.class); 324 mColorExtractor.addOnColorsChangedListener(this); 325 mUsingDarkText = mColorExtractor.getColors(ColorExtractor.TYPE_DARK, 326 WallpaperManager.FLAG_SYSTEM).supportsDarkText(); 327 setTheme(mUsingDarkText ? R.style.RecentsTheme_Wallpaper_Light 328 : R.style.RecentsTheme_Wallpaper); 329 330 // Set the Recents layout 331 setContentView(R.layout.recents); 332 takeKeyEvents(true); 333 mRecentsView = findViewById(R.id.recents_view); 334 mScrimViews = new SystemBarScrimViews(this); 335 getWindow().getAttributes().privateFlags |= 336 WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY; 337 if (LegacyRecentsImpl.getConfiguration().isLowRamDevice) { 338 getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); 339 } 340 341 mLastConfig = new Configuration(Utilities.getAppConfiguration(this)); 342 343 // Set the window background 344 mRecentsView.updateBackgroundScrim(getWindow(), isInMultiWindowMode()); 345 346 // Create the home intent runnable 347 mHomeIntent = new Intent(Intent.ACTION_MAIN, null); 348 mHomeIntent.addCategory(Intent.CATEGORY_HOME); 349 mHomeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 350 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 351 352 // Register the broadcast receiver to handle messages when the screen is turned off 353 IntentFilter filter = new IntentFilter(); 354 filter.addAction(Intent.ACTION_SCREEN_OFF); 355 filter.addAction(Intent.ACTION_USER_SWITCHED); 356 registerReceiver(mSystemBroadcastReceiver, filter); 357 358 getWindow().addPrivateFlags(LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION); 359 } 360 361 @Override onStart()362 protected void onStart() { 363 super.onStart(); 364 365 // Reload the stack view whenever we are made visible again 366 reloadStackView(); 367 368 // Notify that recents is now visible 369 EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, true)); 370 MetricsLogger.visible(this, MetricsEvent.OVERVIEW_ACTIVITY); 371 372 // Getting system scrim colors ignoring wallpaper visibility since it should never be grey. 373 ColorExtractor.GradientColors systemColors = mColorExtractor.getNeutralColors(); 374 // We don't want to interpolate colors because we're defining the initial state. 375 // Gradient should be set/ready when you open "Recents". 376 mRecentsView.setScrimColors(systemColors, false); 377 378 // Notify of the next draw 379 mRecentsView.getViewTreeObserver().addOnPreDrawListener(mRecentsDrawnEventListener); 380 381 // If Recents was restarted, then it should complete the enter animation with partially 382 // reset launch state with dock, app and home set to false 383 Object isRelaunching = getLastNonConfigurationInstance(); 384 if (isRelaunching != null && isRelaunching instanceof Boolean && (boolean) isRelaunching) { 385 RecentsActivityLaunchState launchState = LegacyRecentsImpl.getConfiguration().getLaunchState(); 386 launchState.launchedViaDockGesture = false; 387 launchState.launchedFromApp = false; 388 launchState.launchedFromHome = false; 389 onEnterAnimationComplete(); 390 } 391 mRecentsStartRequested = false; 392 } 393 394 @Override onColorsChanged(ColorExtractor colorExtractor, int which)395 public void onColorsChanged(ColorExtractor colorExtractor, int which) { 396 if ((which & WallpaperManager.FLAG_SYSTEM) != 0) { 397 ColorExtractor.GradientColors colors = mColorExtractor.getNeutralColors(); 398 boolean darkText = colors.supportsDarkText(); 399 if (darkText != mUsingDarkText) { 400 mUsingDarkText = darkText; 401 setTheme(mUsingDarkText ? R.style.RecentsTheme_Wallpaper_Light 402 : R.style.RecentsTheme_Wallpaper); 403 mRecentsView.reevaluateStyles(); 404 } 405 mRecentsView.setScrimColors(colors, true /* animated */); 406 } 407 } 408 409 /** 410 * Reloads the stack views upon launching Recents. 411 */ reloadStackView()412 private void reloadStackView() { 413 // If the Recents component has preloaded a load plan, then use that to prevent 414 // reconstructing the task stack 415 RecentsTaskLoader loader = LegacyRecentsImpl.getTaskLoader(); 416 RecentsTaskLoadPlan loadPlan = RecentsImpl.consumeInstanceLoadPlan(); 417 if (loadPlan == null) { 418 loadPlan = new RecentsTaskLoadPlan(this); 419 } 420 421 // Start loading tasks according to the load plan 422 RecentsConfiguration config = LegacyRecentsImpl.getConfiguration(); 423 RecentsActivityLaunchState launchState = config.getLaunchState(); 424 if (!loadPlan.hasTasks()) { 425 loader.preloadTasks(loadPlan, launchState.launchedToTaskId); 426 } 427 428 RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options(); 429 loadOpts.runningTaskId = launchState.launchedToTaskId; 430 loadOpts.numVisibleTasks = launchState.launchedNumVisibleTasks; 431 loadOpts.numVisibleTaskThumbnails = launchState.launchedNumVisibleThumbnails; 432 loader.loadTasks(loadPlan, loadOpts); 433 TaskStack stack = loadPlan.getTaskStack(); 434 mRecentsView.onReload(stack, mIsVisible); 435 436 // Update the nav bar scrim, but defer the animation until the enter-window event 437 boolean animateNavBarScrim = !launchState.launchedViaDockGesture; 438 mScrimViews.updateNavBarScrim(animateNavBarScrim, stack.getTaskCount() > 0, null); 439 440 // If this is a new instance relaunched by AM, without going through the normal mechanisms, 441 // then we have to manually trigger the enter animation state 442 boolean wasLaunchedByAm = !launchState.launchedFromHome && 443 !launchState.launchedFromApp; 444 if (wasLaunchedByAm) { 445 EventBus.getDefault().send(new EnterRecentsWindowAnimationCompletedEvent()); 446 } 447 448 // Keep track of whether we launched from the nav bar button or via alt-tab 449 if (launchState.launchedWithAltTab) { 450 MetricsLogger.count(this, "overview_trigger_alttab", 1); 451 } else { 452 MetricsLogger.count(this, "overview_trigger_nav_btn", 1); 453 } 454 455 // Keep track of whether we launched from an app or from home 456 if (launchState.launchedFromApp) { 457 Task launchTarget = stack.getLaunchTarget(); 458 int launchTaskIndexInStack = launchTarget != null 459 ? stack.indexOfTask(launchTarget) 460 : 0; 461 MetricsLogger.count(this, "overview_source_app", 1); 462 // If from an app, track the stack index of the app in the stack (for affiliated tasks) 463 MetricsLogger.histogram(this, "overview_source_app_index", launchTaskIndexInStack); 464 } else { 465 MetricsLogger.count(this, "overview_source_home", 1); 466 } 467 468 // Keep track of the total stack task count 469 int taskCount = mRecentsView.getStack().getTaskCount(); 470 MetricsLogger.histogram(this, "overview_task_count", taskCount); 471 472 // After we have resumed, set the visible state until the next onStop() call 473 mIsVisible = true; 474 } 475 476 @Override onEnterAnimationComplete()477 public void onEnterAnimationComplete() { 478 super.onEnterAnimationComplete(); 479 EventBus.getDefault().send(new EnterRecentsWindowAnimationCompletedEvent()); 480 481 // Workaround for b/64694148: The animation started callback is not made (see 482 // RecentsImpl.getThumbnailTransitionActivityOptions) so reset the transition-waiting state 483 // once the enter animation has completed. 484 EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false)); 485 } 486 487 @Override onRetainNonConfigurationInstance()488 public Object onRetainNonConfigurationInstance() { 489 return true; 490 } 491 492 @Override onPause()493 protected void onPause() { 494 super.onPause(); 495 496 mIgnoreAltTabRelease = false; 497 } 498 499 @Override onConfigurationChanged(Configuration newConfig)500 public void onConfigurationChanged(Configuration newConfig) { 501 super.onConfigurationChanged(newConfig); 502 503 // Notify of the config change 504 Configuration newDeviceConfiguration = Utilities.getAppConfiguration(this); 505 int numStackTasks = mRecentsView.getStack().getTaskCount(); 506 EventBus.getDefault().send(new ConfigurationChangedEvent(false /* fromMultiWindow */, 507 mLastConfig.orientation != newDeviceConfiguration.orientation, 508 mLastConfig.densityDpi != newDeviceConfiguration.densityDpi, numStackTasks > 0)); 509 510 mLastConfig.updateFrom(newDeviceConfiguration); 511 } 512 513 @Override onMultiWindowModeChanged(boolean isInMultiWindowMode)514 public void onMultiWindowModeChanged(boolean isInMultiWindowMode) { 515 super.onMultiWindowModeChanged(isInMultiWindowMode); 516 517 // Set the window background 518 mRecentsView.updateBackgroundScrim(getWindow(), isInMultiWindowMode); 519 520 // Reload the task stack view if we are still visible to pick up the change in tasks that 521 // result from entering/exiting multi-window 522 if (mIsVisible) { 523 reloadTaskStack(isInMultiWindowMode, true /* sendConfigChangedEvent */); 524 } 525 } 526 527 @Override onStop()528 protected void onStop() { 529 super.onStop(); 530 531 // Notify that recents is now hidden 532 mIsVisible = false; 533 EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, false)); 534 MetricsLogger.hidden(this, MetricsEvent.OVERVIEW_ACTIVITY); 535 LegacyRecentsImpl.getTaskLoader().getHighResThumbnailLoader().setVisible(false); 536 537 // When recents starts again before onStop, do not reset launch flags so entrance animation 538 // can run 539 if (!isChangingConfigurations() && !mRecentsStartRequested) { 540 // Workaround for b/22542869, if the RecentsActivity is started again, but without going 541 // through SystemUI, we need to reset the config launch flags to ensure that we do not 542 // wait on the system to send a signal that was never queued. 543 RecentsConfiguration config = LegacyRecentsImpl.getConfiguration(); 544 RecentsActivityLaunchState launchState = config.getLaunchState(); 545 launchState.reset(); 546 } 547 548 // Force a gc to attempt to clean up bitmap references more quickly (b/38258699) 549 LegacyRecentsImpl.getSystemServices().gc(); 550 } 551 552 @Override onDestroy()553 protected void onDestroy() { 554 super.onDestroy(); 555 556 // In the case that the activity finished on startup, just skip the unregistration below 557 if (mFinishedOnStartup) { 558 return; 559 } 560 561 // Unregister the system broadcast receivers 562 unregisterReceiver(mSystemBroadcastReceiver); 563 564 // Unregister any broadcast receivers for the task loader 565 mPackageMonitor.unregister(); 566 567 EventBus.getDefault().unregister(this); 568 } 569 570 @Override onAttachedToWindow()571 public void onAttachedToWindow() { 572 super.onAttachedToWindow(); 573 EventBus.getDefault().register(mScrimViews, EVENT_BUS_PRIORITY); 574 } 575 576 @Override onDetachedFromWindow()577 public void onDetachedFromWindow() { 578 super.onDetachedFromWindow(); 579 EventBus.getDefault().unregister(mScrimViews); 580 } 581 582 @Override onTrimMemory(int level)583 public void onTrimMemory(int level) { 584 RecentsTaskLoader loader = LegacyRecentsImpl.getTaskLoader(); 585 if (loader != null) { 586 loader.onTrimMemory(level); 587 } 588 } 589 590 @Override onKeyDown(int keyCode, KeyEvent event)591 public boolean onKeyDown(int keyCode, KeyEvent event) { 592 switch (keyCode) { 593 case KeyEvent.KEYCODE_TAB: { 594 int altTabKeyDelay = getResources().getInteger(R.integer.recents_alt_tab_key_delay); 595 boolean hasRepKeyTimeElapsed = (SystemClock.elapsedRealtime() - 596 mLastTabKeyEventTime) > altTabKeyDelay; 597 if (event.getRepeatCount() <= 0 || hasRepKeyTimeElapsed) { 598 // Focus the next task in the stack 599 final boolean backward = event.isShiftPressed(); 600 if (backward) { 601 EventBus.getDefault().send(new FocusPreviousTaskViewEvent()); 602 } else { 603 EventBus.getDefault().send(new FocusNextTaskViewEvent()); 604 } 605 mLastTabKeyEventTime = SystemClock.elapsedRealtime(); 606 607 // In the case of another ALT event, don't ignore the next release 608 if (event.isAltPressed()) { 609 mIgnoreAltTabRelease = false; 610 } 611 } 612 return true; 613 } 614 case KeyEvent.KEYCODE_DPAD_UP: 615 case KeyEvent.KEYCODE_DPAD_DOWN: 616 case KeyEvent.KEYCODE_DPAD_LEFT: 617 case KeyEvent.KEYCODE_DPAD_RIGHT: { 618 final Direction direction = NavigateTaskViewEvent.getDirectionFromKeyCode(keyCode); 619 EventBus.getDefault().send(new NavigateTaskViewEvent(direction)); 620 return true; 621 } 622 case KeyEvent.KEYCODE_DEL: 623 case KeyEvent.KEYCODE_FORWARD_DEL: { 624 if (event.getRepeatCount() <= 0) { 625 EventBus.getDefault().send(new DismissFocusedTaskViewEvent()); 626 627 // Keep track of deletions by keyboard 628 MetricsLogger.histogram(this, "overview_task_dismissed_source", 629 Constants.Metrics.DismissSourceKeyboard); 630 return true; 631 } 632 } 633 default: 634 break; 635 } 636 return super.onKeyDown(keyCode, event); 637 } 638 639 @Override onUserInteraction()640 public void onUserInteraction() { 641 EventBus.getDefault().send(mUserInteractionEvent); 642 } 643 644 @Override onBackPressed()645 public void onBackPressed() { 646 // Back behaves like the recents button so just trigger a toggle event 647 EventBus.getDefault().send(new ToggleRecentsEvent()); 648 } 649 650 /**** EventBus events ****/ 651 onBusEvent(ToggleRecentsEvent event)652 public final void onBusEvent(ToggleRecentsEvent event) { 653 RecentsActivityLaunchState launchState = LegacyRecentsImpl.getConfiguration().getLaunchState(); 654 if (launchState.launchedFromHome) { 655 dismissRecentsToHome(true /* animateTaskViews */); 656 } else { 657 dismissRecentsToLaunchTargetTaskOrHome(); 658 } 659 } 660 onBusEvent(RecentsActivityStartingEvent event)661 public final void onBusEvent(RecentsActivityStartingEvent event) { 662 mRecentsStartRequested = true; 663 } 664 onBusEvent(HideRecentsEvent event)665 public final void onBusEvent(HideRecentsEvent event) { 666 if (event.triggeredFromAltTab) { 667 // If we are hiding from releasing Alt-Tab, dismiss Recents to the focused app 668 if (!mIgnoreAltTabRelease) { 669 dismissRecentsToFocusedTaskOrHome(); 670 } 671 } else if (event.triggeredFromHomeKey) { 672 dismissRecentsToHome(true /* animateTaskViews */); 673 674 // Cancel any pending dozes 675 EventBus.getDefault().send(mUserInteractionEvent); 676 } else { 677 // Do nothing 678 } 679 } 680 onBusEvent(EnterRecentsWindowLastAnimationFrameEvent event)681 public final void onBusEvent(EnterRecentsWindowLastAnimationFrameEvent event) { 682 mRecentsView.getViewTreeObserver().addOnPreDrawListener(this); 683 mRecentsView.invalidate(); 684 } 685 onBusEvent(ExitRecentsWindowFirstAnimationFrameEvent event)686 public final void onBusEvent(ExitRecentsWindowFirstAnimationFrameEvent event) { 687 mRecentsView.getViewTreeObserver().addOnPreDrawListener(this); 688 mRecentsView.invalidate(); 689 } 690 onBusEvent(DockedFirstAnimationFrameEvent event)691 public final void onBusEvent(DockedFirstAnimationFrameEvent event) { 692 mRecentsView.getViewTreeObserver().addOnPreDrawListener(this); 693 mRecentsView.invalidate(); 694 } 695 onBusEvent(CancelEnterRecentsWindowAnimationEvent event)696 public final void onBusEvent(CancelEnterRecentsWindowAnimationEvent event) { 697 RecentsActivityLaunchState launchState = LegacyRecentsImpl.getConfiguration().getLaunchState(); 698 int launchToTaskId = launchState.launchedToTaskId; 699 if (launchToTaskId != -1 && 700 (event.launchTask == null || launchToTaskId != event.launchTask.key.id)) { 701 ActivityManagerWrapper am = ActivityManagerWrapper.getInstance(); 702 am.cancelWindowTransition(launchState.launchedToTaskId); 703 } 704 } 705 onBusEvent(ShowApplicationInfoEvent event)706 public final void onBusEvent(ShowApplicationInfoEvent event) { 707 // Create a new task stack with the application info details activity 708 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, 709 Uri.fromParts("package", event.task.key.getComponent().getPackageName(), null)); 710 intent.setComponent(intent.resolveActivity(getPackageManager())); 711 TaskStackBuilder.create(this) 712 .addNextIntentWithParentStack(intent).startActivities(null, 713 new UserHandle(event.task.key.userId)); 714 715 // Keep track of app-info invocations 716 MetricsLogger.count(this, "overview_app_info", 1); 717 } 718 onBusEvent(ShowIncompatibleAppOverlayEvent event)719 public final void onBusEvent(ShowIncompatibleAppOverlayEvent event) { 720 if (mIncompatibleAppOverlay == null) { 721 mIncompatibleAppOverlay = Utilities.findViewStubById(this, 722 R.id.incompatible_app_overlay_stub).inflate(); 723 mIncompatibleAppOverlay.setWillNotDraw(false); 724 mIncompatibleAppOverlay.setVisibility(View.VISIBLE); 725 } 726 mIncompatibleAppOverlay.animate() 727 .alpha(1f) 728 .setDuration(INCOMPATIBLE_APP_ALPHA_DURATION) 729 .setInterpolator(Interpolators.ALPHA_IN) 730 .start(); 731 } 732 onBusEvent(HideIncompatibleAppOverlayEvent event)733 public final void onBusEvent(HideIncompatibleAppOverlayEvent event) { 734 if (mIncompatibleAppOverlay != null) { 735 mIncompatibleAppOverlay.animate() 736 .alpha(0f) 737 .setDuration(INCOMPATIBLE_APP_ALPHA_DURATION) 738 .setInterpolator(Interpolators.ALPHA_OUT) 739 .start(); 740 } 741 } 742 onBusEvent(DeleteTaskDataEvent event)743 public final void onBusEvent(DeleteTaskDataEvent event) { 744 // Remove any stored data from the loader 745 RecentsTaskLoader loader = LegacyRecentsImpl.getTaskLoader(); 746 loader.deleteTaskData(event.task, false); 747 748 // Remove the task from activity manager 749 ActivityManagerWrapper.getInstance().removeTask(event.task.key.id); 750 } 751 onBusEvent(TaskViewDismissedEvent event)752 public final void onBusEvent(TaskViewDismissedEvent event) { 753 mRecentsView.updateScrimOpacity(); 754 } 755 onBusEvent(AllTaskViewsDismissedEvent event)756 public final void onBusEvent(AllTaskViewsDismissedEvent event) { 757 SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices(); 758 if (ssp.hasDockedTask()) { 759 mRecentsView.showEmptyView(event.msgResId); 760 } else { 761 // Just go straight home (no animation necessary because there are no more task views) 762 dismissRecentsToHome(false /* animateTaskViews */); 763 } 764 765 // Keep track of all-deletions 766 MetricsLogger.count(this, "overview_task_all_dismissed", 1); 767 } 768 onBusEvent(LaunchTaskSucceededEvent event)769 public final void onBusEvent(LaunchTaskSucceededEvent event) { 770 MetricsLogger.histogram(this, "overview_task_launch_index", event.taskIndexFromStackFront); 771 } 772 onBusEvent(LaunchTaskFailedEvent event)773 public final void onBusEvent(LaunchTaskFailedEvent event) { 774 // Return to Home 775 dismissRecentsToHome(true /* animateTaskViews */); 776 777 MetricsLogger.count(this, "overview_task_launch_failed", 1); 778 } 779 onBusEvent(ScreenPinningRequestEvent event)780 public final void onBusEvent(ScreenPinningRequestEvent event) { 781 MetricsLogger.count(this, "overview_screen_pinned", 1); 782 } 783 onBusEvent(StackViewScrolledEvent event)784 public final void onBusEvent(StackViewScrolledEvent event) { 785 // Once the user has scrolled while holding alt-tab, then we should ignore the release of 786 // the key 787 mIgnoreAltTabRelease = true; 788 } 789 onBusEvent(final DockedTopTaskEvent event)790 public final void onBusEvent(final DockedTopTaskEvent event) { 791 mRecentsView.getViewTreeObserver().addOnPreDrawListener(mRecentsDrawnEventListener); 792 mRecentsView.invalidate(); 793 } 794 onBusEvent(final ActivityUnpinnedEvent event)795 public final void onBusEvent(final ActivityUnpinnedEvent event) { 796 if (mIsVisible) { 797 // Skip the configuration change event as the PiP activity does not actually affect the 798 // config of recents 799 reloadTaskStack(isInMultiWindowMode(), false /* sendConfigChangedEvent */); 800 } 801 } 802 reloadTaskStack(boolean isInMultiWindowMode, boolean sendConfigChangedEvent)803 private void reloadTaskStack(boolean isInMultiWindowMode, boolean sendConfigChangedEvent) { 804 // Reload the task stack completely 805 RecentsConfiguration config = LegacyRecentsImpl.getConfiguration(); 806 RecentsActivityLaunchState launchState = config.getLaunchState(); 807 RecentsTaskLoader loader = LegacyRecentsImpl.getTaskLoader(); 808 RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(this); 809 loader.preloadTasks(loadPlan, -1 /* runningTaskId */); 810 811 RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options(); 812 loadOpts.numVisibleTasks = launchState.launchedNumVisibleTasks; 813 loadOpts.numVisibleTaskThumbnails = launchState.launchedNumVisibleThumbnails; 814 loader.loadTasks(loadPlan, loadOpts); 815 816 TaskStack stack = loadPlan.getTaskStack(); 817 int numStackTasks = stack.getTaskCount(); 818 boolean showDeferredAnimation = numStackTasks > 0; 819 820 if (sendConfigChangedEvent) { 821 EventBus.getDefault().send(new ConfigurationChangedEvent(true /* fromMultiWindow */, 822 false /* fromDeviceOrientationChange */, false /* fromDisplayDensityChange */, 823 numStackTasks > 0)); 824 } 825 EventBus.getDefault().send(new MultiWindowStateChangedEvent(isInMultiWindowMode, 826 showDeferredAnimation, stack)); 827 } 828 829 @Override onPreDraw()830 public boolean onPreDraw() { 831 mRecentsView.getViewTreeObserver().removeOnPreDrawListener(this); 832 return true; 833 } 834 onPackageChanged(String packageName, int userId)835 public void onPackageChanged(String packageName, int userId) { 836 LegacyRecentsImpl.getTaskLoader().onPackageChanged(packageName); 837 EventBus.getDefault().send(new PackagesChangedEvent(packageName, userId)); 838 } 839 840 @Override dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)841 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 842 super.dump(prefix, fd, writer, args); 843 EventBus.getDefault().dump(prefix, writer); 844 LegacyRecentsImpl.getTaskLoader().dump(prefix, writer); 845 846 String id = Integer.toHexString(System.identityHashCode(this)); 847 848 writer.print(prefix); writer.print(TAG); 849 writer.print(" visible="); writer.print(mIsVisible ? "Y" : "N"); 850 writer.print(" currentTime="); writer.print(System.currentTimeMillis()); 851 writer.print(" [0x"); writer.print(id); writer.print("]"); 852 writer.println(); 853 854 if (mRecentsView != null) { 855 mRecentsView.dump(prefix, writer); 856 } 857 } 858 } 859