AlternateRecentsComponent.java revision bd91297e895410bb27df3e930be5d9eab30129af
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.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/** A proxy implementation for the recents component */ 59public class AlternateRecentsComponent { 60 61 /** A handler for messages from the recents implementation */ 62 class RecentsMessageHandler extends Handler { 63 @Override 64 public void handleMessage(Message msg) { 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 Console.log(Constants.DebugFlags.App.RecentsComponent, 80 "[RecentsComponent|ServiceConnection|onServiceConnected]", 81 "toggleRecents: " + mToggleRecentsUponServiceBound); 82 mService = new Messenger(service); 83 mServiceIsBound = true; 84 85 // Toggle recents if this service connection was triggered by hitting the recents button 86 if (mToggleRecentsUponServiceBound) { 87 startAlternateRecentsActivity(); 88 } 89 mToggleRecentsUponServiceBound = false; 90 } 91 92 @Override 93 public void onServiceDisconnected(ComponentName className) { 94 Console.log(Constants.DebugFlags.App.RecentsComponent, 95 "[RecentsComponent|ServiceConnection|onServiceDisconnected]"); 96 mService = null; 97 mServiceIsBound = false; 98 } 99 } 100 101 final static int MSG_UPDATE_FOR_CONFIGURATION = 0; 102 final static int MSG_UPDATE_TASK_THUMBNAIL = 1; 103 final static int MSG_PRELOAD_TASKS = 2; 104 final static int MSG_CANCEL_PRELOAD_TASKS = 3; 105 final static int MSG_CLOSE_RECENTS = 4; 106 final static int MSG_TOGGLE_RECENTS = 5; 107 108 final static int sMinToggleDelay = 425; 109 110 final static String sToggleRecentsAction = "com.android.systemui.recents.SHOW_RECENTS"; 111 final static String sRecentsPackage = "com.android.systemui"; 112 final static String sRecentsActivity = "com.android.systemui.recents.RecentsActivity"; 113 final static String sRecentsService = "com.android.systemui.recents.RecentsService"; 114 115 Context mContext; 116 117 // Recents service binding 118 Messenger mService = null; 119 Messenger mMessenger; 120 boolean mServiceIsBound = false; 121 boolean mToggleRecentsUponServiceBound; 122 RecentsServiceConnection mConnection = new RecentsServiceConnection(); 123 124 View mStatusBarView; 125 Rect mFirstTaskRect = new Rect(); 126 long mLastToggleTime; 127 128 public AlternateRecentsComponent(Context context) { 129 mContext = context; 130 mMessenger = new Messenger(new RecentsMessageHandler()); 131 } 132 133 public void onStart() { 134 Console.log(Constants.DebugFlags.App.RecentsComponent, "[RecentsComponent|start]"); 135 136 // Try to create a long-running connection to the recents service 137 bindToRecentsService(false); 138 } 139 140 /** Toggles the alternate recents activity */ 141 public void onToggleRecents(Display display, int layoutDirection, View statusBarView) { 142 Console.logStartTracingTime(Constants.DebugFlags.App.TimeRecentsStartup, 143 Constants.DebugFlags.App.TimeRecentsStartupKey); 144 Console.logStartTracingTime(Constants.DebugFlags.App.TimeRecentsLaunchTask, 145 Constants.DebugFlags.App.TimeRecentsLaunchKey); 146 Console.log(Constants.DebugFlags.App.RecentsComponent, "[RecentsComponent|toggleRecents]", 147 "serviceIsBound: " + mServiceIsBound); 148 mStatusBarView = statusBarView; 149 if (!mServiceIsBound) { 150 // Try to create a long-running connection to the recents service before toggling 151 // recents 152 bindToRecentsService(true); 153 return; 154 } 155 156 try { 157 startAlternateRecentsActivity(); 158 } catch (ActivityNotFoundException e) { 159 Console.logRawError("Failed to launch RecentAppsIntent", e); 160 } 161 } 162 163 public void onPreloadRecents() { 164 // Do nothing 165 } 166 167 public void onCancelPreloadingRecents() { 168 // Do nothing 169 } 170 171 public void onCloseRecents() { 172 Console.log(Constants.DebugFlags.App.RecentsComponent, "[RecentsComponent|closeRecents]"); 173 if (mServiceIsBound) { 174 // Try and update the recents configuration 175 try { 176 Bundle data = new Bundle(); 177 Message msg = Message.obtain(null, MSG_CLOSE_RECENTS, 0, 0); 178 msg.setData(data); 179 mService.send(msg); 180 } catch (RemoteException re) { 181 re.printStackTrace(); 182 } 183 } 184 } 185 186 public void onConfigurationChanged(Configuration newConfig) { 187 if (mServiceIsBound) { 188 Resources res = mContext.getResources(); 189 int statusBarHeight = res.getDimensionPixelSize( 190 com.android.internal.R.dimen.status_bar_height); 191 int navBarHeight = res.getDimensionPixelSize( 192 com.android.internal.R.dimen.navigation_bar_height); 193 Rect rect = new Rect(); 194 WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 195 wm.getDefaultDisplay().getRectSize(rect); 196 197 // Try and update the recents configuration 198 try { 199 Bundle data = new Bundle(); 200 data.putParcelable("windowRect", rect); 201 data.putParcelable("systemInsets", new Rect(0, statusBarHeight, 0, 0)); 202 Message msg = Message.obtain(null, MSG_UPDATE_FOR_CONFIGURATION, 0, 0); 203 msg.setData(data); 204 msg.replyTo = mMessenger; 205 mService.send(msg); 206 } catch (RemoteException re) { 207 re.printStackTrace(); 208 } 209 } 210 } 211 212 /** Binds to the recents implementation */ 213 private void bindToRecentsService(boolean toggleRecentsUponConnection) { 214 mToggleRecentsUponServiceBound = toggleRecentsUponConnection; 215 Intent intent = new Intent(); 216 intent.setClassName(sRecentsPackage, sRecentsService); 217 mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE); 218 } 219 220 /** Loads the first task thumbnail */ 221 Bitmap loadFirstTaskThumbnail() { 222 ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); 223 List<ActivityManager.RecentTaskInfo> tasks = am.getRecentTasksForUser(1, 224 ActivityManager.RECENT_IGNORE_UNAVAILABLE | ActivityManager.RECENT_INCLUDE_RELATED, 225 UserHandle.CURRENT.getIdentifier()); 226 for (ActivityManager.RecentTaskInfo t : tasks) { 227 // Skip tasks in the home stack 228 if (am.isInHomeStack(t.persistentId)) { 229 return null; 230 } 231 232 Bitmap thumbnail = am.getTaskTopThumbnail(t.persistentId); 233 return thumbnail; 234 } 235 return null; 236 } 237 238 /** Returns whether there is a first task */ 239 boolean hasFirstTask() { 240 ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); 241 List<ActivityManager.RecentTaskInfo> tasks = am.getRecentTasksForUser(1, 242 ActivityManager.RECENT_IGNORE_UNAVAILABLE | ActivityManager.RECENT_INCLUDE_RELATED, 243 UserHandle.CURRENT.getIdentifier()); 244 for (ActivityManager.RecentTaskInfo t : tasks) { 245 // Skip tasks in the home stack 246 if (am.isInHomeStack(t.persistentId)) { 247 continue; 248 } 249 250 return true; 251 } 252 return false; 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 ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); 298 List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1); 299 if (!tasks.isEmpty()) { 300 ComponentName topActivity = tasks.get(0).topActivity; 301 302 // Check if the front most activity is recents 303 if (topActivity.getPackageName().equals(sRecentsPackage) && 304 topActivity.getClassName().equals(sRecentsActivity)) { 305 // Notify Recents to toggle itself 306 try { 307 Bundle data = new Bundle(); 308 Message msg = Message.obtain(null, MSG_TOGGLE_RECENTS, 0, 0); 309 msg.setData(data); 310 mService.send(msg); 311 312 // Time this path 313 Console.logTraceTime(Constants.DebugFlags.App.TimeRecentsStartup, 314 Constants.DebugFlags.App.TimeRecentsStartupKey, "sendToggleRecents"); 315 Console.logTraceTime(Constants.DebugFlags.App.TimeRecentsLaunchTask, 316 Constants.DebugFlags.App.TimeRecentsLaunchKey, "sendToggleRecents"); 317 } catch (RemoteException re) { 318 re.printStackTrace(); 319 } 320 mLastToggleTime = System.currentTimeMillis(); 321 return; 322 } 323 } 324 325 // Otherwise, Recents is not the front-most activity and we should animate into it 326 Rect taskRect = mFirstTaskRect; 327 if (taskRect != null && taskRect.width() > 0 && taskRect.height() > 0 && hasFirstTask()) { 328 // Loading from thumbnail 329 Bitmap thumbnail; 330 Bitmap firstThumbnail = loadFirstTaskThumbnail(); 331 if (firstThumbnail == null) { 332 // Load the thumbnail from the screenshot 333 WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 334 Display display = wm.getDefaultDisplay(); 335 Bitmap screenshot = takeScreenshot(display); 336 Resources res = mContext.getResources(); 337 int size = Math.min(screenshot.getWidth(), screenshot.getHeight()); 338 int statusBarHeight = res.getDimensionPixelSize( 339 com.android.internal.R.dimen.status_bar_height); 340 thumbnail = Bitmap.createBitmap(mFirstTaskRect.width(), mFirstTaskRect.height(), 341 Bitmap.Config.ARGB_8888); 342 Canvas c = new Canvas(thumbnail); 343 c.drawBitmap(screenshot, new Rect(0, statusBarHeight, size, statusBarHeight + size), 344 new Rect(0, 0, mFirstTaskRect.width(), mFirstTaskRect.height()), null); 345 c.setBitmap(null); 346 // Recycle the old screenshot 347 screenshot.recycle(); 348 } else { 349 // Create the thumbnail 350 thumbnail = Bitmap.createBitmap(mFirstTaskRect.width(), mFirstTaskRect.height(), 351 Bitmap.Config.ARGB_8888); 352 int size = Math.min(firstThumbnail.getWidth(), firstThumbnail.getHeight()); 353 Canvas c = new Canvas(thumbnail); 354 c.drawBitmap(firstThumbnail, new Rect(0, 0, size, size), 355 new Rect(0, 0, mFirstTaskRect.width(), mFirstTaskRect.height()), null); 356 c.setBitmap(null); 357 // Recycle the old thumbnail 358 firstThumbnail.recycle(); 359 } 360 361 ActivityOptions opts = ActivityOptions.makeThumbnailScaleDownAnimation(mStatusBarView, 362 thumbnail, mFirstTaskRect.left, mFirstTaskRect.top, null); 363 startAlternateRecentsActivity(opts); 364 } else { 365 ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, 366 R.anim.recents_from_launcher_enter, 367 R.anim.recents_from_launcher_exit); 368 startAlternateRecentsActivity(opts); 369 } 370 371 Console.logTraceTime(Constants.DebugFlags.App.TimeRecentsStartup, 372 Constants.DebugFlags.App.TimeRecentsStartupKey, "startRecentsActivity"); 373 mLastToggleTime = System.currentTimeMillis(); 374 } 375 376 /** Starts the recents activity */ 377 void startAlternateRecentsActivity(ActivityOptions opts) { 378 Intent intent = new Intent(sToggleRecentsAction); 379 intent.setClassName(sRecentsPackage, sRecentsActivity); 380 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 381 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 382 if (opts != null) { 383 mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle( 384 UserHandle.USER_CURRENT)); 385 } else { 386 mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT)); 387 } 388 } 389} 390