AlternateRecentsComponent.java revision 0d767551c55d9e594a0b944bd1926c21a344b5ae
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.Iterator; 48import java.util.List; 49 50/** A proxy implementation for the recents component */ 51public class AlternateRecentsComponent { 52 53 /** A handler for messages from the recents implementation */ 54 class RecentsMessageHandler extends Handler { 55 @Override 56 public void handleMessage(Message msg) { 57 if (msg.what == MSG_UPDATE_FOR_CONFIGURATION) { 58 Resources res = mContext.getResources(); 59 float statusBarHeight = res.getDimensionPixelSize( 60 com.android.internal.R.dimen.status_bar_height); 61 Bundle replyData = msg.getData().getParcelable("replyData"); 62 mSingleCountFirstTaskRect = replyData.getParcelable("singleCountTaskRect"); 63 mSingleCountFirstTaskRect.offset(0, (int) statusBarHeight); 64 mMultipleCountFirstTaskRect = replyData.getParcelable("multipleCountTaskRect"); 65 mMultipleCountFirstTaskRect.offset(0, (int) statusBarHeight); 66 } 67 } 68 } 69 70 /** A service connection to the recents implementation */ 71 class RecentsServiceConnection implements ServiceConnection { 72 @Override 73 public void onServiceConnected(ComponentName className, IBinder service) { 74 Console.log(Constants.DebugFlags.App.RecentsComponent, 75 "[RecentsComponent|ServiceConnection|onServiceConnected]", 76 "toggleRecents: " + mToggleRecentsUponServiceBound); 77 mService = new Messenger(service); 78 mServiceIsBound = true; 79 80 // Toggle recents if this service connection was triggered by hitting the recents button 81 if (mToggleRecentsUponServiceBound) { 82 startAlternateRecentsActivity(); 83 } 84 mToggleRecentsUponServiceBound = false; 85 } 86 87 @Override 88 public void onServiceDisconnected(ComponentName className) { 89 Console.log(Constants.DebugFlags.App.RecentsComponent, 90 "[RecentsComponent|ServiceConnection|onServiceDisconnected]"); 91 mService = null; 92 mServiceIsBound = false; 93 } 94 } 95 96 final static int MSG_UPDATE_FOR_CONFIGURATION = 0; 97 final static int MSG_UPDATE_TASK_THUMBNAIL = 1; 98 final static int MSG_PRELOAD_TASKS = 2; 99 final static int MSG_CANCEL_PRELOAD_TASKS = 3; 100 final static int MSG_CLOSE_RECENTS = 4; 101 final static int MSG_TOGGLE_RECENTS = 5; 102 103 final static int sMinToggleDelay = 425; 104 105 final static String sToggleRecentsAction = "com.android.systemui.recents.SHOW_RECENTS"; 106 final static String sRecentsPackage = "com.android.systemui"; 107 final static String sRecentsActivity = "com.android.systemui.recents.RecentsActivity"; 108 final static String sRecentsService = "com.android.systemui.recents.RecentsService"; 109 110 Context mContext; 111 SystemServicesProxy mSystemServicesProxy; 112 113 // Recents service binding 114 Messenger mService = null; 115 Messenger mMessenger; 116 boolean mServiceIsBound = false; 117 boolean mToggleRecentsUponServiceBound; 118 RecentsServiceConnection mConnection = new RecentsServiceConnection(); 119 120 View mStatusBarView; 121 Rect mSingleCountFirstTaskRect = new Rect(); 122 Rect mMultipleCountFirstTaskRect = new Rect(); 123 long mLastToggleTime; 124 125 public AlternateRecentsComponent(Context context) { 126 mContext = context; 127 mSystemServicesProxy = new SystemServicesProxy(context); 128 mMessenger = new Messenger(new RecentsMessageHandler()); 129 } 130 131 public void onStart() { 132 Console.log(Constants.DebugFlags.App.RecentsComponent, "[RecentsComponent|start]"); 133 134 // Try to create a long-running connection to the recents service 135 bindToRecentsService(false); 136 } 137 138 /** Toggles the alternate recents activity */ 139 public void onToggleRecents(Display display, int layoutDirection, View statusBarView) { 140 Console.logStartTracingTime(Constants.DebugFlags.App.TimeRecentsStartup, 141 Constants.DebugFlags.App.TimeRecentsStartupKey); 142 Console.logStartTracingTime(Constants.DebugFlags.App.TimeRecentsLaunchTask, 143 Constants.DebugFlags.App.TimeRecentsLaunchKey); 144 Console.log(Constants.DebugFlags.App.RecentsComponent, "[RecentsComponent|toggleRecents]", 145 "serviceIsBound: " + mServiceIsBound); 146 mStatusBarView = statusBarView; 147 if (!mServiceIsBound) { 148 // Try to create a long-running connection to the recents service before toggling 149 // recents 150 bindToRecentsService(true); 151 return; 152 } 153 154 try { 155 startAlternateRecentsActivity(); 156 } catch (ActivityNotFoundException e) { 157 Console.logRawError("Failed to launch RecentAppsIntent", e); 158 } 159 } 160 161 public void onPreloadRecents() { 162 // Do nothing 163 } 164 165 public void onCancelPreloadingRecents() { 166 // Do nothing 167 } 168 169 public void onCloseRecents() { 170 Console.log(Constants.DebugFlags.App.RecentsComponent, "[RecentsComponent|closeRecents]"); 171 if (mServiceIsBound) { 172 // Try and update the recents configuration 173 try { 174 Bundle data = new Bundle(); 175 Message msg = Message.obtain(null, MSG_CLOSE_RECENTS, 0, 0); 176 msg.setData(data); 177 mService.send(msg); 178 } catch (RemoteException re) { 179 re.printStackTrace(); 180 } 181 } 182 } 183 184 public void onConfigurationChanged(Configuration newConfig) { 185 if (mServiceIsBound) { 186 Resources res = mContext.getResources(); 187 int statusBarHeight = res.getDimensionPixelSize( 188 com.android.internal.R.dimen.status_bar_height); 189 int navBarHeight = res.getDimensionPixelSize( 190 com.android.internal.R.dimen.navigation_bar_height); 191 Rect rect = new Rect(); 192 WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 193 wm.getDefaultDisplay().getRectSize(rect); 194 195 // Try and update the recents configuration 196 try { 197 Bundle data = new Bundle(); 198 data.putParcelable("windowRect", rect); 199 data.putParcelable("systemInsets", new Rect(0, statusBarHeight, 0, 0)); 200 Message msg = Message.obtain(null, MSG_UPDATE_FOR_CONFIGURATION, 0, 0); 201 msg.setData(data); 202 msg.replyTo = mMessenger; 203 mService.send(msg); 204 } catch (RemoteException re) { 205 re.printStackTrace(); 206 } 207 } 208 } 209 210 /** Binds to the recents implementation */ 211 private void bindToRecentsService(boolean toggleRecentsUponConnection) { 212 mToggleRecentsUponServiceBound = toggleRecentsUponConnection; 213 Intent intent = new Intent(); 214 intent.setClassName(sRecentsPackage, sRecentsService); 215 mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE); 216 } 217 218 /** Loads the first task thumbnail */ 219 Bitmap loadFirstTaskThumbnail() { 220 SystemServicesProxy ssp = mSystemServicesProxy; 221 List<ActivityManager.RecentTaskInfo> tasks = ssp.getRecentTasks(1, 222 UserHandle.CURRENT.getIdentifier()); 223 for (ActivityManager.RecentTaskInfo t : tasks) { 224 // Skip tasks in the home stack 225 if (ssp.isInHomeStack(t.persistentId)) { 226 return null; 227 } 228 229 Bitmap thumbnail = ssp.getTaskThumbnail(t.persistentId); 230 return thumbnail; 231 } 232 return null; 233 } 234 235 /** Returns whether there is are multiple recents tasks */ 236 boolean hasMultipleRecentsTask() { 237 // NOTE: Currently there's no method to get the number of non-home tasks, so we have to 238 // compute this ourselves 239 SystemServicesProxy ssp = mSystemServicesProxy; 240 List<ActivityManager.RecentTaskInfo> tasks = ssp.getRecentTasks(4, 241 UserHandle.CURRENT.getIdentifier()); 242 Iterator<ActivityManager.RecentTaskInfo> iter = tasks.iterator(); 243 while (iter.hasNext()) { 244 ActivityManager.RecentTaskInfo t = iter.next(); 245 246 // Skip tasks in the home stack 247 if (ssp.isInHomeStack(t.persistentId)) { 248 iter.remove(); 249 continue; 250 } 251 } 252 return (tasks.size() > 1); 253 } 254 255 /** Converts from the device rotation to the degree */ 256 float getDegreesForRotation(int value) { 257 switch (value) { 258 case Surface.ROTATION_90: 259 return 360f - 90f; 260 case Surface.ROTATION_180: 261 return 360f - 180f; 262 case Surface.ROTATION_270: 263 return 360f - 270f; 264 } 265 return 0f; 266 } 267 268 /** Takes a screenshot of the surface */ 269 Bitmap takeScreenshot(Display display) { 270 DisplayMetrics dm = new DisplayMetrics(); 271 display.getRealMetrics(dm); 272 float[] dims = {dm.widthPixels, dm.heightPixels}; 273 float degrees = getDegreesForRotation(display.getRotation()); 274 boolean requiresRotation = (degrees > 0); 275 if (requiresRotation) { 276 // Get the dimensions of the device in its native orientation 277 Matrix m = new Matrix(); 278 m.preRotate(-degrees); 279 m.mapPoints(dims); 280 dims[0] = Math.abs(dims[0]); 281 dims[1] = Math.abs(dims[1]); 282 } 283 return SurfaceControl.screenshot((int) dims[0], (int) dims[1]); 284 } 285 286 /** Starts the recents activity */ 287 void startAlternateRecentsActivity() { 288 // If the user has toggled it too quickly, then just eat up the event here (it's better than 289 // showing a janky screenshot). 290 // NOTE: Ideally, the screenshot mechanism would take the window transform into account 291 if (System.currentTimeMillis() - mLastToggleTime < sMinToggleDelay) { 292 return; 293 } 294 295 // If Recents is the front most activity, then we should just communicate with it directly 296 // to launch the first task or dismiss itself 297 SystemServicesProxy ssp = mSystemServicesProxy; 298 List<ActivityManager.RunningTaskInfo> tasks = ssp.getRunningTasks(1); 299 boolean isTopTaskHome = false; 300 if (!tasks.isEmpty()) { 301 ActivityManager.RunningTaskInfo topTask = tasks.get(0); 302 ComponentName topActivity = topTask.topActivity; 303 304 // Check if the front most activity is recents 305 if (topActivity.getPackageName().equals(sRecentsPackage) && 306 topActivity.getClassName().equals(sRecentsActivity)) { 307 // Notify Recents to toggle itself 308 try { 309 Bundle data = new Bundle(); 310 Message msg = Message.obtain(null, MSG_TOGGLE_RECENTS, 0, 0); 311 msg.setData(data); 312 mService.send(msg); 313 314 // Time this path 315 Console.logTraceTime(Constants.DebugFlags.App.TimeRecentsStartup, 316 Constants.DebugFlags.App.TimeRecentsStartupKey, "sendToggleRecents"); 317 Console.logTraceTime(Constants.DebugFlags.App.TimeRecentsLaunchTask, 318 Constants.DebugFlags.App.TimeRecentsLaunchKey, "sendToggleRecents"); 319 } catch (RemoteException re) { 320 re.printStackTrace(); 321 } 322 mLastToggleTime = System.currentTimeMillis(); 323 return; 324 } 325 326 // Determine whether the top task is currently home 327 isTopTaskHome = ssp.isInHomeStack(topTask.id); 328 } 329 330 // Otherwise, Recents is not the front-most activity and we should animate into it 331 boolean hasMultipleTasks = hasMultipleRecentsTask(); 332 Rect taskRect = hasMultipleTasks ? mMultipleCountFirstTaskRect : mSingleCountFirstTaskRect; 333 if (!isTopTaskHome && taskRect != null && taskRect.width() > 0 && taskRect.height() > 0) { 334 // Loading from thumbnail 335 Bitmap thumbnail; 336 Bitmap firstThumbnail = loadFirstTaskThumbnail(); 337 if (firstThumbnail != null) {// 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 Resources res = mContext.getResources(); 353 int size = Math.min(screenshot.getWidth(), screenshot.getHeight()); 354 int statusBarHeight = res.getDimensionPixelSize( 355 com.android.internal.R.dimen.status_bar_height); 356 thumbnail = Bitmap.createBitmap(taskRect.width(), taskRect.height(), 357 Bitmap.Config.ARGB_8888); 358 Canvas c = new Canvas(thumbnail); 359 c.drawBitmap(screenshot, new Rect(0, statusBarHeight, size, statusBarHeight + size), 360 new Rect(0, 0, taskRect.width(), taskRect.height()), null); 361 c.setBitmap(null); 362 // Recycle the temporary screenshot 363 screenshot.recycle(); 364 } 365 366 ActivityOptions opts = ActivityOptions.makeThumbnailScaleDownAnimation(mStatusBarView, 367 thumbnail, taskRect.left, taskRect.top, null); 368 startAlternateRecentsActivity(opts); 369 } else { 370 ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, 371 R.anim.recents_from_launcher_enter, 372 R.anim.recents_from_launcher_exit); 373 startAlternateRecentsActivity(opts); 374 } 375 376 Console.logTraceTime(Constants.DebugFlags.App.TimeRecentsStartup, 377 Constants.DebugFlags.App.TimeRecentsStartupKey, "startRecentsActivity"); 378 mLastToggleTime = System.currentTimeMillis(); 379 } 380 381 /** Starts the recents activity */ 382 void startAlternateRecentsActivity(ActivityOptions opts) { 383 Intent intent = new Intent(sToggleRecentsAction); 384 intent.setClassName(sRecentsPackage, sRecentsActivity); 385 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 386 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 387 if (opts != null) { 388 mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle( 389 UserHandle.USER_CURRENT)); 390 } else { 391 mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT)); 392 } 393 } 394} 395