AlternateRecentsComponent.java revision 96e3bc1f8d7c199df6fca603d0c5e59d9b70ca1b
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.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.res.Configuration; 27import android.content.res.Resources; 28import android.graphics.Bitmap; 29import android.graphics.Canvas; 30import android.graphics.Matrix; 31import android.graphics.Rect; 32import android.os.Bundle; 33import android.os.Handler; 34import android.os.IBinder; 35import android.os.Message; 36import android.os.Messenger; 37import android.os.RemoteException; 38import android.os.UserHandle; 39import android.util.DisplayMetrics; 40import android.view.Display; 41import android.view.Surface; 42import android.view.SurfaceControl; 43import android.view.View; 44import android.view.WindowManager; 45import com.android.systemui.R; 46 47import java.util.ArrayList; 48import java.util.Iterator; 49import java.util.List; 50 51/** A proxy implementation for the recents component */ 52public class AlternateRecentsComponent { 53 54 /** A handler for messages from the recents implementation */ 55 class RecentsMessageHandler extends Handler { 56 @Override 57 public void handleMessage(Message msg) { 58 if (msg.what == MSG_UPDATE_FOR_CONFIGURATION) { 59 Resources res = mContext.getResources(); 60 float statusBarHeight = res.getDimensionPixelSize( 61 com.android.internal.R.dimen.status_bar_height); 62 Bundle replyData = msg.getData().getParcelable(KEY_CONFIGURATION_DATA); 63 mSingleCountFirstTaskRect = replyData.getParcelable(KEY_SINGLE_TASK_STACK_RECT); 64 mSingleCountFirstTaskRect.offset(0, (int) statusBarHeight); 65 mMultipleCountFirstTaskRect = replyData.getParcelable(KEY_MULTIPLE_TASK_STACK_RECT); 66 mMultipleCountFirstTaskRect.offset(0, (int) statusBarHeight); 67 Console.log(Constants.DebugFlags.App.RecentsComponent, 68 "[RecentsComponent|RecentsMessageHandler|handleMessage]", 69 "singleTaskRect: " + mSingleCountFirstTaskRect + 70 " multipleTaskRect: " + mMultipleCountFirstTaskRect); 71 72 // If we had the update the animation rects as a result of onServiceConnected, then 73 // we check for whether we need to toggle the recents here. 74 if (mToggleRecentsUponServiceBound) { 75 startAlternateRecentsActivity(); 76 mToggleRecentsUponServiceBound = false; 77 } 78 } 79 } 80 } 81 82 /** A service connection to the recents implementation */ 83 class RecentsServiceConnection implements ServiceConnection { 84 @Override 85 public void onServiceConnected(ComponentName className, IBinder service) { 86 Console.log(Constants.DebugFlags.App.RecentsComponent, 87 "[RecentsComponent|ServiceConnection|onServiceConnected]", 88 "toggleRecents: " + mToggleRecentsUponServiceBound); 89 mService = new Messenger(service); 90 mServiceIsBound = true; 91 92 if (hasValidTaskRects()) { 93 // Toggle recents if this new service connection was triggered by hitting recents 94 if (mToggleRecentsUponServiceBound) { 95 startAlternateRecentsActivity(); 96 mToggleRecentsUponServiceBound = false; 97 } 98 } else { 99 // Otherwise, update the animation rects before starting the recents if requested 100 updateAnimationRects(); 101 } 102 } 103 104 @Override 105 public void onServiceDisconnected(ComponentName className) { 106 Console.log(Constants.DebugFlags.App.RecentsComponent, 107 "[RecentsComponent|ServiceConnection|onServiceDisconnected]"); 108 mService = null; 109 mServiceIsBound = false; 110 } 111 } 112 113 final public static int MSG_UPDATE_FOR_CONFIGURATION = 0; 114 final public static int MSG_UPDATE_TASK_THUMBNAIL = 1; 115 final public static int MSG_PRELOAD_TASKS = 2; 116 final public static int MSG_CANCEL_PRELOAD_TASKS = 3; 117 final public static int MSG_CLOSE_RECENTS = 4; 118 final public static int MSG_TOGGLE_RECENTS = 5; 119 120 final public static String EXTRA_ANIMATING_WITH_THUMBNAIL = "recents.animatingWithThumbnail"; 121 final public static String KEY_CONFIGURATION_DATA = "recents.data.updateForConfiguration"; 122 final public static String KEY_WINDOW_RECT = "recents.windowRect"; 123 final public static String KEY_SYSTEM_INSETS = "recents.systemInsets"; 124 final public static String KEY_SINGLE_TASK_STACK_RECT = "recents.singleCountTaskRect"; 125 final public static String KEY_MULTIPLE_TASK_STACK_RECT = "recents.multipleCountTaskRect"; 126 127 128 final static int sMinToggleDelay = 425; 129 130 final static String sToggleRecentsAction = "com.android.systemui.recents.SHOW_RECENTS"; 131 final static String sRecentsPackage = "com.android.systemui"; 132 final static String sRecentsActivity = "com.android.systemui.recents.RecentsActivity"; 133 final static String sRecentsService = "com.android.systemui.recents.RecentsService"; 134 135 Context mContext; 136 SystemServicesProxy mSystemServicesProxy; 137 138 // Recents service binding 139 Messenger mService = null; 140 Messenger mMessenger; 141 boolean mServiceIsBound = false; 142 boolean mToggleRecentsUponServiceBound; 143 RecentsServiceConnection mConnection = new RecentsServiceConnection(); 144 145 View mStatusBarView; 146 Rect mSingleCountFirstTaskRect = new Rect(); 147 Rect mMultipleCountFirstTaskRect = new Rect(); 148 long mLastToggleTime; 149 150 public AlternateRecentsComponent(Context context) { 151 mContext = context; 152 mSystemServicesProxy = new SystemServicesProxy(context); 153 mMessenger = new Messenger(new RecentsMessageHandler()); 154 } 155 156 public void onStart() { 157 Console.log(Constants.DebugFlags.App.RecentsComponent, "[RecentsComponent|start]"); 158 159 // Try to create a long-running connection to the recents service 160 bindToRecentsService(false); 161 } 162 163 /** Toggles the alternate recents activity */ 164 public void onToggleRecents(Display display, int layoutDirection, View statusBarView) { 165 Console.logStartTracingTime(Constants.DebugFlags.App.TimeRecentsStartup, 166 Constants.DebugFlags.App.TimeRecentsStartupKey); 167 Console.logStartTracingTime(Constants.DebugFlags.App.TimeRecentsLaunchTask, 168 Constants.DebugFlags.App.TimeRecentsLaunchKey); 169 Console.log(Constants.DebugFlags.App.RecentsComponent, "[RecentsComponent|toggleRecents]", 170 "serviceIsBound: " + mServiceIsBound); 171 mStatusBarView = statusBarView; 172 if (!mServiceIsBound) { 173 // Try to create a long-running connection to the recents service before toggling 174 // recents 175 bindToRecentsService(true); 176 return; 177 } 178 179 try { 180 startAlternateRecentsActivity(); 181 } catch (ActivityNotFoundException e) { 182 Console.logRawError("Failed to launch RecentAppsIntent", e); 183 } 184 } 185 186 public void onPreloadRecents() { 187 // Do nothing 188 } 189 190 public void onCancelPreloadingRecents() { 191 // Do nothing 192 } 193 194 public void onCloseRecents() { 195 Console.log(Constants.DebugFlags.App.RecentsComponent, "[RecentsComponent|closeRecents]"); 196 if (mServiceIsBound) { 197 // Try and update the recents configuration 198 try { 199 Bundle data = new Bundle(); 200 Message msg = Message.obtain(null, MSG_CLOSE_RECENTS, 0, 0); 201 msg.setData(data); 202 mService.send(msg); 203 } catch (RemoteException re) { 204 re.printStackTrace(); 205 } 206 } 207 } 208 209 public void onConfigurationChanged(Configuration newConfig) { 210 updateAnimationRects(); 211 } 212 213 /** Binds to the recents implementation */ 214 private void bindToRecentsService(boolean toggleRecentsUponConnection) { 215 mToggleRecentsUponServiceBound = toggleRecentsUponConnection; 216 Intent intent = new Intent(); 217 intent.setClassName(sRecentsPackage, sRecentsService); 218 mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE); 219 } 220 221 /** Returns whether we have valid task rects to animate to. */ 222 boolean hasValidTaskRects() { 223 return mSingleCountFirstTaskRect != null && mSingleCountFirstTaskRect.width() > 0 && 224 mSingleCountFirstTaskRect.height() > 0 && mMultipleCountFirstTaskRect != null && 225 mMultipleCountFirstTaskRect.width() > 0 && mMultipleCountFirstTaskRect.height() > 0; 226 } 227 228 /** Updates each of the task animation rects. */ 229 void updateAnimationRects() { 230 if (mServiceIsBound) { 231 Resources res = mContext.getResources(); 232 int statusBarHeight = res.getDimensionPixelSize( 233 com.android.internal.R.dimen.status_bar_height); 234 int navBarHeight = res.getDimensionPixelSize( 235 com.android.internal.R.dimen.navigation_bar_height); 236 Rect rect = new Rect(); 237 WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 238 wm.getDefaultDisplay().getRectSize(rect); 239 240 // Try and update the recents configuration 241 try { 242 Bundle data = new Bundle(); 243 data.putParcelable(KEY_WINDOW_RECT, rect); 244 data.putParcelable(KEY_SYSTEM_INSETS, new Rect(0, statusBarHeight, 0, 0)); 245 Message msg = Message.obtain(null, MSG_UPDATE_FOR_CONFIGURATION, 0, 0); 246 msg.setData(data); 247 msg.replyTo = mMessenger; 248 mService.send(msg); 249 } catch (RemoteException re) { 250 re.printStackTrace(); 251 } 252 } 253 } 254 255 /** Loads the first task thumbnail */ 256 Bitmap loadFirstTaskThumbnail() { 257 SystemServicesProxy ssp = mSystemServicesProxy; 258 List<ActivityManager.RecentTaskInfo> tasks = ssp.getRecentTasks(1, 259 UserHandle.CURRENT.getIdentifier()); 260 for (ActivityManager.RecentTaskInfo t : tasks) { 261 // Skip tasks in the home stack 262 if (ssp.isInHomeStack(t.persistentId)) { 263 return null; 264 } 265 266 return ssp.getTaskThumbnail(t.persistentId); 267 } 268 return null; 269 } 270 271 /** Returns whether there is are multiple recents tasks */ 272 boolean hasMultipleRecentsTask(List<ActivityManager.RecentTaskInfo> tasks) { 273 // NOTE: Currently there's no method to get the number of non-home tasks, so we have to 274 // compute this ourselves 275 SystemServicesProxy ssp = mSystemServicesProxy; 276 Iterator<ActivityManager.RecentTaskInfo> iter = tasks.iterator(); 277 while (iter.hasNext()) { 278 ActivityManager.RecentTaskInfo t = iter.next(); 279 280 // Skip tasks in the home stack 281 if (ssp.isInHomeStack(t.persistentId)) { 282 iter.remove(); 283 continue; 284 } 285 } 286 return (tasks.size() > 1); 287 } 288 289 /** Returns whether the base intent of the top task stack was launched with the flag 290 * Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS. */ 291 boolean isTopTaskExcludeFromRecents(List<ActivityManager.RecentTaskInfo> tasks) { 292 if (tasks.size() > 0) { 293 ActivityManager.RecentTaskInfo t = tasks.get(0); 294 Console.log(t.baseIntent.toString()); 295 return (t.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0; 296 } 297 return false; 298 } 299 300 /** Converts from the device rotation to the degree */ 301 float getDegreesForRotation(int value) { 302 switch (value) { 303 case Surface.ROTATION_90: 304 return 360f - 90f; 305 case Surface.ROTATION_180: 306 return 360f - 180f; 307 case Surface.ROTATION_270: 308 return 360f - 270f; 309 } 310 return 0f; 311 } 312 313 /** Takes a screenshot of the surface */ 314 Bitmap takeScreenshot(Display display) { 315 DisplayMetrics dm = new DisplayMetrics(); 316 display.getRealMetrics(dm); 317 float[] dims = {dm.widthPixels, dm.heightPixels}; 318 float degrees = getDegreesForRotation(display.getRotation()); 319 boolean requiresRotation = (degrees > 0); 320 if (requiresRotation) { 321 // Get the dimensions of the device in its native orientation 322 Matrix m = new Matrix(); 323 m.preRotate(-degrees); 324 m.mapPoints(dims); 325 dims[0] = Math.abs(dims[0]); 326 dims[1] = Math.abs(dims[1]); 327 } 328 return SurfaceControl.screenshot((int) dims[0], (int) dims[1]); 329 } 330 331 /** Creates the activity options for a thumbnail transition. */ 332 ActivityOptions getThumbnailTransitionActivityOptions(Rect taskRect) { 333 // Loading from thumbnail 334 Bitmap thumbnail; 335 Bitmap firstThumbnail = loadFirstTaskThumbnail(); 336 if (firstThumbnail != null) { 337 // Create the thumbnail 338 thumbnail = Bitmap.createBitmap(taskRect.width(), taskRect.height(), 339 Bitmap.Config.ARGB_8888); 340 int size = Math.min(firstThumbnail.getWidth(), firstThumbnail.getHeight()); 341 Canvas c = new Canvas(thumbnail); 342 c.drawBitmap(firstThumbnail, new Rect(0, 0, size, size), 343 new Rect(0, 0, taskRect.width(), taskRect.height()), null); 344 c.setBitmap(null); 345 // Recycle the old thumbnail 346 firstThumbnail.recycle(); 347 } else { 348 // Load the thumbnail from the screenshot if can't get one from the system 349 WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 350 Display display = wm.getDefaultDisplay(); 351 Bitmap screenshot = takeScreenshot(display); 352 if (screenshot != null) { 353 Resources res = mContext.getResources(); 354 int size = Math.min(screenshot.getWidth(), screenshot.getHeight()); 355 int statusBarHeight = res.getDimensionPixelSize( 356 com.android.internal.R.dimen.status_bar_height); 357 thumbnail = Bitmap.createBitmap(taskRect.width(), taskRect.height(), 358 Bitmap.Config.ARGB_8888); 359 Canvas c = new Canvas(thumbnail); 360 c.drawBitmap(screenshot, new Rect(0, statusBarHeight, size, statusBarHeight + 361 size), new Rect(0, 0, taskRect.width(), taskRect.height()), null); 362 c.setBitmap(null); 363 // Recycle the temporary screenshot 364 screenshot.recycle(); 365 } else { 366 return null; 367 } 368 } 369 370 return ActivityOptions.makeThumbnailScaleDownAnimation(mStatusBarView, thumbnail, 371 taskRect.left, taskRect.top, null); 372 } 373 374 /** Starts the recents activity */ 375 void startAlternateRecentsActivity() { 376 // If the user has toggled it too quickly, then just eat up the event here (it's better than 377 // showing a janky screenshot). 378 // NOTE: Ideally, the screenshot mechanism would take the window transform into account 379 if (System.currentTimeMillis() - mLastToggleTime < sMinToggleDelay) { 380 return; 381 } 382 383 // If Recents is the front most activity, then we should just communicate with it directly 384 // to launch the first task or dismiss itself 385 SystemServicesProxy ssp = mSystemServicesProxy; 386 List<ActivityManager.RunningTaskInfo> tasks = ssp.getRunningTasks(1); 387 boolean isTopTaskHome = false; 388 if (!tasks.isEmpty()) { 389 ActivityManager.RunningTaskInfo topTask = tasks.get(0); 390 ComponentName topActivity = topTask.topActivity; 391 392 // Check if the front most activity is recents 393 if (topActivity.getPackageName().equals(sRecentsPackage) && 394 topActivity.getClassName().equals(sRecentsActivity)) { 395 // Notify Recents to toggle itself 396 try { 397 Bundle data = new Bundle(); 398 Message msg = Message.obtain(null, MSG_TOGGLE_RECENTS, 0, 0); 399 msg.setData(data); 400 mService.send(msg); 401 402 // Time this path 403 Console.logTraceTime(Constants.DebugFlags.App.TimeRecentsStartup, 404 Constants.DebugFlags.App.TimeRecentsStartupKey, "sendToggleRecents"); 405 Console.logTraceTime(Constants.DebugFlags.App.TimeRecentsLaunchTask, 406 Constants.DebugFlags.App.TimeRecentsLaunchKey, "sendToggleRecents"); 407 } catch (RemoteException re) { 408 re.printStackTrace(); 409 } 410 mLastToggleTime = System.currentTimeMillis(); 411 return; 412 } 413 414 // Determine whether the top task is currently home 415 isTopTaskHome = ssp.isInHomeStack(topTask.id); 416 } 417 418 // Otherwise, Recents is not the front-most activity and we should animate into it. If 419 // the activity at the root of the top task stack is excluded from recents, or if that 420 // task stack is in the home stack, then we just do a simple transition. Otherwise, we 421 // animate to the rects defined by the Recents service, which can differ depending on the 422 // number of items in the list. 423 List<ActivityManager.RecentTaskInfo> recentTasks = 424 ssp.getRecentTasks(4, UserHandle.CURRENT.getIdentifier()); 425 Rect taskRect = hasMultipleRecentsTask(recentTasks) ? mMultipleCountFirstTaskRect : 426 mSingleCountFirstTaskRect; 427 boolean isTaskExcludedFromRecents = isTopTaskExcludeFromRecents(recentTasks); 428 boolean useThumbnailTransition = !isTopTaskHome && !isTaskExcludedFromRecents && 429 hasValidTaskRects(); 430 431 if (useThumbnailTransition) { 432 // Try starting with a thumbnail transition 433 ActivityOptions opts = getThumbnailTransitionActivityOptions(taskRect); 434 if (opts != null) { 435 startAlternateRecentsActivity(opts, true); 436 } else { 437 // Fall through below to the non-thumbnail transition 438 useThumbnailTransition = false; 439 } 440 } 441 442 // If there is no thumbnail transition, then just use a generic transition 443 // XXX: This should be different between home and from a recents-excluded app, perhaps the 444 // recents-excluded app should still show up in recents, when the app is in the 445 // foreground 446 if (!useThumbnailTransition) { 447 ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, 448 R.anim.recents_from_launcher_enter, 449 R.anim.recents_from_launcher_exit); 450 startAlternateRecentsActivity(opts, false); 451 } 452 453 Console.logTraceTime(Constants.DebugFlags.App.TimeRecentsStartup, 454 Constants.DebugFlags.App.TimeRecentsStartupKey, "startRecentsActivity"); 455 mLastToggleTime = System.currentTimeMillis(); 456 } 457 458 /** Starts the recents activity */ 459 void startAlternateRecentsActivity(ActivityOptions opts, boolean animatingWithThumbnail) { 460 Intent intent = new Intent(sToggleRecentsAction); 461 intent.setClassName(sRecentsPackage, sRecentsActivity); 462 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 463 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 464 intent.putExtra(EXTRA_ANIMATING_WITH_THUMBNAIL, animatingWithThumbnail); 465 if (opts != null) { 466 mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle( 467 UserHandle.USER_CURRENT)); 468 } else { 469 mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT)); 470 } 471 } 472} 473