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