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