SystemServicesProxy.java revision 1f24c7e37bc794057a156a730c7e4b53b01212ed
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.misc; 18 19import android.app.ActivityManager; 20import android.app.ActivityManagerNative; 21import android.app.ActivityOptions; 22import android.app.AppGlobals; 23import android.app.IActivityManager; 24import android.app.SearchManager; 25import android.appwidget.AppWidgetHost; 26import android.appwidget.AppWidgetManager; 27import android.appwidget.AppWidgetProviderInfo; 28import android.content.ComponentName; 29import android.content.Context; 30import android.content.Intent; 31import android.content.pm.ActivityInfo; 32import android.content.pm.IPackageManager; 33import android.content.pm.PackageManager; 34import android.graphics.Bitmap; 35import android.graphics.BitmapFactory; 36import android.graphics.Canvas; 37import android.graphics.Color; 38import android.graphics.Paint; 39import android.graphics.PorterDuff; 40import android.graphics.PorterDuffXfermode; 41import android.graphics.Rect; 42import android.graphics.drawable.ColorDrawable; 43import android.graphics.drawable.Drawable; 44import android.os.Bundle; 45import android.os.ParcelFileDescriptor; 46import android.os.RemoteException; 47import android.os.UserHandle; 48import android.os.UserManager; 49import android.util.Log; 50import android.util.Pair; 51import android.view.Display; 52import android.view.DisplayInfo; 53import android.view.SurfaceControl; 54import android.view.WindowManager; 55import com.android.systemui.recents.Constants; 56 57import java.io.IOException; 58import java.util.ArrayList; 59import java.util.Iterator; 60import java.util.List; 61import java.util.Random; 62 63/** 64 * Acts as a shim around the real system services that we need to access data from, and provides 65 * a point of injection when testing UI. 66 */ 67public class SystemServicesProxy { 68 final static String TAG = "SystemServicesProxy"; 69 70 ActivityManager mAm; 71 IActivityManager mIam; 72 AppWidgetManager mAwm; 73 PackageManager mPm; 74 IPackageManager mIpm; 75 UserManager mUm; 76 SearchManager mSm; 77 WindowManager mWm; 78 Display mDisplay; 79 String mRecentsPackage; 80 ComponentName mAssistComponent; 81 82 Bitmap mDummyIcon; 83 Paint mBgProtectionPaint; 84 Canvas mBgProtectionCanvas; 85 86 /** Private constructor */ 87 public SystemServicesProxy(Context context) { 88 mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 89 mIam = ActivityManagerNative.getDefault(); 90 mAwm = AppWidgetManager.getInstance(context); 91 mPm = context.getPackageManager(); 92 mUm = (UserManager) context.getSystemService(Context.USER_SERVICE); 93 mIpm = AppGlobals.getPackageManager(); 94 mSm = (SearchManager) context.getSystemService(Context.SEARCH_SERVICE); 95 mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 96 mDisplay = mWm.getDefaultDisplay(); 97 mRecentsPackage = context.getPackageName(); 98 99 // Create the protection paints 100 mBgProtectionPaint = new Paint(); 101 mBgProtectionPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP)); 102 mBgProtectionPaint.setColor(0xFFffffff); 103 mBgProtectionCanvas = new Canvas(); 104 105 // Resolve the assist intent 106 Intent assist = mSm.getAssistIntent(context, false); 107 if (assist != null) { 108 mAssistComponent = assist.getComponent(); 109 } 110 111 if (Constants.DebugFlags.App.EnableSystemServicesProxy) { 112 // Create a dummy icon 113 mDummyIcon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); 114 mDummyIcon.eraseColor(0xFF999999); 115 } 116 } 117 118 /** Returns a list of the recents tasks */ 119 public List<ActivityManager.RecentTaskInfo> getRecentTasks(int numLatestTasks, int userId) { 120 if (mAm == null) return null; 121 122 // If we are mocking, then create some recent tasks 123 if (Constants.DebugFlags.App.EnableSystemServicesProxy) { 124 ArrayList<ActivityManager.RecentTaskInfo> tasks = 125 new ArrayList<ActivityManager.RecentTaskInfo>(); 126 int count = Math.min(numLatestTasks, Constants.DebugFlags.App.SystemServicesProxyMockTaskCount); 127 for (int i = 0; i < count; i++) { 128 // Create a dummy component name 129 int packageIndex = i % Constants.DebugFlags.App.SystemServicesProxyMockPackageCount; 130 ComponentName cn = new ComponentName("com.android.test" + packageIndex, 131 "com.android.test" + i + ".Activity"); 132 String description = "" + i + " - " + 133 Long.toString(Math.abs(new Random().nextLong()), 36); 134 // Create the recent task info 135 ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo(); 136 rti.id = rti.persistentId = i; 137 rti.baseIntent = new Intent(); 138 rti.baseIntent.setComponent(cn); 139 rti.description = description; 140 rti.firstActiveTime = rti.lastActiveTime = i; 141 if (i % 2 == 0) { 142 rti.taskDescription = new ActivityManager.TaskDescription(description, 143 Bitmap.createBitmap(mDummyIcon), 144 0xFF000000 | (0xFFFFFF & new Random().nextInt())); 145 } else { 146 rti.taskDescription = new ActivityManager.TaskDescription(); 147 } 148 tasks.add(rti); 149 } 150 return tasks; 151 } 152 153 // Remove home/recents/excluded tasks 154 int minNumTasksToQuery = 10; 155 int numTasksToQuery = Math.max(minNumTasksToQuery, numLatestTasks); 156 List<ActivityManager.RecentTaskInfo> tasks = mAm.getRecentTasksForUser(numTasksToQuery, 157 ActivityManager.RECENT_IGNORE_UNAVAILABLE | 158 ActivityManager.RECENT_INCLUDE_PROFILES | 159 ActivityManager.RECENT_WITH_EXCLUDED, userId); 160 boolean isFirstValidTask = true; 161 Iterator<ActivityManager.RecentTaskInfo> iter = tasks.iterator(); 162 while (iter.hasNext()) { 163 ActivityManager.RecentTaskInfo t = iter.next(); 164 165 // NOTE: The order of these checks happens in the expected order of the traversal of the 166 // tasks 167 168 // Skip tasks from this Recents package 169 if (t.baseIntent.getComponent().getPackageName().equals(mRecentsPackage)) { 170 iter.remove(); 171 continue; 172 } 173 // Check the first non-recents task, include this task even if it is marked as excluded 174 // from recents. In other words, only remove excluded tasks if it is not the first task 175 boolean isExcluded = (t.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) 176 == Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; 177 if (isExcluded && !isFirstValidTask) { 178 iter.remove(); 179 continue; 180 } 181 isFirstValidTask = false; 182 // Skip tasks in the home stack 183 if (isInHomeStack(t.persistentId)) { 184 iter.remove(); 185 continue; 186 } 187 } 188 189 return tasks.subList(0, Math.min(tasks.size(), numLatestTasks)); 190 } 191 192 /** Returns a list of the running tasks */ 193 public List<ActivityManager.RunningTaskInfo> getRunningTasks(int numTasks) { 194 if (mAm == null) return null; 195 return mAm.getRunningTasks(numTasks); 196 } 197 198 /** Returns whether the specified task is in the home stack */ 199 public boolean isInHomeStack(int taskId) { 200 if (mAm == null) return false; 201 202 // If we are mocking, then just return false 203 if (Constants.DebugFlags.App.EnableSystemServicesProxy) { 204 return false; 205 } 206 207 return mAm.isInHomeStack(taskId); 208 } 209 210 /** Returns the top task thumbnail for the given task id */ 211 public Bitmap getTaskThumbnail(int taskId) { 212 if (mAm == null) return null; 213 214 // If we are mocking, then just return a dummy thumbnail 215 if (Constants.DebugFlags.App.EnableSystemServicesProxy) { 216 Bitmap thumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); 217 thumbnail.eraseColor(0xff333333); 218 return thumbnail; 219 } 220 221 Bitmap thumbnail = SystemServicesProxy.getThumbnail(mAm, taskId); 222 if (thumbnail != null) { 223 // We use a dumb heuristic for now, if the thumbnail is purely transparent in the top 224 // left pixel, then assume the whole thumbnail is transparent. Generally, proper 225 // screenshots are always composed onto a bitmap that has no alpha. 226 if (Color.alpha(thumbnail.getPixel(0, 0)) == 0) { 227 mBgProtectionCanvas.setBitmap(thumbnail); 228 mBgProtectionCanvas.drawRect(0, 0, thumbnail.getWidth(), thumbnail.getHeight(), 229 mBgProtectionPaint); 230 mBgProtectionCanvas.setBitmap(null); 231 Log.e(TAG, "Invalid screenshot detected from getTaskThumbnail()"); 232 } 233 } 234 return thumbnail; 235 } 236 237 /** 238 * Returns a task thumbnail from the activity manager 239 */ 240 public static Bitmap getThumbnail(ActivityManager activityManager, int taskId) { 241 ActivityManager.TaskThumbnail taskThumbnail = activityManager.getTaskThumbnail(taskId); 242 Bitmap thumbnail = taskThumbnail.mainThumbnail; 243 ParcelFileDescriptor descriptor = taskThumbnail.thumbnailFileDescriptor; 244 if (thumbnail == null && descriptor != null) { 245 thumbnail = BitmapFactory.decodeFileDescriptor(descriptor.getFileDescriptor()); 246 } 247 if (descriptor != null) { 248 try { 249 descriptor.close(); 250 } catch (IOException e) { 251 } 252 } 253 return thumbnail; 254 } 255 256 /** Moves a task to the front with the specified activity options */ 257 public void moveTaskToFront(int taskId, ActivityOptions opts) { 258 if (mAm == null) return; 259 if (Constants.DebugFlags.App.EnableSystemServicesProxy) return; 260 261 if (opts != null) { 262 mAm.moveTaskToFront(taskId, ActivityManager.MOVE_TASK_WITH_HOME, 263 opts.toBundle()); 264 } else { 265 mAm.moveTaskToFront(taskId, ActivityManager.MOVE_TASK_WITH_HOME); 266 } 267 } 268 269 /** Removes the task and kills the process */ 270 public void removeTask(int taskId, boolean isDocument) { 271 if (mAm == null) return; 272 if (Constants.DebugFlags.App.EnableSystemServicesProxy) return; 273 274 // Remove the task, and only kill the process if it is not a document 275 mAm.removeTask(taskId, isDocument ? 0 : ActivityManager.REMOVE_TASK_KILL_PROCESS); 276 } 277 278 /** 279 * Returns the activity info for a given component name. 280 * 281 * @param cn The component name of the activity. 282 * @param userId The userId of the user that this is for. 283 */ 284 public ActivityInfo getActivityInfo(ComponentName cn, int userId) { 285 if (mIpm == null) return null; 286 if (Constants.DebugFlags.App.EnableSystemServicesProxy) return new ActivityInfo(); 287 288 try { 289 return mIpm.getActivityInfo(cn, PackageManager.GET_META_DATA, userId); 290 } catch (RemoteException e) { 291 e.printStackTrace(); 292 return null; 293 } 294 } 295 296 /** 297 * Returns the activity info for a given component name. 298 * 299 * @param cn The component name of the activity. 300 */ 301 public ActivityInfo getActivityInfo(ComponentName cn) { 302 if (mPm == null) return null; 303 if (Constants.DebugFlags.App.EnableSystemServicesProxy) return new ActivityInfo(); 304 305 try { 306 return mPm.getActivityInfo(cn, PackageManager.GET_META_DATA); 307 } catch (PackageManager.NameNotFoundException e) { 308 e.printStackTrace(); 309 return null; 310 } 311 } 312 313 /** Returns the activity label */ 314 public String getActivityLabel(ActivityInfo info) { 315 if (mPm == null) return null; 316 317 // If we are mocking, then return a mock label 318 if (Constants.DebugFlags.App.EnableSystemServicesProxy) { 319 return "Recent Task"; 320 } 321 322 return info.loadLabel(mPm).toString(); 323 } 324 325 /** 326 * Returns the activity icon for the ActivityInfo for a user, badging if 327 * necessary. 328 */ 329 public Drawable getActivityIcon(ActivityInfo info, int userId) { 330 if (mPm == null || mUm == null) return null; 331 332 // If we are mocking, then return a mock label 333 if (Constants.DebugFlags.App.EnableSystemServicesProxy) { 334 return new ColorDrawable(0xFF666666); 335 } 336 337 Drawable icon = info.loadIcon(mPm); 338 return getBadgedIcon(icon, userId); 339 } 340 341 /** 342 * Returns the given icon for a user, badging if necessary. 343 */ 344 public Drawable getBadgedIcon(Drawable icon, int userId) { 345 if (userId != UserHandle.myUserId()) { 346 icon = mUm.getBadgedDrawableForUser(icon, new UserHandle(userId)); 347 } 348 return icon; 349 } 350 351 /** 352 * Resolves and binds the search app widget that is to appear in the recents. 353 */ 354 public Pair<Integer, AppWidgetProviderInfo> bindSearchAppWidget(AppWidgetHost host) { 355 if (mAwm == null) return null; 356 if (mAssistComponent == null) return null; 357 358 // Find the first Recents widget from the same package as the global assist activity 359 List<AppWidgetProviderInfo> widgets = mAwm.getInstalledProviders( 360 AppWidgetProviderInfo.WIDGET_CATEGORY_RECENTS); 361 AppWidgetProviderInfo searchWidgetInfo = null; 362 for (AppWidgetProviderInfo info : widgets) { 363 if (info.provider.getPackageName().equals(mAssistComponent.getPackageName())) { 364 searchWidgetInfo = info; 365 break; 366 } 367 } 368 369 // Return early if there is no search widget 370 if (searchWidgetInfo == null) return null; 371 372 // Allocate a new widget id and try and bind the app widget (if that fails, then just skip) 373 int searchWidgetId = host.allocateAppWidgetId(); 374 Bundle opts = new Bundle(); 375 opts.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY, 376 AppWidgetProviderInfo.WIDGET_CATEGORY_RECENTS); 377 if (!mAwm.bindAppWidgetIdIfAllowed(searchWidgetId, searchWidgetInfo.provider, opts)) { 378 return null; 379 } 380 return new Pair<Integer, AppWidgetProviderInfo>(searchWidgetId, searchWidgetInfo); 381 } 382 383 /** 384 * Returns the app widget info for the specified app widget id. 385 */ 386 public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) { 387 if (mAwm == null) return null; 388 389 return mAwm.getAppWidgetInfo(appWidgetId); 390 } 391 392 /** 393 * Destroys the specified app widget. 394 */ 395 public void unbindSearchAppWidget(AppWidgetHost host, int appWidgetId) { 396 if (mAwm == null) return; 397 398 // Delete the app widget 399 host.deleteAppWidgetId(appWidgetId); 400 } 401 402 /** 403 * Returns the window rect. 404 */ 405 public Rect getWindowRect() { 406 Rect windowRect = new Rect(); 407 if (mWm == null) return windowRect; 408 409 mWm.getDefaultDisplay().getRectSize(windowRect); 410 return windowRect; 411 } 412 413 /** 414 * Locks the current task. 415 */ 416 public void lockCurrentTask() { 417 if (mIam == null) return; 418 419 try { 420 mIam.startLockTaskModeOnCurrent(); 421 } catch (RemoteException e) {} 422 } 423 424 /** 425 * Takes a screenshot of the current surface. 426 */ 427 public Bitmap takeScreenshot() { 428 DisplayInfo di = new DisplayInfo(); 429 mDisplay.getDisplayInfo(di); 430 return SurfaceControl.screenshot(di.getNaturalWidth(), di.getNaturalHeight()); 431 } 432 433 /** 434 * Takes a screenshot of the current app. 435 */ 436 public Bitmap takeAppScreenshot() { 437 return takeScreenshot(); 438 } 439} 440