Recents.java revision 303e1ff1fec8b240b587bb18b981247a99833aa8
1/* 2 * Copyright (C) 2013 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.recent; 18 19import android.app.ActivityManager; 20import android.app.ActivityOptions; 21import android.content.ActivityNotFoundException; 22import android.content.ComponentName; 23import android.content.Context; 24import android.content.Intent; 25import android.content.ServiceConnection; 26import android.content.pm.ActivityInfo; 27import android.content.pm.PackageManager; 28import android.content.res.Configuration; 29import android.content.res.Resources; 30import android.graphics.Bitmap; 31import android.graphics.Canvas; 32import android.graphics.Matrix; 33import android.graphics.Paint; 34import android.graphics.Rect; 35import android.graphics.drawable.BitmapDrawable; 36import android.graphics.drawable.Drawable; 37import android.os.Bundle; 38import android.os.Handler; 39import android.os.IBinder; 40import android.os.Message; 41import android.os.Messenger; 42import android.os.RemoteException; 43import android.os.SystemProperties; 44import android.os.UserHandle; 45import android.util.DisplayMetrics; 46import android.util.Log; 47import android.view.Display; 48import android.view.Surface; 49import android.view.SurfaceControl; 50import android.view.View; 51import android.view.WindowManager; 52import com.android.systemui.R; 53import com.android.systemui.RecentsComponent; 54import com.android.systemui.SystemUI; 55 56import java.util.List; 57 58 59public class Recents extends SystemUI implements RecentsComponent { 60 /** A handler for messages from the recents implementation */ 61 class RecentsMessageHandler extends Handler { 62 @Override 63 public void handleMessage(Message msg) { 64 if (!mUseAlternateRecents) return; 65 if (msg.what == MSG_UPDATE_FOR_CONFIGURATION) { 66 Resources res = mContext.getResources(); 67 float statusBarHeight = res.getDimensionPixelSize( 68 com.android.internal.R.dimen.status_bar_height); 69 mFirstTaskRect = (Rect) msg.getData().getParcelable("taskRect"); 70 mFirstTaskRect.offset(0, (int) statusBarHeight); 71 } 72 } 73 } 74 75 /** A service connection to the recents implementation */ 76 class RecentsServiceConnection implements ServiceConnection { 77 @Override 78 public void onServiceConnected(ComponentName className, IBinder service) { 79 if (!mUseAlternateRecents) return; 80 81 Log.d(TAG, "[RecentsComponent|ServiceConnection|onServiceConnected] toggleRecents: " + 82 mToggleRecentsUponServiceBound); 83 mService = new Messenger(service); 84 mServiceIsBound = true; 85 86 // Toggle recents if this service connection was triggered by hitting the recents button 87 if (mToggleRecentsUponServiceBound) { 88 startAlternateRecentsActivity(); 89 } 90 mToggleRecentsUponServiceBound = false; 91 } 92 93 @Override 94 public void onServiceDisconnected(ComponentName className) { 95 if (!mUseAlternateRecents) return; 96 97 Log.d(TAG, "[RecentsComponent|ServiceConnection|onServiceDisconnected]"); 98 mService = null; 99 mServiceIsBound = false; 100 } 101 } 102 103 private static final String TAG = "Recents"; 104 private static final boolean DEBUG = true; 105 106 final static int MSG_UPDATE_FOR_CONFIGURATION = 0; 107 final static int MSG_UPDATE_TASK_THUMBNAIL = 1; 108 final static int MSG_PRELOAD_TASKS = 2; 109 final static int MSG_CANCEL_PRELOAD_TASKS = 3; 110 111 final static String sToggleRecentsAction = "com.android.systemui.recents.TOGGLE_RECENTS"; 112 final static String sRecentsPackage = "com.android.systemui"; 113 final static String sRecentsActivity = "com.android.systemui.recents.RecentsActivity"; 114 final static String sRecentsService = "com.android.systemui.recents.RecentsService"; 115 116 // Which recents to use 117 boolean mUseAlternateRecents; 118 119 // Recents service binding 120 Messenger mService = null; 121 Messenger mMessenger; 122 boolean mServiceIsBound = false; 123 boolean mToggleRecentsUponServiceBound; 124 RecentsServiceConnection mConnection = new RecentsServiceConnection(); 125 126 View mStatusBarView; 127 Rect mFirstTaskRect = new Rect(); 128 129 public Recents() { 130 mMessenger = new Messenger(new RecentsMessageHandler()); 131 } 132 133 @Override 134 public void start() { 135 mUseAlternateRecents = 136 SystemProperties.getBoolean("persist.recents.use_alternate", false); 137 138 putComponent(RecentsComponent.class, this); 139 140 if (mUseAlternateRecents) { 141 Log.d(TAG, "[RecentsComponent|start]"); 142 143 // Try to create a long-running connection to the recents service 144 bindToRecentsService(false); 145 } 146 } 147 148 @Override 149 public void toggleRecents(Display display, int layoutDirection, View statusBarView) { 150 if (mUseAlternateRecents) { 151 // Launch the alternate recents if required 152 toggleAlternateRecents(display, layoutDirection, statusBarView); 153 return; 154 } 155 156 if (DEBUG) Log.d(TAG, "toggle recents panel"); 157 try { 158 TaskDescription firstTask = RecentTasksLoader.getInstance(mContext).getFirstTask(); 159 160 Intent intent = new Intent(RecentsActivity.TOGGLE_RECENTS_INTENT); 161 intent.setClassName("com.android.systemui", 162 "com.android.systemui.recent.RecentsActivity"); 163 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 164 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 165 166 if (firstTask == null) { 167 if (RecentsActivity.forceOpaqueBackground(mContext)) { 168 ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, 169 R.anim.recents_launch_from_launcher_enter, 170 R.anim.recents_launch_from_launcher_exit); 171 mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle( 172 UserHandle.USER_CURRENT)); 173 } else { 174 // The correct window animation will be applied via the activity's style 175 mContext.startActivityAsUser(intent, new UserHandle( 176 UserHandle.USER_CURRENT)); 177 } 178 179 } else { 180 Bitmap first = null; 181 if (firstTask.getThumbnail() instanceof BitmapDrawable) { 182 first = ((BitmapDrawable) firstTask.getThumbnail()).getBitmap(); 183 } else { 184 first = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); 185 Drawable d = RecentTasksLoader.getInstance(mContext).getDefaultThumbnail(); 186 d.draw(new Canvas(first)); 187 } 188 final Resources res = mContext.getResources(); 189 190 float thumbWidth = res 191 .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_width); 192 float thumbHeight = res 193 .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_height); 194 if (first == null) { 195 throw new RuntimeException("Recents thumbnail is null"); 196 } 197 if (first.getWidth() != thumbWidth || first.getHeight() != thumbHeight) { 198 first = Bitmap.createScaledBitmap(first, (int) thumbWidth, (int) thumbHeight, 199 true); 200 if (first == null) { 201 throw new RuntimeException("Recents thumbnail is null"); 202 } 203 } 204 205 206 DisplayMetrics dm = new DisplayMetrics(); 207 display.getMetrics(dm); 208 // calculate it here, but consider moving it elsewhere 209 // first, determine which orientation you're in. 210 final Configuration config = res.getConfiguration(); 211 int x, y; 212 213 if (config.orientation == Configuration.ORIENTATION_PORTRAIT) { 214 float appLabelLeftMargin = res.getDimensionPixelSize( 215 R.dimen.status_bar_recents_app_label_left_margin); 216 float appLabelWidth = res.getDimensionPixelSize( 217 R.dimen.status_bar_recents_app_label_width); 218 float thumbLeftMargin = res.getDimensionPixelSize( 219 R.dimen.status_bar_recents_thumbnail_left_margin); 220 float thumbBgPadding = res.getDimensionPixelSize( 221 R.dimen.status_bar_recents_thumbnail_bg_padding); 222 223 float width = appLabelLeftMargin + 224 +appLabelWidth 225 + thumbLeftMargin 226 + thumbWidth 227 + 2 * thumbBgPadding; 228 229 x = (int) ((dm.widthPixels - width) / 2f + appLabelLeftMargin + appLabelWidth 230 + thumbBgPadding + thumbLeftMargin); 231 y = (int) (dm.heightPixels 232 - res.getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_height) 233 - thumbBgPadding); 234 if (layoutDirection == View.LAYOUT_DIRECTION_RTL) { 235 x = dm.widthPixels - x - res.getDimensionPixelSize( 236 R.dimen.status_bar_recents_thumbnail_width); 237 } 238 239 } else { // if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) { 240 float thumbTopMargin = res.getDimensionPixelSize( 241 R.dimen.status_bar_recents_thumbnail_top_margin); 242 float thumbBgPadding = res.getDimensionPixelSize( 243 R.dimen.status_bar_recents_thumbnail_bg_padding); 244 float textPadding = res.getDimensionPixelSize( 245 R.dimen.status_bar_recents_text_description_padding); 246 float labelTextSize = res.getDimensionPixelSize( 247 R.dimen.status_bar_recents_app_label_text_size); 248 Paint p = new Paint(); 249 p.setTextSize(labelTextSize); 250 float labelTextHeight = p.getFontMetricsInt().bottom 251 - p.getFontMetricsInt().top; 252 float descriptionTextSize = res.getDimensionPixelSize( 253 R.dimen.status_bar_recents_app_description_text_size); 254 p.setTextSize(descriptionTextSize); 255 float descriptionTextHeight = p.getFontMetricsInt().bottom 256 - p.getFontMetricsInt().top; 257 258 float statusBarHeight = res.getDimensionPixelSize( 259 com.android.internal.R.dimen.status_bar_height); 260 float recentsItemTopPadding = statusBarHeight; 261 262 float height = thumbTopMargin 263 + thumbHeight 264 + 2 * thumbBgPadding + textPadding + labelTextHeight 265 + recentsItemTopPadding + textPadding + descriptionTextHeight; 266 float recentsItemRightPadding = res 267 .getDimensionPixelSize(R.dimen.status_bar_recents_item_padding); 268 float recentsScrollViewRightPadding = res 269 .getDimensionPixelSize(R.dimen.status_bar_recents_right_glow_margin); 270 x = (int) (dm.widthPixels - res 271 .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_width) 272 - thumbBgPadding - recentsItemRightPadding 273 - recentsScrollViewRightPadding); 274 y = (int) ((dm.heightPixels - statusBarHeight - height) / 2f + thumbTopMargin 275 + recentsItemTopPadding + thumbBgPadding + statusBarHeight); 276 } 277 278 ActivityOptions opts = ActivityOptions.makeThumbnailScaleDownAnimation( 279 statusBarView, 280 first, x, y, 281 new ActivityOptions.OnAnimationStartedListener() { 282 public void onAnimationStarted() { 283 Intent intent = 284 new Intent(RecentsActivity.WINDOW_ANIMATION_START_INTENT); 285 intent.setPackage("com.android.systemui"); 286 mContext.sendBroadcastAsUser(intent, 287 new UserHandle(UserHandle.USER_CURRENT)); 288 } 289 }); 290 intent.putExtra(RecentsActivity.WAITING_FOR_WINDOW_ANIMATION_PARAM, true); 291 mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle( 292 UserHandle.USER_CURRENT)); 293 } 294 } catch (ActivityNotFoundException e) { 295 Log.e(TAG, "Failed to launch RecentAppsIntent", e); 296 } 297 } 298 299 /** Toggles the alternate recents activity */ 300 public void toggleAlternateRecents(Display display, int layoutDirection, View statusBarView) { 301 if (!mUseAlternateRecents) return; 302 303 Log.d(TAG, "[RecentsComponent|toggleRecents] serviceIsBound: " + mServiceIsBound); 304 mStatusBarView = statusBarView; 305 if (!mServiceIsBound) { 306 // Try to create a long-running connection to the recents service before toggling 307 // recents 308 bindToRecentsService(true); 309 return; 310 } 311 312 try { 313 startAlternateRecentsActivity(); 314 } catch (ActivityNotFoundException e) { 315 Log.e(TAG, "Failed to launch RecentAppsIntent", e); 316 } 317 } 318 319 @Override 320 protected void onConfigurationChanged(Configuration newConfig) { 321 if (mServiceIsBound) { 322 Resources res = mContext.getResources(); 323 int statusBarHeight = res.getDimensionPixelSize( 324 com.android.internal.R.dimen.status_bar_height); 325 int navBarHeight = res.getDimensionPixelSize( 326 com.android.internal.R.dimen.navigation_bar_height); 327 Rect rect = new Rect(); 328 WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 329 wm.getDefaultDisplay().getRectSize(rect); 330 331 // Try and update the recents configuration 332 try { 333 Bundle data = new Bundle(); 334 data.putParcelable("windowRect", rect); 335 data.putParcelable("systemInsets", new Rect(0, statusBarHeight, 0, 0)); 336 Message msg = Message.obtain(null, MSG_UPDATE_FOR_CONFIGURATION, 0, 0); 337 msg.setData(data); 338 msg.replyTo = mMessenger; 339 mService.send(msg); 340 } catch (RemoteException re) { 341 re.printStackTrace(); 342 } 343 } 344 } 345 346 /** Binds to the recents implementation */ 347 private void bindToRecentsService(boolean toggleRecentsUponConnection) { 348 if (!mUseAlternateRecents) return; 349 350 mToggleRecentsUponServiceBound = toggleRecentsUponConnection; 351 Intent intent = new Intent(); 352 intent.setClassName(sRecentsPackage, sRecentsService); 353 mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE); 354 } 355 356 /** Loads the first task thumbnail */ 357 Bitmap loadFirstTaskThumbnail() { 358 ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); 359 List<ActivityManager.RecentTaskInfo> tasks = am.getRecentTasksForUser(1, 360 ActivityManager.RECENT_IGNORE_UNAVAILABLE, UserHandle.CURRENT.getIdentifier()); 361 for (ActivityManager.RecentTaskInfo t : tasks) { 362 // Skip tasks in the home stack 363 if (am.isInHomeStack(t.persistentId)) { 364 return null; 365 } 366 367 Bitmap thumbnail = am.getTaskTopThumbnail(t.persistentId); 368 return thumbnail; 369 } 370 return null; 371 } 372 373 /** Returns whether there is a first task */ 374 boolean hasFirstTask() { 375 ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); 376 List<ActivityManager.RecentTaskInfo> tasks = am.getRecentTasksForUser(1, 377 ActivityManager.RECENT_IGNORE_UNAVAILABLE, UserHandle.CURRENT.getIdentifier()); 378 for (ActivityManager.RecentTaskInfo t : tasks) { 379 // Skip tasks in the home stack 380 if (am.isInHomeStack(t.persistentId)) { 381 continue; 382 } 383 384 return true; 385 } 386 return false; 387 } 388 389 /** Converts from the device rotation to the degree */ 390 float getDegreesForRotation(int value) { 391 switch (value) { 392 case Surface.ROTATION_90: 393 return 360f - 90f; 394 case Surface.ROTATION_180: 395 return 360f - 180f; 396 case Surface.ROTATION_270: 397 return 360f - 270f; 398 } 399 return 0f; 400 } 401 402 /** Takes a screenshot of the surface */ 403 Bitmap takeScreenshot(Display display) { 404 DisplayMetrics dm = new DisplayMetrics(); 405 display.getRealMetrics(dm); 406 float[] dims = {dm.widthPixels, dm.heightPixels}; 407 float degrees = getDegreesForRotation(display.getRotation()); 408 boolean requiresRotation = (degrees > 0); 409 if (requiresRotation) { 410 // Get the dimensions of the device in its native orientation 411 Matrix m = new Matrix(); 412 m.preRotate(-degrees); 413 m.mapPoints(dims); 414 dims[0] = Math.abs(dims[0]); 415 dims[1] = Math.abs(dims[1]); 416 } 417 return SurfaceControl.screenshot((int) dims[0], (int) dims[1]); 418 } 419 420 /** Starts the recents activity */ 421 void startAlternateRecentsActivity() { 422 Rect taskRect = mFirstTaskRect; 423 if (taskRect != null && taskRect.width() > 0 && taskRect.height() > 0 && hasFirstTask()) { 424 // Loading from thumbnail 425 Bitmap thumbnail; 426 Bitmap firstThumbnail = loadFirstTaskThumbnail(); 427 if (firstThumbnail == null) { 428 // Load the thumbnail from the screenshot 429 WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 430 Display display = wm.getDefaultDisplay(); 431 Bitmap screenshot = takeScreenshot(display); 432 Resources res = mContext.getResources(); 433 int size = Math.min(screenshot.getWidth(), screenshot.getHeight()); 434 int statusBarHeight = res.getDimensionPixelSize( 435 com.android.internal.R.dimen.status_bar_height); 436 thumbnail = Bitmap.createBitmap(mFirstTaskRect.width(), mFirstTaskRect.height(), 437 Bitmap.Config.ARGB_8888); 438 Canvas c = new Canvas(thumbnail); 439 c.drawBitmap(screenshot, new Rect(0, statusBarHeight, size, statusBarHeight + size), 440 new Rect(0, 0, mFirstTaskRect.width(), mFirstTaskRect.height()), null); 441 c.setBitmap(null); 442 // Recycle the old screenshot 443 screenshot.recycle(); 444 } else { 445 // Create the thumbnail 446 thumbnail = Bitmap.createBitmap(mFirstTaskRect.width(), mFirstTaskRect.height(), 447 Bitmap.Config.ARGB_8888); 448 int size = Math.min(firstThumbnail.getWidth(), firstThumbnail.getHeight()); 449 Canvas c = new Canvas(thumbnail); 450 c.drawBitmap(firstThumbnail, new Rect(0, 0, size, size), 451 new Rect(0, 0, mFirstTaskRect.width(), mFirstTaskRect.height()), null); 452 c.setBitmap(null); 453 // Recycle the old thumbnail 454 firstThumbnail.recycle(); 455 } 456 457 ActivityOptions opts = ActivityOptions.makeThumbnailScaleDownAnimation(mStatusBarView, 458 thumbnail, mFirstTaskRect.left, mFirstTaskRect.top, null); 459 startAlternateRecentsActivity(opts); 460 } else { 461 ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, 462 R.anim.recents_from_launcher_enter, 463 R.anim.recents_from_launcher_exit); 464 startAlternateRecentsActivity(opts); 465 } 466 } 467 468 /** Starts the recents activity */ 469 void startAlternateRecentsActivity(ActivityOptions opts) { 470 Intent intent = new Intent(sToggleRecentsAction); 471 intent.setClassName(sRecentsPackage, sRecentsActivity); 472 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 473 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 474 if (opts != null) { 475 mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle( 476 UserHandle.USER_CURRENT)); 477 } else { 478 mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT)); 479 } 480 } 481 482 @Override 483 public void preloadRecentTasksList() { 484 if (mUseAlternateRecents) { 485 Log.d(TAG, "[RecentsComponent|preloadRecents]"); 486 } else { 487 Intent intent = new Intent(RecentsActivity.PRELOAD_INTENT); 488 intent.setClassName("com.android.systemui", 489 "com.android.systemui.recent.RecentsPreloadReceiver"); 490 mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT)); 491 492 RecentTasksLoader.getInstance(mContext).preloadFirstTask(); 493 } 494 } 495 496 @Override 497 public void cancelPreloadingRecentTasksList() { 498 if (mUseAlternateRecents) { 499 Log.d(TAG, "[RecentsComponent|cancelPreload]"); 500 } else { 501 Intent intent = new Intent(RecentsActivity.CANCEL_PRELOAD_INTENT); 502 intent.setClassName("com.android.systemui", 503 "com.android.systemui.recent.RecentsPreloadReceiver"); 504 mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT)); 505 506 RecentTasksLoader.getInstance(mContext).cancelPreloadingFirstTask(); 507 } 508 } 509 510 @Override 511 public void closeRecents() { 512 if (mUseAlternateRecents) { 513 Log.d(TAG, "[RecentsComponent|closeRecents]"); 514 } else { 515 Intent intent = new Intent(RecentsActivity.CLOSE_RECENTS_INTENT); 516 intent.setPackage("com.android.systemui"); 517 mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT)); 518 519 RecentTasksLoader.getInstance(mContext).cancelPreloadingFirstTask(); 520 } 521 } 522} 523