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