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