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