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