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