RecentsActivity.java revision aef51c63d7086088b0c245d18f052a181fe1ff45
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.appwidget.AppWidgetHost; 21import android.appwidget.AppWidgetHostView; 22import android.appwidget.AppWidgetManager; 23import android.appwidget.AppWidgetProviderInfo; 24import android.content.BroadcastReceiver; 25import android.content.ComponentName; 26import android.content.Context; 27import android.content.Intent; 28import android.content.IntentFilter; 29import android.content.res.Configuration; 30import android.os.Bundle; 31import android.util.Pair; 32import android.view.Gravity; 33import android.view.KeyEvent; 34import android.view.LayoutInflater; 35import android.view.View; 36import android.view.ViewGroup; 37import android.view.WindowManager; 38import android.widget.FrameLayout; 39import com.android.systemui.R; 40import com.android.systemui.recents.model.SpaceNode; 41import com.android.systemui.recents.model.TaskStack; 42import com.android.systemui.recents.views.RecentsView; 43 44import java.util.ArrayList; 45import java.util.Set; 46 47/** Our special app widget host */ 48class RecentsAppWidgetHost extends AppWidgetHost { 49 /* Callbacks to notify when an app package changes */ 50 interface RecentsAppWidgetHostCallbacks { 51 public void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidgetInfo); 52 } 53 54 RecentsAppWidgetHostCallbacks mCb; 55 56 public RecentsAppWidgetHost(Context context, int hostId, RecentsAppWidgetHostCallbacks cb) { 57 super(context, hostId); 58 mCb = cb; 59 } 60 61 @Override 62 protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget) { 63 mCb.onProviderChanged(appWidgetId, appWidget); 64 } 65} 66 67/* Activity */ 68public class RecentsActivity extends Activity implements RecentsView.RecentsViewCallbacks, 69 RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks { 70 FrameLayout mContainerView; 71 RecentsView mRecentsView; 72 View mEmptyView; 73 View mNavBarScrimView; 74 75 AppWidgetHost mAppWidgetHost; 76 AppWidgetProviderInfo mSearchAppWidgetInfo; 77 AppWidgetHostView mSearchAppWidgetHostView; 78 79 boolean mVisible; 80 boolean mTaskLaunched; 81 82 // Broadcast receiver to handle messages from our RecentsService 83 BroadcastReceiver mServiceBroadcastReceiver = new BroadcastReceiver() { 84 @Override 85 public void onReceive(Context context, Intent intent) { 86 String action = intent.getAction(); 87 if (Console.Enabled) { 88 Console.log(Constants.Log.App.SystemUIHandshake, 89 "[RecentsActivity|serviceBroadcast]", action, Console.AnsiRed); 90 } 91 if (action.equals(RecentsService.ACTION_HIDE_RECENTS_ACTIVITY)) { 92 if (intent.getBooleanExtra(RecentsService.EXTRA_TRIGGERED_FROM_ALT_TAB, false)) { 93 // Dismiss recents, launching the focused task 94 dismissRecentsIfVisible(); 95 } else { 96 // Otherwise, just finish the activity without launching any other activities 97 finish(); 98 } 99 } else if (action.equals(RecentsService.ACTION_TOGGLE_RECENTS_ACTIVITY)) { 100 // Try and unfilter and filtered stacks 101 if (!mRecentsView.unfilterFilteredStacks()) { 102 // If there are no filtered stacks, dismiss recents and launch the first task 103 dismissRecentsIfVisible(); 104 } 105 } else if (action.equals(RecentsService.ACTION_START_ENTER_ANIMATION)) { 106 // Try and start the enter animation (or restart it on configuration changed) 107 mRecentsView.startOnEnterAnimation(); 108 } 109 } 110 }; 111 112 // Broadcast receiver to handle messages from the system 113 BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() { 114 @Override 115 public void onReceive(Context context, Intent intent) { 116 finish(); 117 } 118 }; 119 120 /** Updates the set of recent tasks */ 121 void updateRecentsTasks(Intent launchIntent) { 122 // Update the configuration based on the launch intent 123 RecentsConfiguration config = RecentsConfiguration.getInstance(); 124 config.launchedWithThumbnailAnimation = launchIntent.getBooleanExtra( 125 AlternateRecentsComponent.EXTRA_ANIMATING_WITH_THUMBNAIL, false); 126 config.launchedFromAltTab = launchIntent.getBooleanExtra( 127 AlternateRecentsComponent.EXTRA_FROM_ALT_TAB, false); 128 129 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 130 SpaceNode root = loader.reload(this, Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount); 131 ArrayList<TaskStack> stacks = root.getStacks(); 132 if (!stacks.isEmpty()) { 133 mRecentsView.setBSP(root); 134 } 135 136 // Hide the scrim by default when we enter recents 137 mNavBarScrimView.setVisibility(View.INVISIBLE); 138 139 // Add the default no-recents layout 140 if (stacks.size() == 1 && stacks.get(0).getTaskCount() == 0) { 141 mEmptyView.setVisibility(View.VISIBLE); 142 143 // Dim the background even more 144 WindowManager.LayoutParams wlp = getWindow().getAttributes(); 145 wlp.dimAmount = Constants.Values.Window.DarkBackgroundDim; 146 getWindow().setAttributes(wlp); 147 getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); 148 } else { 149 mEmptyView.setVisibility(View.GONE); 150 151 // Un-dim the background 152 WindowManager.LayoutParams wlp = getWindow().getAttributes(); 153 wlp.dimAmount = 0f; 154 getWindow().setAttributes(wlp); 155 getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); 156 } 157 } 158 159 /** Attempts to allocate and bind the search bar app widget */ 160 void bindSearchBarAppWidget() { 161 if (Constants.DebugFlags.App.EnableSearchLayout) { 162 RecentsConfiguration config = RecentsConfiguration.getInstance(); 163 SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy(); 164 165 // Reset the host view and widget info 166 mSearchAppWidgetHostView = null; 167 mSearchAppWidgetInfo = null; 168 169 // Try and load the app widget id from the settings 170 int appWidgetId = config.searchBarAppWidgetId; 171 if (appWidgetId >= 0) { 172 mSearchAppWidgetInfo = ssp.getAppWidgetInfo(appWidgetId); 173 if (mSearchAppWidgetInfo == null) { 174 // If there is no actual widget associated with that id, then delete it and 175 // prepare to bind another app widget in its place 176 ssp.unbindSearchAppWidget(mAppWidgetHost, appWidgetId); 177 appWidgetId = -1; 178 } 179 if (Console.Enabled) { 180 Console.log(Constants.Log.App.SystemUIHandshake, 181 "[RecentsActivity|onCreate|settings|appWidgetId]", 182 "Id: " + appWidgetId, 183 Console.AnsiBlue); 184 } 185 } 186 187 // If there is no id, then bind a new search app widget 188 if (appWidgetId < 0) { 189 Pair<Integer, AppWidgetProviderInfo> widgetInfo = 190 ssp.bindSearchAppWidget(mAppWidgetHost); 191 if (widgetInfo != null) { 192 if (Console.Enabled) { 193 Console.log(Constants.Log.App.SystemUIHandshake, 194 "[RecentsActivity|onCreate|searchWidget]", 195 "Id: " + widgetInfo.first + " Info: " + widgetInfo.second, 196 Console.AnsiBlue); 197 } 198 199 // Save the app widget id into the settings 200 config.updateSearchBarAppWidgetId(this, widgetInfo.first); 201 mSearchAppWidgetInfo = widgetInfo.second; 202 } 203 } 204 } 205 } 206 207 /** Creates the search bar app widget view */ 208 void addSearchBarAppWidgetView() { 209 if (Constants.DebugFlags.App.EnableSearchLayout) { 210 RecentsConfiguration config = RecentsConfiguration.getInstance(); 211 int appWidgetId = config.searchBarAppWidgetId; 212 if (appWidgetId >= 0) { 213 if (Console.Enabled) { 214 Console.log(Constants.Log.App.SystemUIHandshake, 215 "[RecentsActivity|onCreate|addSearchAppWidgetView]", 216 "Id: " + appWidgetId, 217 Console.AnsiBlue); 218 } 219 mSearchAppWidgetHostView = mAppWidgetHost.createView(this, appWidgetId, 220 mSearchAppWidgetInfo); 221 Bundle opts = new Bundle(); 222 opts.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY, 223 AppWidgetProviderInfo.WIDGET_CATEGORY_RECENTS); 224 mSearchAppWidgetHostView.updateAppWidgetOptions(opts); 225 // Set the padding to 0 for this search widget 226 mSearchAppWidgetHostView.setPadding(0, 0, 0, 0); 227 mRecentsView.setSearchBar(mSearchAppWidgetHostView); 228 } else { 229 mRecentsView.setSearchBar(null); 230 } 231 } 232 } 233 234 /** Dismisses recents if we are already visible and the intent is to toggle the recents view */ 235 boolean dismissRecentsIfVisible() { 236 if (mVisible) { 237 if (!mRecentsView.launchFocusedTask()) { 238 if (!mRecentsView.launchFirstTask()) { 239 finish(); 240 } 241 } 242 return true; 243 } 244 return false; 245 } 246 247 /** Called with the activity is first created. */ 248 @Override 249 public void onCreate(Bundle savedInstanceState) { 250 super.onCreate(savedInstanceState); 251 if (Console.Enabled) { 252 Console.logDivider(Constants.Log.App.SystemUIHandshake); 253 Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onCreate]", 254 getIntent().getAction() + " visible: " + mVisible, Console.AnsiRed); 255 Console.logTraceTime(Constants.Log.App.TimeRecentsStartup, 256 Constants.Log.App.TimeRecentsStartupKey, "onCreate"); 257 } 258 259 // Initialize the loader and the configuration 260 RecentsTaskLoader.initialize(this); 261 RecentsConfiguration.reinitialize(this); 262 263 // Initialize the widget host (the host id is static and does not change) 264 mAppWidgetHost = new RecentsAppWidgetHost(this, Constants.Values.App.AppWidgetHostId, this); 265 266 // Create the view hierarchy 267 mRecentsView = new RecentsView(this); 268 mRecentsView.setCallbacks(this); 269 mRecentsView.setLayoutParams(new FrameLayout.LayoutParams( 270 FrameLayout.LayoutParams.MATCH_PARENT, 271 FrameLayout.LayoutParams.MATCH_PARENT)); 272 mRecentsView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | 273 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | 274 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); 275 276 // Create the empty view 277 LayoutInflater inflater = LayoutInflater.from(this); 278 mEmptyView = inflater.inflate(R.layout.recents_empty, mContainerView, false); 279 mNavBarScrimView = inflater.inflate(R.layout.recents_nav_bar_scrim, mContainerView, false); 280 281 mContainerView = new FrameLayout(this); 282 mContainerView.addView(mRecentsView); 283 mContainerView.addView(mEmptyView); 284 mContainerView.addView(mNavBarScrimView); 285 setContentView(mContainerView); 286 287 // Update the recent tasks 288 updateRecentsTasks(getIntent()); 289 290 // Bind the search app widget when we first start up 291 bindSearchBarAppWidget(); 292 // Add the search bar layout 293 addSearchBarAppWidgetView(); 294 295 // Update if we are getting a configuration change 296 if (savedInstanceState != null) { 297 onConfigurationChange(); 298 } 299 } 300 301 void onConfigurationChange() { 302 // Try and start the enter animation (or restart it on configuration changed) 303 mRecentsView.startOnEnterAnimation(); 304 } 305 306 @Override 307 protected void onNewIntent(Intent intent) { 308 super.onNewIntent(intent); 309 // Reset the task launched flag if we encounter an onNewIntent() before onStop() 310 mTaskLaunched = false; 311 312 if (Console.Enabled) { 313 Console.logDivider(Constants.Log.App.SystemUIHandshake); 314 Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onNewIntent]", 315 intent.getAction() + " visible: " + mVisible, Console.AnsiRed); 316 Console.logTraceTime(Constants.Log.App.TimeRecentsStartup, 317 Constants.Log.App.TimeRecentsStartupKey, "onNewIntent"); 318 } 319 320 // Initialize the loader and the configuration 321 RecentsTaskLoader.initialize(this); 322 RecentsConfiguration.reinitialize(this); 323 324 // Update the recent tasks 325 updateRecentsTasks(intent); 326 327 // Don't attempt to rebind the search bar widget, but just add the search bar layout 328 addSearchBarAppWidgetView(); 329 } 330 331 @Override 332 protected void onStart() { 333 if (Console.Enabled) { 334 Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onStart]", "", 335 Console.AnsiRed); 336 } 337 super.onStart(); 338 339 // Start listening for widget package changes if there is one bound 340 RecentsConfiguration config = RecentsConfiguration.getInstance(); 341 if (config.searchBarAppWidgetId >= 0) { 342 mAppWidgetHost.startListening(); 343 } 344 345 mVisible = true; 346 } 347 348 @Override 349 protected void onResume() { 350 if (Console.Enabled) { 351 Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onResume]", "", 352 Console.AnsiRed); 353 } 354 super.onResume(); 355 } 356 357 @Override 358 public void onAttachedToWindow() { 359 if (Console.Enabled) { 360 Console.log(Constants.Log.App.SystemUIHandshake, 361 "[RecentsActivity|onAttachedToWindow]", "", 362 Console.AnsiRed); 363 } 364 super.onAttachedToWindow(); 365 366 // Register the broadcast receiver to handle messages from our service 367 IntentFilter filter = new IntentFilter(); 368 filter.addAction(RecentsService.ACTION_HIDE_RECENTS_ACTIVITY); 369 filter.addAction(RecentsService.ACTION_TOGGLE_RECENTS_ACTIVITY); 370 filter.addAction(RecentsService.ACTION_START_ENTER_ANIMATION); 371 registerReceiver(mServiceBroadcastReceiver, filter); 372 373 // Register the broadcast receiver to handle messages when the screen is turned off 374 filter = new IntentFilter(); 375 filter.addAction(Intent.ACTION_SCREEN_OFF); 376 registerReceiver(mScreenOffReceiver, filter); 377 378 // Register any broadcast receivers for the task loader 379 RecentsTaskLoader.getInstance().registerReceivers(this, mRecentsView); 380 } 381 382 @Override 383 public void onDetachedFromWindow() { 384 if (Console.Enabled) { 385 Console.log(Constants.Log.App.SystemUIHandshake, 386 "[RecentsActivity|onDetachedFromWindow]", "", 387 Console.AnsiRed); 388 } 389 super.onDetachedFromWindow(); 390 391 // Unregister any broadcast receivers we have registered 392 unregisterReceiver(mServiceBroadcastReceiver); 393 unregisterReceiver(mScreenOffReceiver); 394 RecentsTaskLoader.getInstance().unregisterReceivers(); 395 } 396 397 @Override 398 protected void onPause() { 399 if (Console.Enabled) { 400 Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onPause]", "", 401 Console.AnsiRed); 402 } 403 super.onPause(); 404 } 405 406 @Override 407 protected void onStop() { 408 if (Console.Enabled) { 409 Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onStop]", "", 410 Console.AnsiRed); 411 } 412 super.onStop(); 413 414 // Stop listening for widget package changes if there was one bound 415 RecentsConfiguration config = RecentsConfiguration.getInstance(); 416 if (config.searchBarAppWidgetId >= 0) { 417 mAppWidgetHost.stopListening(); 418 } 419 420 mVisible = false; 421 mTaskLaunched = false; 422 } 423 424 @Override 425 protected void onDestroy() { 426 if (Console.Enabled) { 427 Console.log(Constants.Log.App.SystemUIHandshake, "[RecentsActivity|onDestroy]", "", 428 Console.AnsiRed); 429 } 430 super.onDestroy(); 431 } 432 433 @Override 434 public void onTrimMemory(int level) { 435 RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); 436 if (loader != null) { 437 loader.onTrimMemory(level); 438 } 439 } 440 441 @Override 442 public boolean onKeyDown(int keyCode, KeyEvent event) { 443 if (keyCode == KeyEvent.KEYCODE_TAB) { 444 // Focus the next task in the stack 445 final boolean backward = event.isShiftPressed(); 446 mRecentsView.focusNextTask(!backward); 447 return true; 448 } 449 450 return super.onKeyDown(keyCode, event); 451 } 452 453 @Override 454 public void onBackPressed() { 455 // Unfilter any stacks 456 if (!mRecentsView.unfilterFilteredStacks()) { 457 if (!mRecentsView.launchFirstTask()) { 458 super.onBackPressed(); 459 } 460 } 461 } 462 463 @Override 464 public void onEnterAnimationTriggered() { 465 // Fade in the scrim 466 RecentsConfiguration config = RecentsConfiguration.getInstance(); 467 if (config.hasNavBarScrim()) { 468 mNavBarScrimView.setVisibility(View.VISIBLE); 469 mNavBarScrimView.setAlpha(0f); 470 mNavBarScrimView.animate().alpha(1f) 471 .setStartDelay(config.taskBarEnterAnimDelay) 472 .setDuration(config.navBarScrimEnterDuration) 473 .setInterpolator(config.fastOutSlowInInterpolator) 474 .withLayer() 475 .start(); 476 } 477 } 478 479 @Override 480 public void onTaskLaunching(boolean isTaskInStackBounds) { 481 mTaskLaunched = true; 482 483 // Fade out the scrim 484 RecentsConfiguration config = RecentsConfiguration.getInstance(); 485 if (!isTaskInStackBounds && config.hasNavBarScrim()) { 486 mNavBarScrimView.animate().alpha(0f) 487 .setStartDelay(0) 488 .setDuration(config.taskBarExitAnimDuration) 489 .setInterpolator(config.fastOutSlowInInterpolator) 490 .withLayer() 491 .start(); 492 } 493 } 494 495 @Override 496 public void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidgetInfo) { 497 RecentsConfiguration config = RecentsConfiguration.getInstance(); 498 SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy(); 499 if (appWidgetId > -1 && appWidgetId == config.searchBarAppWidgetId) { 500 // The search provider may have changed, so just delete the old widget and bind it again 501 ssp.unbindSearchAppWidget(mAppWidgetHost, appWidgetId); 502 config.updateSearchBarAppWidgetId(this, -1); 503 // Load the widget again 504 bindSearchBarAppWidget(); 505 addSearchBarAppWidgetView(); 506 } 507 } 508} 509