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