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