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