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