RecentsActivity.java revision f1fbd77cf057e43926f9a0347692611386d09f40
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.appwidget.AppWidgetHostView; 22import android.appwidget.AppWidgetManager; 23import android.appwidget.AppWidgetProviderInfo; 24import android.content.BroadcastReceiver; 25import android.content.Context; 26import android.content.Intent; 27import android.content.IntentFilter; 28import android.os.Bundle; 29import android.os.UserHandle; 30import android.util.Pair; 31import android.view.KeyEvent; 32import android.view.View; 33import android.view.ViewStub; 34import com.android.systemui.R; 35import com.android.systemui.recents.model.RecentsTaskLoader; 36import com.android.systemui.recents.model.SpaceNode; 37import com.android.systemui.recents.model.TaskStack; 38import com.android.systemui.recents.views.FullscreenTransitionOverlayView; 39import com.android.systemui.recents.views.RecentsView; 40import com.android.systemui.recents.views.SystemBarScrimViews; 41import com.android.systemui.recents.views.ViewAnimation; 42 43import java.lang.reflect.InvocationTargetException; 44import java.util.ArrayList; 45 46/* Activity */ 47public class RecentsActivity extends Activity implements RecentsView.RecentsViewCallbacks, 48 RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks, 49 FullscreenTransitionOverlayView.FullScreenTransitionViewCallbacks { 50 51 RecentsView mRecentsView; 52 SystemBarScrimViews mScrimViews; 53 ViewStub mEmptyViewStub; 54 View mEmptyView; 55 ViewStub mFullscreenOverlayStub; 56 FullscreenTransitionOverlayView mFullScreenOverlayView; 57 58 RecentsConfiguration mConfig; 59 60 RecentsAppWidgetHost mAppWidgetHost; 61 AppWidgetProviderInfo mSearchAppWidgetInfo; 62 AppWidgetHostView mSearchAppWidgetHostView; 63 64 boolean mVisible; 65 boolean mTaskLaunched; 66 67 // Runnables to finish the Recents activity 68 FinishRecentsRunnable mFinishRunnable = new FinishRecentsRunnable(true); 69 FinishRecentsRunnable mFinishWithoutAnimationRunnable = new FinishRecentsRunnable(false); 70 FinishRecentsRunnable mFinishLaunchHomeRunnable; 71 72 /** 73 * A Runnable to finish Recents either with/without a transition, and either by calling finish() 74 * or just launching the specified intent. 75 */ 76 class FinishRecentsRunnable implements Runnable { 77 boolean mUseCustomFinishTransition; 78 Intent mLaunchIntent; 79 ActivityOptions mLaunchOpts; 80 81 public FinishRecentsRunnable(boolean withTransition) { 82 mUseCustomFinishTransition = withTransition; 83 } 84 85 public FinishRecentsRunnable(Intent launchIntent, ActivityOptions opts) { 86 mLaunchIntent = launchIntent; 87 mLaunchOpts = opts; 88 } 89 90 @Override 91 public void run() { 92 // Mark Recents as no longer visible 93 AlternateRecentsComponent.notifyVisibilityChanged(false); 94 // Finish Recents 95 if (mLaunchIntent != null) { 96 if (mLaunchOpts != null) { 97 startActivityAsUser(mLaunchIntent, new UserHandle(UserHandle.USER_CURRENT)); 98 } else { 99 startActivityAsUser(mLaunchIntent, mLaunchOpts.toBundle(), 100 new UserHandle(UserHandle.USER_CURRENT)); 101 } 102 } else { 103 finish(); 104 if (mUseCustomFinishTransition) { 105 overridePendingTransition(R.anim.recents_to_launcher_enter, 106 R.anim.recents_to_launcher_exit); 107 } 108 } 109 } 110 } 111 112 // Broadcast receiver to handle messages from our RecentsService 113 BroadcastReceiver mServiceBroadcastReceiver = new BroadcastReceiver() { 114 @Override 115 public void onReceive(Context context, Intent intent) { 116 String action = intent.getAction(); 117 if (Console.Enabled) { 118 Console.log(Constants.Log.App.SystemUIHandshake, 119 "[RecentsActivity|serviceBroadcast]", action, Console.AnsiRed); 120 } 121 if (action.equals(RecentsService.ACTION_HIDE_RECENTS_ACTIVITY)) { 122 if (intent.getBooleanExtra(RecentsService.EXTRA_TRIGGERED_FROM_ALT_TAB, false)) { 123 // Dismiss recents, launching the focused task 124 dismissRecentsIfVisible(); 125 } else { 126 // If we are mid-animation into Recents, then reverse it and finish 127 if (mFullScreenOverlayView == null || 128 !mFullScreenOverlayView.cancelAnimateOnEnterRecents(mFinishRunnable)) { 129 // Otherwise, either finish Recents, or launch Home directly 130 ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(context, 131 null, mFinishLaunchHomeRunnable, null); 132 mRecentsView.startExitToHomeAnimation( 133 new ViewAnimation.TaskViewExitContext(exitTrigger)); 134 } 135 } 136 } else if (action.equals(RecentsService.ACTION_TOGGLE_RECENTS_ACTIVITY)) { 137 // Try and unfilter and filtered stacks 138 if (!mRecentsView.unfilterFilteredStacks()) { 139 // If there are no filtered stacks, dismiss recents and launch the first task 140 dismissRecentsIfVisible(); 141 } 142 } else if (action.equals(RecentsService.ACTION_START_ENTER_ANIMATION)) { 143 // Try and start the enter animation (or restart it on configuration changed) 144 mRecentsView.startEnterRecentsAnimation(new ViewAnimation.TaskViewEnterContext(mFullScreenOverlayView)); 145 // Call our callback 146 onEnterAnimationTriggered(); 147 } 148 } 149 }; 150 151 // Broadcast receiver to handle messages from the system 152 BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() { 153 @Override 154 public void onReceive(Context context, Intent intent) { 155 mFinishWithoutAnimationRunnable.run(); 156 } 157 }; 158 159 /** Updates the set of recent tasks */ 160 void updateRecentsTasks(Intent launchIntent) { 161 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 162 SpaceNode root = loader.reload(this, Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount); 163 ArrayList<TaskStack> stacks = root.getStacks(); 164 if (!stacks.isEmpty()) { 165 mRecentsView.setBSP(root); 166 } 167 168 // Update the configuration based on the launch intent 169 mConfig.launchedFromHome = launchIntent.getBooleanExtra( 170 AlternateRecentsComponent.EXTRA_FROM_HOME, false); 171 mConfig.launchedFromAppWithThumbnail = launchIntent.getBooleanExtra( 172 AlternateRecentsComponent.EXTRA_FROM_APP_THUMBNAIL, false); 173 mConfig.launchedFromAppWithScreenshot = launchIntent.getBooleanExtra( 174 AlternateRecentsComponent.EXTRA_FROM_APP_FULL_SCREENSHOT, false); 175 mConfig.launchedWithAltTab = launchIntent.getBooleanExtra( 176 AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_ALT_TAB, false); 177 mConfig.launchedWithNoRecentTasks = !root.hasTasks(); 178 179 // Show the scrim if we animate into Recents without window transitions 180 mScrimViews.prepareEnterRecentsAnimation(); 181 182 // Add the default no-recents layout 183 if (mEmptyView == null) { 184 mEmptyView = mEmptyViewStub.inflate(); 185 } 186 if (mConfig.launchedWithNoRecentTasks) { 187 mEmptyView.setVisibility(View.VISIBLE); 188 } else { 189 mEmptyView.setVisibility(View.GONE); 190 } 191 } 192 193 /** Attempts to allocate and bind the search bar app widget */ 194 void bindSearchBarAppWidget() { 195 if (Constants.DebugFlags.App.EnableSearchLayout) { 196 SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy(); 197 198 // Reset the host view and widget info 199 mSearchAppWidgetHostView = null; 200 mSearchAppWidgetInfo = null; 201 202 // Try and load the app widget id from the settings 203 int appWidgetId = mConfig.searchBarAppWidgetId; 204 if (appWidgetId >= 0) { 205 mSearchAppWidgetInfo = ssp.getAppWidgetInfo(appWidgetId); 206 if (mSearchAppWidgetInfo == null) { 207 // If there is no actual widget associated with that id, then delete it and 208 // prepare to bind another app widget in its place 209 ssp.unbindSearchAppWidget(mAppWidgetHost, appWidgetId); 210 appWidgetId = -1; 211 } 212 if (Console.Enabled) { 213 Console.log(Constants.Log.App.SystemUIHandshake, 214 "[RecentsActivity|onCreate|settings|appWidgetId]", 215 "Id: " + appWidgetId, 216 Console.AnsiBlue); 217 } 218 } 219 220 // If there is no id, then bind a new search app widget 221 if (appWidgetId < 0) { 222 Pair<Integer, AppWidgetProviderInfo> widgetInfo = 223 ssp.bindSearchAppWidget(mAppWidgetHost); 224 if (widgetInfo != null) { 225 if (Console.Enabled) { 226 Console.log(Constants.Log.App.SystemUIHandshake, 227 "[RecentsActivity|onCreate|searchWidget]", 228 "Id: " + widgetInfo.first + " Info: " + widgetInfo.second, 229 Console.AnsiBlue); 230 } 231 232 // Save the app widget id into the settings 233 mConfig.updateSearchBarAppWidgetId(this, widgetInfo.first); 234 mSearchAppWidgetInfo = widgetInfo.second; 235 } 236 } 237 } 238 } 239 240 /** Creates the search bar app widget view */ 241 void addSearchBarAppWidgetView() { 242 if (Constants.DebugFlags.App.EnableSearchLayout) { 243 int appWidgetId = mConfig.searchBarAppWidgetId; 244 if (appWidgetId >= 0) { 245 if (Console.Enabled) { 246 Console.log(Constants.Log.App.SystemUIHandshake, 247 "[RecentsActivity|onCreate|addSearchAppWidgetView]", 248 "Id: " + appWidgetId, 249 Console.AnsiBlue); 250 } 251 mSearchAppWidgetHostView = mAppWidgetHost.createView(this, appWidgetId, 252 mSearchAppWidgetInfo); 253 Bundle opts = new Bundle(); 254 opts.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY, 255 AppWidgetProviderInfo.WIDGET_CATEGORY_RECENTS); 256 mSearchAppWidgetHostView.updateAppWidgetOptions(opts); 257 // Set the padding to 0 for this search widget 258 mSearchAppWidgetHostView.setPadding(0, 0, 0, 0); 259 mRecentsView.setSearchBar(mSearchAppWidgetHostView); 260 } else { 261 mRecentsView.setSearchBar(null); 262 } 263 } 264 } 265 266 /** Dismisses recents if we are already visible and the intent is to toggle the recents view */ 267 boolean dismissRecentsIfVisible() { 268 if (mVisible) { 269 // If we are mid-animation into Recents, then reverse it and finish 270 if (mFullScreenOverlayView == null || 271 !mFullScreenOverlayView.cancelAnimateOnEnterRecents(mFinishRunnable)) { 272 // If we have a focused task, then launch that task 273 if (!mRecentsView.launchFocusedTask()) { 274 if (mConfig.launchedFromHome) { 275 // Just start the animation out of recents 276 ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(this, 277 null, mFinishLaunchHomeRunnable, null); 278 mRecentsView.startExitToHomeAnimation( 279 new ViewAnimation.TaskViewExitContext(exitTrigger)); 280 } else { 281 // Otherwise, try and launch the first task 282 if (!mRecentsView.launchFirstTask()) { 283 // If there are no tasks, then just finish recents 284 mFinishLaunchHomeRunnable.run(); 285 } 286 } 287 } 288 } 289 return true; 290 } 291 return false; 292 } 293 294 /** Called with the activity is first created. */ 295 @Override 296 public void onCreate(Bundle savedInstanceState) { 297 super.onCreate(savedInstanceState); 298 if (Console.Enabled) { 299 Console.logDivider(Constants.Log.App.SystemUIHandshake); 300 Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onCreate]", 301 getIntent().getAction() + " visible: " + mVisible, Console.AnsiRed); 302 Console.logTraceTime(Constants.Log.App.TimeRecentsStartup, 303 Constants.Log.App.TimeRecentsStartupKey, "onCreate"); 304 } 305 306 // Initialize the loader and the configuration 307 RecentsTaskLoader.initialize(this); 308 mConfig = RecentsConfiguration.reinitialize(this); 309 310 // Create the home intent runnable 311 Intent homeIntent = new Intent(Intent.ACTION_MAIN, null); 312 homeIntent.addCategory(Intent.CATEGORY_HOME); 313 homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 314 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 315 mFinishLaunchHomeRunnable = new FinishRecentsRunnable(homeIntent, 316 ActivityOptions.makeCustomAnimation(this, R.anim.recents_to_launcher_enter, 317 R.anim.recents_to_launcher_exit)); 318 319 // Initialize the widget host (the host id is static and does not change) 320 mAppWidgetHost = new RecentsAppWidgetHost(this, Constants.Values.App.AppWidgetHostId); 321 322 // Set the Recents layout 323 setContentView(R.layout.recents); 324 mRecentsView = (RecentsView) findViewById(R.id.recents_view); 325 mRecentsView.setCallbacks(this); 326 mRecentsView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | 327 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | 328 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); 329 mEmptyViewStub = (ViewStub) findViewById(R.id.empty_view_stub); 330 mFullscreenOverlayStub = (ViewStub) findViewById(R.id.fullscreen_overlay_stub); 331 mScrimViews = new SystemBarScrimViews(this, mConfig); 332 333 // Update the recent tasks 334 updateRecentsTasks(getIntent()); 335 336 // Prepare the screenshot transition if necessary 337 if (Constants.DebugFlags.App.EnableScreenshotAppTransition) { 338 mFullScreenOverlayView = (FullscreenTransitionOverlayView) mFullscreenOverlayStub.inflate(); 339 mFullScreenOverlayView.setCallbacks(this); 340 mFullScreenOverlayView.prepareAnimateOnEnterRecents(AlternateRecentsComponent.getLastScreenshot()); 341 } 342 343 // Bind the search app widget when we first start up 344 bindSearchBarAppWidget(); 345 // Add the search bar layout 346 addSearchBarAppWidgetView(); 347 348 // Update if we are getting a configuration change 349 if (savedInstanceState != null) { 350 onConfigurationChange(); 351 } 352 353 // Private API calls to make the shadows look better 354 try { 355 Utilities.setShadowProperty("ambientShadowStrength", String.valueOf(35f)); 356 Utilities.setShadowProperty("ambientRatio", String.valueOf(0.5f)); 357 } catch (IllegalAccessException e) { 358 e.printStackTrace(); 359 } catch (InvocationTargetException e) { 360 e.printStackTrace(); 361 } 362 } 363 364 void onConfigurationChange() { 365 // Try and start the enter animation (or restart it on configuration changed) 366 mRecentsView.startEnterRecentsAnimation(new ViewAnimation.TaskViewEnterContext(mFullScreenOverlayView)); 367 // Call our callback 368 onEnterAnimationTriggered(); 369 } 370 371 @Override 372 protected void onNewIntent(Intent intent) { 373 super.onNewIntent(intent); 374 // Reset the task launched flag if we encounter an onNewIntent() before onStop() 375 mTaskLaunched = false; 376 377 if (Console.Enabled) { 378 Console.logDivider(Constants.Log.App.SystemUIHandshake); 379 Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onNewIntent]", 380 intent.getAction() + " visible: " + mVisible, Console.AnsiRed); 381 Console.logTraceTime(Constants.Log.App.TimeRecentsStartup, 382 Constants.Log.App.TimeRecentsStartupKey, "onNewIntent"); 383 } 384 385 // Initialize the loader and the configuration 386 RecentsTaskLoader.initialize(this); 387 mConfig = RecentsConfiguration.reinitialize(this); 388 389 // Update the recent tasks 390 updateRecentsTasks(intent); 391 392 // Prepare the screenshot transition if necessary 393 if (Constants.DebugFlags.App.EnableScreenshotAppTransition) { 394 mFullScreenOverlayView.prepareAnimateOnEnterRecents(AlternateRecentsComponent.getLastScreenshot()); 395 } 396 397 // Don't attempt to rebind the search bar widget, but just add the search bar layout 398 addSearchBarAppWidgetView(); 399 } 400 401 @Override 402 protected void onStart() { 403 if (Console.Enabled) { 404 Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onStart]", "", 405 Console.AnsiRed); 406 } 407 super.onStart(); 408 409 mVisible = true; 410 } 411 412 @Override 413 protected void onResume() { 414 if (Console.Enabled) { 415 Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onResume]", "", 416 Console.AnsiRed); 417 } 418 super.onResume(); 419 } 420 421 @Override 422 public void onAttachedToWindow() { 423 if (Console.Enabled) { 424 Console.log(Constants.Log.App.SystemUIHandshake, 425 "[RecentsActivity|onAttachedToWindow]", "", 426 Console.AnsiRed); 427 } 428 super.onAttachedToWindow(); 429 430 // Register the broadcast receiver to handle messages from our service 431 IntentFilter filter = new IntentFilter(); 432 filter.addAction(RecentsService.ACTION_HIDE_RECENTS_ACTIVITY); 433 filter.addAction(RecentsService.ACTION_TOGGLE_RECENTS_ACTIVITY); 434 filter.addAction(RecentsService.ACTION_START_ENTER_ANIMATION); 435 registerReceiver(mServiceBroadcastReceiver, filter); 436 437 // Register the broadcast receiver to handle messages when the screen is turned off 438 filter = new IntentFilter(); 439 filter.addAction(Intent.ACTION_SCREEN_OFF); 440 registerReceiver(mScreenOffReceiver, filter); 441 442 // Register any broadcast receivers for the task loader 443 RecentsTaskLoader.getInstance().registerReceivers(this, mRecentsView); 444 445 // Start listening for widget package changes if there is one bound 446 if (mConfig.searchBarAppWidgetId >= 0) { 447 mAppWidgetHost.startListening(this); 448 } 449 } 450 451 @Override 452 public void onDetachedFromWindow() { 453 if (Console.Enabled) { 454 Console.log(Constants.Log.App.SystemUIHandshake, 455 "[RecentsActivity|onDetachedFromWindow]", "", 456 Console.AnsiRed); 457 } 458 super.onDetachedFromWindow(); 459 460 // Unregister any broadcast receivers we have registered 461 unregisterReceiver(mServiceBroadcastReceiver); 462 unregisterReceiver(mScreenOffReceiver); 463 RecentsTaskLoader.getInstance().unregisterReceivers(); 464 465 // Stop listening for widget package changes if there was one bound 466 if (mConfig.searchBarAppWidgetId >= 0) { 467 mAppWidgetHost.stopListening(); 468 } 469 } 470 471 @Override 472 protected void onPause() { 473 if (Console.Enabled) { 474 Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onPause]", "", 475 Console.AnsiRed); 476 } 477 super.onPause(); 478 } 479 480 @Override 481 protected void onStop() { 482 if (Console.Enabled) { 483 Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onStop]", "", 484 Console.AnsiRed); 485 } 486 super.onStop(); 487 488 mVisible = false; 489 mTaskLaunched = false; 490 } 491 492 @Override 493 protected void onDestroy() { 494 if (Console.Enabled) { 495 Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onDestroy]", "", 496 Console.AnsiRed); 497 } 498 super.onDestroy(); 499 } 500 501 @Override 502 public void onTrimMemory(int level) { 503 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 504 if (loader != null) { 505 loader.onTrimMemory(level); 506 } 507 } 508 509 @Override 510 public boolean onKeyDown(int keyCode, KeyEvent event) { 511 if (keyCode == KeyEvent.KEYCODE_TAB) { 512 // Focus the next task in the stack 513 final boolean backward = event.isShiftPressed(); 514 mRecentsView.focusNextTask(!backward); 515 return true; 516 } 517 518 return super.onKeyDown(keyCode, event); 519 } 520 521 @Override 522 public void onUserInteraction() { 523 mRecentsView.onUserInteraction(); 524 } 525 526 @Override 527 public void onBackPressed() { 528 // If we are mid-animation into Recents, then reverse it and finish 529 if (mFullScreenOverlayView == null || 530 !mFullScreenOverlayView.cancelAnimateOnEnterRecents(mFinishRunnable)) { 531 // If we are currently filtering in any stacks, unfilter them first 532 if (!mRecentsView.unfilterFilteredStacks()) { 533 if (mConfig.launchedFromHome) { 534 // Just start the animation out of recents 535 ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(this, 536 null, mFinishLaunchHomeRunnable, null); 537 mRecentsView.startExitToHomeAnimation( 538 new ViewAnimation.TaskViewExitContext(exitTrigger)); 539 } else { 540 // Otherwise, try and launch the first task 541 if (!mRecentsView.launchFirstTask()) { 542 // If there are no tasks, then just finish recents 543 mFinishLaunchHomeRunnable.run(); 544 } 545 } 546 } 547 } 548 } 549 550 public void onEnterAnimationTriggered() { 551 // Animate the scrims in 552 mScrimViews.startEnterRecentsAnimation(); 553 } 554 555 /**** FullscreenTransitionOverlayView.FullScreenTransitionViewCallbacks Implementation ****/ 556 557 @Override 558 public void onEnterAnimationComplete(boolean canceled) { 559 if (!canceled) { 560 // Reset the full screenshot transition view 561 if (Constants.DebugFlags.App.EnableScreenshotAppTransition) { 562 mFullScreenOverlayView.reset(); 563 } 564 565 // XXX: We should clean up the screenshot in this case as well, but it needs to happen 566 // after to animate up 567 } 568 // Recycle the full screen screenshot 569 AlternateRecentsComponent.consumeLastScreenshot(); 570 } 571 572 /**** RecentsView.RecentsViewCallbacks Implementation ****/ 573 574 @Override 575 public void onExitToHomeAnimationTriggered() { 576 // Animate the scrims out 577 mScrimViews.startExitRecentsAnimation(); 578 } 579 580 @Override 581 public void onTaskLaunching() { 582 mTaskLaunched = true; 583 584 // Mark recents as no longer visible 585 AlternateRecentsComponent.notifyVisibilityChanged(false); 586 } 587 588 /**** RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks Implementation ****/ 589 590 @Override 591 public void refreshSearchWidget() { 592 // Load the Search widget again 593 bindSearchBarAppWidget(); 594 addSearchBarAppWidgetView(); 595 } 596} 597