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.ITaskStackListener; 25 import android.app.SearchManager; 26 import android.appwidget.AppWidgetHost; 27 import android.appwidget.AppWidgetManager; 28 import android.appwidget.AppWidgetProviderInfo; 29 import android.content.ComponentName; 30 import android.content.ContentResolver; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.content.pm.ActivityInfo; 34 import android.content.pm.IPackageManager; 35 import android.content.pm.PackageManager; 36 import android.content.pm.ResolveInfo; 37 import android.content.res.Resources; 38 import android.graphics.Bitmap; 39 import android.graphics.BitmapFactory; 40 import android.graphics.Canvas; 41 import android.graphics.Color; 42 import android.graphics.Paint; 43 import android.graphics.Point; 44 import android.graphics.PorterDuff; 45 import android.graphics.PorterDuffXfermode; 46 import android.graphics.Rect; 47 import android.graphics.drawable.ColorDrawable; 48 import android.graphics.drawable.Drawable; 49 import android.os.Bundle; 50 import android.os.ParcelFileDescriptor; 51 import android.os.RemoteException; 52 import android.os.UserHandle; 53 import android.provider.Settings; 54 import android.util.Log; 55 import android.util.Pair; 56 import android.view.Display; 57 import android.view.DisplayInfo; 58 import android.view.SurfaceControl; 59 import android.view.WindowManager; 60 import android.view.accessibility.AccessibilityManager; 61 import com.android.systemui.R; 62 import com.android.systemui.recents.AlternateRecentsComponent; 63 import com.android.systemui.recents.Constants; 64 65 import java.io.IOException; 66 import java.util.ArrayList; 67 import java.util.Iterator; 68 import java.util.List; 69 import java.util.Random; 70 import java.util.concurrent.atomic.AtomicBoolean; 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 */ 76 public 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 */ SystemServicesProxy(Context context)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 */ getRecentTasks(int numLatestTasks, int userId, boolean isTopTaskHome)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 */ getRunningTasks(int numTasks)217 public List<ActivityManager.RunningTaskInfo> getRunningTasks(int numTasks) { 218 if (mAm == null) return null; 219 return mAm.getRunningTasks(numTasks); 220 } 221 222 /** Returns the top task. */ getTopMostTask()223 public ActivityManager.RunningTaskInfo getTopMostTask() { 224 List<ActivityManager.RunningTaskInfo> tasks = getRunningTasks(1); 225 if (!tasks.isEmpty()) { 226 return tasks.get(0); 227 } 228 return null; 229 } 230 231 /** Returns whether the recents is currently running */ isRecentsTopMost(ActivityManager.RunningTaskInfo topTask, AtomicBoolean isHomeTopMost)232 public boolean isRecentsTopMost(ActivityManager.RunningTaskInfo topTask, 233 AtomicBoolean isHomeTopMost) { 234 if (topTask != null) { 235 ComponentName topActivity = topTask.topActivity; 236 237 // Check if the front most activity is recents 238 if (topActivity.getPackageName().equals(AlternateRecentsComponent.sRecentsPackage) && 239 topActivity.getClassName().equals(AlternateRecentsComponent.sRecentsActivity)) { 240 if (isHomeTopMost != null) { 241 isHomeTopMost.set(false); 242 } 243 return true; 244 } 245 246 if (isHomeTopMost != null) { 247 isHomeTopMost.set(isInHomeStack(topTask.id)); 248 } 249 } 250 return false; 251 } 252 253 /** Returns whether the specified task is in the home stack */ isInHomeStack(int taskId)254 public boolean isInHomeStack(int taskId) { 255 if (mAm == null) return false; 256 257 // If we are mocking, then just return false 258 if (Constants.DebugFlags.App.EnableSystemServicesProxy) { 259 return false; 260 } 261 262 return mAm.isInHomeStack(taskId); 263 } 264 265 /** Returns the top task thumbnail for the given task id */ getTaskThumbnail(int taskId)266 public Bitmap getTaskThumbnail(int taskId) { 267 if (mAm == null) return null; 268 269 // If we are mocking, then just return a dummy thumbnail 270 if (Constants.DebugFlags.App.EnableSystemServicesProxy) { 271 Bitmap thumbnail = Bitmap.createBitmap(mDummyThumbnailWidth, mDummyThumbnailHeight, 272 Bitmap.Config.ARGB_8888); 273 thumbnail.eraseColor(0xff333333); 274 return thumbnail; 275 } 276 277 Bitmap thumbnail = SystemServicesProxy.getThumbnail(mAm, taskId); 278 if (thumbnail != null) { 279 thumbnail.setHasAlpha(false); 280 // We use a dumb heuristic for now, if the thumbnail is purely transparent in the top 281 // left pixel, then assume the whole thumbnail is transparent. Generally, proper 282 // screenshots are always composed onto a bitmap that has no alpha. 283 if (Color.alpha(thumbnail.getPixel(0, 0)) == 0) { 284 mBgProtectionCanvas.setBitmap(thumbnail); 285 mBgProtectionCanvas.drawRect(0, 0, thumbnail.getWidth(), thumbnail.getHeight(), 286 mBgProtectionPaint); 287 mBgProtectionCanvas.setBitmap(null); 288 Log.e(TAG, "Invalid screenshot detected from getTaskThumbnail()"); 289 } 290 } 291 return thumbnail; 292 } 293 294 /** 295 * Returns a task thumbnail from the activity manager 296 */ getThumbnail(ActivityManager activityManager, int taskId)297 public static Bitmap getThumbnail(ActivityManager activityManager, int taskId) { 298 ActivityManager.TaskThumbnail taskThumbnail = activityManager.getTaskThumbnail(taskId); 299 if (taskThumbnail == null) return null; 300 301 Bitmap thumbnail = taskThumbnail.mainThumbnail; 302 ParcelFileDescriptor descriptor = taskThumbnail.thumbnailFileDescriptor; 303 if (thumbnail == null && descriptor != null) { 304 thumbnail = BitmapFactory.decodeFileDescriptor(descriptor.getFileDescriptor(), 305 null, sBitmapOptions); 306 } 307 if (descriptor != null) { 308 try { 309 descriptor.close(); 310 } catch (IOException e) { 311 } 312 } 313 return thumbnail; 314 } 315 316 /** Moves a task to the front with the specified activity options */ moveTaskToFront(int taskId, ActivityOptions opts)317 public void moveTaskToFront(int taskId, ActivityOptions opts) { 318 if (mAm == null) return; 319 if (Constants.DebugFlags.App.EnableSystemServicesProxy) return; 320 321 if (opts != null) { 322 mAm.moveTaskToFront(taskId, ActivityManager.MOVE_TASK_WITH_HOME, 323 opts.toBundle()); 324 } else { 325 mAm.moveTaskToFront(taskId, ActivityManager.MOVE_TASK_WITH_HOME); 326 } 327 } 328 329 /** Removes the task */ removeTask(int taskId)330 public void removeTask(int taskId) { 331 if (mAm == null) return; 332 if (Constants.DebugFlags.App.EnableSystemServicesProxy) return; 333 334 // Remove the task. 335 mAm.removeTask(taskId); 336 } 337 338 /** 339 * Returns the activity info for a given component name. 340 * 341 * @param cn The component name of the activity. 342 * @param userId The userId of the user that this is for. 343 */ getActivityInfo(ComponentName cn, int userId)344 public ActivityInfo getActivityInfo(ComponentName cn, int userId) { 345 if (mIpm == null) return null; 346 if (Constants.DebugFlags.App.EnableSystemServicesProxy) return new ActivityInfo(); 347 348 try { 349 return mIpm.getActivityInfo(cn, PackageManager.GET_META_DATA, userId); 350 } catch (RemoteException e) { 351 e.printStackTrace(); 352 return null; 353 } 354 } 355 356 /** 357 * Returns the activity info for a given component name. 358 * 359 * @param cn The component name of the activity. 360 */ getActivityInfo(ComponentName cn)361 public ActivityInfo getActivityInfo(ComponentName cn) { 362 if (mPm == null) return null; 363 if (Constants.DebugFlags.App.EnableSystemServicesProxy) return new ActivityInfo(); 364 365 try { 366 return mPm.getActivityInfo(cn, PackageManager.GET_META_DATA); 367 } catch (PackageManager.NameNotFoundException e) { 368 e.printStackTrace(); 369 return null; 370 } 371 } 372 373 /** Returns the activity label */ getActivityLabel(ActivityInfo info)374 public String getActivityLabel(ActivityInfo info) { 375 if (mPm == null) return null; 376 377 // If we are mocking, then return a mock label 378 if (Constants.DebugFlags.App.EnableSystemServicesProxy) { 379 return "Recent Task"; 380 } 381 382 return info.loadLabel(mPm).toString(); 383 } 384 385 /** 386 * Returns the activity icon for the ActivityInfo for a user, badging if 387 * necessary. 388 */ getActivityIcon(ActivityInfo info, int userId)389 public Drawable getActivityIcon(ActivityInfo info, int userId) { 390 if (mPm == null) return null; 391 392 // If we are mocking, then return a mock label 393 if (Constants.DebugFlags.App.EnableSystemServicesProxy) { 394 return new ColorDrawable(0xFF666666); 395 } 396 397 Drawable icon = info.loadIcon(mPm); 398 return getBadgedIcon(icon, userId); 399 } 400 401 /** 402 * Returns the given icon for a user, badging if necessary. 403 */ getBadgedIcon(Drawable icon, int userId)404 public Drawable getBadgedIcon(Drawable icon, int userId) { 405 if (userId != UserHandle.myUserId()) { 406 icon = mPm.getUserBadgedIcon(icon, new UserHandle(userId)); 407 } 408 return icon; 409 } 410 411 /** Returns the package name of the home activity. */ getHomeActivityPackageName()412 public String getHomeActivityPackageName() { 413 if (mPm == null) return null; 414 if (Constants.DebugFlags.App.EnableSystemServicesProxy) return null; 415 416 ArrayList<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>(); 417 ComponentName defaultHomeActivity = mPm.getHomeActivities(homeActivities); 418 if (defaultHomeActivity != null) { 419 return defaultHomeActivity.getPackageName(); 420 } else if (homeActivities.size() == 1) { 421 ResolveInfo info = homeActivities.get(0); 422 if (info.activityInfo != null) { 423 return info.activityInfo.packageName; 424 } 425 } 426 return null; 427 } 428 429 /** 430 * Returns whether the foreground user is the owner. 431 */ isForegroundUserOwner()432 public boolean isForegroundUserOwner() { 433 if (mAm == null) return false; 434 435 return mAm.getCurrentUser() == UserHandle.USER_OWNER; 436 } 437 438 /** 439 * Resolves and returns the first Recents widget from the same package as the global 440 * assist activity. 441 */ resolveSearchAppWidget()442 public AppWidgetProviderInfo resolveSearchAppWidget() { 443 if (mAwm == null) return null; 444 if (mAssistComponent == null) return null; 445 446 // Find the first Recents widget from the same package as the global assist activity 447 List<AppWidgetProviderInfo> widgets = mAwm.getInstalledProviders( 448 AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX); 449 for (AppWidgetProviderInfo info : widgets) { 450 if (info.provider.getPackageName().equals(mAssistComponent.getPackageName())) { 451 return info; 452 } 453 } 454 return null; 455 } 456 457 /** 458 * Resolves and binds the search app widget that is to appear in the recents. 459 */ bindSearchAppWidget(AppWidgetHost host)460 public Pair<Integer, AppWidgetProviderInfo> bindSearchAppWidget(AppWidgetHost host) { 461 if (mAwm == null) return null; 462 if (mAssistComponent == null) return null; 463 464 // Find the first Recents widget from the same package as the global assist activity 465 AppWidgetProviderInfo searchWidgetInfo = resolveSearchAppWidget(); 466 467 // Return early if there is no search widget 468 if (searchWidgetInfo == null) return null; 469 470 // Allocate a new widget id and try and bind the app widget (if that fails, then just skip) 471 int searchWidgetId = host.allocateAppWidgetId(); 472 Bundle opts = new Bundle(); 473 opts.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY, 474 AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX); 475 if (!mAwm.bindAppWidgetIdIfAllowed(searchWidgetId, searchWidgetInfo.provider, opts)) { 476 host.deleteAppWidgetId(searchWidgetId); 477 return null; 478 } 479 return new Pair<Integer, AppWidgetProviderInfo>(searchWidgetId, searchWidgetInfo); 480 } 481 482 /** 483 * Returns the app widget info for the specified app widget id. 484 */ getAppWidgetInfo(int appWidgetId)485 public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) { 486 if (mAwm == null) return null; 487 488 return mAwm.getAppWidgetInfo(appWidgetId); 489 } 490 491 /** 492 * Destroys the specified app widget. 493 */ unbindSearchAppWidget(AppWidgetHost host, int appWidgetId)494 public void unbindSearchAppWidget(AppWidgetHost host, int appWidgetId) { 495 if (mAwm == null) return; 496 497 // Delete the app widget 498 host.deleteAppWidgetId(appWidgetId); 499 } 500 501 /** 502 * Returns whether touch exploration is currently enabled. 503 */ isTouchExplorationEnabled()504 public boolean isTouchExplorationEnabled() { 505 if (mAccm == null) return false; 506 507 return mAccm.isEnabled() && mAccm.isTouchExplorationEnabled(); 508 } 509 510 /** 511 * Returns a global setting. 512 */ getGlobalSetting(Context context, String setting)513 public int getGlobalSetting(Context context, String setting) { 514 ContentResolver cr = context.getContentResolver(); 515 return Settings.Global.getInt(cr, setting, 0); 516 } 517 518 /** 519 * Returns a system setting. 520 */ getSystemSetting(Context context, String setting)521 public int getSystemSetting(Context context, String setting) { 522 ContentResolver cr = context.getContentResolver(); 523 return Settings.System.getInt(cr, setting, 0); 524 } 525 526 /** 527 * Returns the window rect. 528 */ getWindowRect()529 public Rect getWindowRect() { 530 Rect windowRect = new Rect(); 531 if (mWm == null) return windowRect; 532 533 Point p = new Point(); 534 mWm.getDefaultDisplay().getRealSize(p); 535 windowRect.set(0, 0, p.x, p.y); 536 return windowRect; 537 } 538 539 /** 540 * Takes a screenshot of the current surface. 541 */ takeScreenshot()542 public Bitmap takeScreenshot() { 543 DisplayInfo di = new DisplayInfo(); 544 mDisplay.getDisplayInfo(di); 545 return SurfaceControl.screenshot(di.getNaturalWidth(), di.getNaturalHeight()); 546 } 547 548 /** 549 * Takes a screenshot of the current app. 550 */ takeAppScreenshot()551 public Bitmap takeAppScreenshot() { 552 return takeScreenshot(); 553 } 554 555 /** Starts an activity from recents. */ startActivityFromRecents(Context context, int taskId, String taskName, ActivityOptions options)556 public boolean startActivityFromRecents(Context context, int taskId, String taskName, 557 ActivityOptions options) { 558 if (mIam != null) { 559 try { 560 mIam.startActivityFromRecents(taskId, options == null ? null : options.toBundle()); 561 return true; 562 } catch (Exception e) { 563 Console.logError(context, 564 context.getString(R.string.recents_launch_error_message, taskName)); 565 } 566 } 567 return false; 568 } 569 570 /** Starts an in-place animation on the front most application windows. */ startInPlaceAnimationOnFrontMostApplication(ActivityOptions opts)571 public void startInPlaceAnimationOnFrontMostApplication(ActivityOptions opts) { 572 if (mIam == null) return; 573 574 try { 575 mIam.startInPlaceAnimationOnFrontMostApplication(opts); 576 } catch (Exception e) { 577 e.printStackTrace(); 578 } 579 } 580 581 /** Registers a task stack listener with the system. */ registerTaskStackListener(ITaskStackListener listener)582 public void registerTaskStackListener(ITaskStackListener listener) { 583 if (mIam == null) return; 584 585 try { 586 mIam.registerTaskStackListener(listener); 587 } catch (Exception e) { 588 e.printStackTrace(); 589 } 590 } 591 } 592