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