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