Home | History | Annotate | Download | only in misc
      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