SystemServicesProxy.java revision 85cfec811e35025dbde54f4dc09fe0e1337c36b8
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.Context;
30import android.content.Intent;
31import android.content.pm.ActivityInfo;
32import android.content.pm.IPackageManager;
33import android.content.pm.PackageManager;
34import android.content.res.Resources;
35import android.graphics.Bitmap;
36import android.graphics.BitmapFactory;
37import android.graphics.Canvas;
38import android.graphics.Color;
39import android.graphics.Paint;
40import android.graphics.PorterDuff;
41import android.graphics.PorterDuffXfermode;
42import android.graphics.Rect;
43import android.graphics.drawable.ColorDrawable;
44import android.graphics.drawable.Drawable;
45import android.os.Bundle;
46import android.os.ParcelFileDescriptor;
47import android.os.RemoteException;
48import android.os.UserHandle;
49import android.os.UserManager;
50import android.util.Log;
51import android.util.Pair;
52import android.view.Display;
53import android.view.DisplayInfo;
54import android.view.SurfaceControl;
55import android.view.WindowManager;
56import com.android.systemui.recents.Constants;
57
58import java.io.IOException;
59import java.util.ArrayList;
60import java.util.Iterator;
61import java.util.List;
62import java.util.Random;
63
64/**
65 * Acts as a shim around the real system services that we need to access data from, and provides
66 * a point of injection when testing UI.
67 */
68public class SystemServicesProxy {
69    final static String TAG = "SystemServicesProxy";
70
71    ActivityManager mAm;
72    IActivityManager mIam;
73    AppWidgetManager mAwm;
74    PackageManager mPm;
75    IPackageManager mIpm;
76    UserManager mUm;
77    SearchManager mSm;
78    WindowManager mWm;
79    Display mDisplay;
80    String mRecentsPackage;
81    ComponentName mAssistComponent;
82
83    Bitmap mDummyIcon;
84    int mDummyThumbnailWidth;
85    int mDummyThumbnailHeight;
86    Paint mBgProtectionPaint;
87    Canvas mBgProtectionCanvas;
88
89    /** Private constructor */
90    public SystemServicesProxy(Context context) {
91        mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
92        mIam = ActivityManagerNative.getDefault();
93        mAwm = AppWidgetManager.getInstance(context);
94        mPm = context.getPackageManager();
95        mUm = (UserManager) context.getSystemService(Context.USER_SERVICE);
96        mIpm = AppGlobals.getPackageManager();
97        mSm = (SearchManager) context.getSystemService(Context.SEARCH_SERVICE);
98        mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
99        mDisplay = mWm.getDefaultDisplay();
100        mRecentsPackage = context.getPackageName();
101
102        // Get the dummy thumbnail width/heights
103        Resources res = context.getResources();
104        int wId = com.android.internal.R.dimen.thumbnail_width;
105        int hId = com.android.internal.R.dimen.thumbnail_height;
106        mDummyThumbnailWidth = res.getDimensionPixelSize(wId);
107        mDummyThumbnailHeight = res.getDimensionPixelSize(hId);
108
109        // Create the protection paints
110        mBgProtectionPaint = new Paint();
111        mBgProtectionPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));
112        mBgProtectionPaint.setColor(0xFFffffff);
113        mBgProtectionCanvas = new Canvas();
114
115        // Resolve the assist intent
116        Intent assist = mSm.getAssistIntent(context, false);
117        if (assist != null) {
118            mAssistComponent = assist.getComponent();
119        }
120
121        if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
122            // Create a dummy icon
123            mDummyIcon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
124            mDummyIcon.eraseColor(0xFF999999);
125        }
126    }
127
128    /** Returns a list of the recents tasks */
129    public List<ActivityManager.RecentTaskInfo> getRecentTasks(int numLatestTasks, int userId) {
130        if (mAm == null) return null;
131
132        // If we are mocking, then create some recent tasks
133        if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
134            ArrayList<ActivityManager.RecentTaskInfo> tasks =
135                    new ArrayList<ActivityManager.RecentTaskInfo>();
136            int count = Math.min(numLatestTasks, Constants.DebugFlags.App.SystemServicesProxyMockTaskCount);
137            for (int i = 0; i < count; i++) {
138                // Create a dummy component name
139                int packageIndex = i % Constants.DebugFlags.App.SystemServicesProxyMockPackageCount;
140                ComponentName cn = new ComponentName("com.android.test" + packageIndex,
141                        "com.android.test" + i + ".Activity");
142                String description = "" + i + " - " +
143                        Long.toString(Math.abs(new Random().nextLong()), 36);
144                // Create the recent task info
145                ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
146                rti.id = rti.persistentId = i;
147                rti.baseIntent = new Intent();
148                rti.baseIntent.setComponent(cn);
149                rti.description = description;
150                rti.firstActiveTime = rti.lastActiveTime = i;
151                if (i % 2 == 0) {
152                    rti.taskDescription = new ActivityManager.TaskDescription(description,
153                        Bitmap.createBitmap(mDummyIcon),
154                        0xFF000000 | (0xFFFFFF & new Random().nextInt()));
155                } else {
156                    rti.taskDescription = new ActivityManager.TaskDescription();
157                }
158                tasks.add(rti);
159            }
160            return tasks;
161        }
162
163        // Remove home/recents/excluded tasks
164        int minNumTasksToQuery = 10;
165        int numTasksToQuery = Math.max(minNumTasksToQuery, numLatestTasks);
166        List<ActivityManager.RecentTaskInfo> tasks = mAm.getRecentTasksForUser(numTasksToQuery,
167                ActivityManager.RECENT_IGNORE_UNAVAILABLE |
168                ActivityManager.RECENT_INCLUDE_PROFILES |
169                ActivityManager.RECENT_WITH_EXCLUDED, userId);
170        boolean isFirstValidTask = true;
171        Iterator<ActivityManager.RecentTaskInfo> iter = tasks.iterator();
172        while (iter.hasNext()) {
173            ActivityManager.RecentTaskInfo t = iter.next();
174
175            // NOTE: The order of these checks happens in the expected order of the traversal of the
176            // tasks
177
178            // Skip tasks from this Recents package
179            if (t.baseIntent.getComponent().getPackageName().equals(mRecentsPackage)) {
180                iter.remove();
181                continue;
182            }
183            // Check the first non-recents task, include this task even if it is marked as excluded
184            // from recents.  In other words, only remove excluded tasks if it is not the first task
185            boolean isExcluded = (t.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
186                    == Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
187            if (isExcluded && !isFirstValidTask) {
188                iter.remove();
189                continue;
190            }
191            isFirstValidTask = false;
192            // Skip tasks in the home stack
193            if (isInHomeStack(t.persistentId)) {
194                iter.remove();
195                continue;
196            }
197        }
198
199        return tasks.subList(0, Math.min(tasks.size(), numLatestTasks));
200    }
201
202    /** Returns a list of the running tasks */
203    public List<ActivityManager.RunningTaskInfo> getRunningTasks(int numTasks) {
204        if (mAm == null) return null;
205        return mAm.getRunningTasks(numTasks);
206    }
207
208    /** Returns whether the specified task is in the home stack */
209    public boolean isInHomeStack(int taskId) {
210        if (mAm == null) return false;
211
212        // If we are mocking, then just return false
213        if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
214            return false;
215        }
216
217        return mAm.isInHomeStack(taskId);
218    }
219
220    /** Returns the top task thumbnail for the given task id */
221    public Bitmap getTaskThumbnail(int taskId) {
222        if (mAm == null) return null;
223
224        // If we are mocking, then just return a dummy thumbnail
225        if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
226            Bitmap thumbnail = Bitmap.createBitmap(mDummyThumbnailWidth, mDummyThumbnailHeight,
227                    Bitmap.Config.ARGB_8888);
228            thumbnail.eraseColor(0xff333333);
229            return thumbnail;
230        }
231
232        Bitmap thumbnail = SystemServicesProxy.getThumbnail(mAm, taskId);
233        if (thumbnail != null) {
234            // We use a dumb heuristic for now, if the thumbnail is purely transparent in the top
235            // left pixel, then assume the whole thumbnail is transparent. Generally, proper
236            // screenshots are always composed onto a bitmap that has no alpha.
237            if (Color.alpha(thumbnail.getPixel(0, 0)) == 0) {
238                mBgProtectionCanvas.setBitmap(thumbnail);
239                mBgProtectionCanvas.drawRect(0, 0, thumbnail.getWidth(), thumbnail.getHeight(),
240                        mBgProtectionPaint);
241                mBgProtectionCanvas.setBitmap(null);
242                Log.e(TAG, "Invalid screenshot detected from getTaskThumbnail()");
243            }
244        }
245        return thumbnail;
246    }
247
248    /**
249     * Returns a task thumbnail from the activity manager
250     */
251    public static Bitmap getThumbnail(ActivityManager activityManager, int taskId) {
252        ActivityManager.TaskThumbnail taskThumbnail = activityManager.getTaskThumbnail(taskId);
253        if (taskThumbnail == null) return null;
254
255        Bitmap thumbnail = taskThumbnail.mainThumbnail;
256        ParcelFileDescriptor descriptor = taskThumbnail.thumbnailFileDescriptor;
257        if (thumbnail == null && descriptor != null) {
258            thumbnail = BitmapFactory.decodeFileDescriptor(descriptor.getFileDescriptor());
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 || mUm == 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 = mUm.getBadgedDrawableForUser(icon, new UserHandle(userId));
360        }
361        return icon;
362    }
363
364    /**
365     * Resolves and binds the search app widget that is to appear in the recents.
366     */
367    public Pair<Integer, AppWidgetProviderInfo> bindSearchAppWidget(AppWidgetHost host) {
368        if (mAwm == null) return null;
369        if (mAssistComponent == null) return null;
370
371        // Find the first Recents widget from the same package as the global assist activity
372        List<AppWidgetProviderInfo> widgets = mAwm.getInstalledProviders(
373                AppWidgetProviderInfo.WIDGET_CATEGORY_RECENTS);
374        AppWidgetProviderInfo searchWidgetInfo = null;
375        for (AppWidgetProviderInfo info : widgets) {
376            if (info.provider.getPackageName().equals(mAssistComponent.getPackageName())) {
377                searchWidgetInfo = info;
378                break;
379            }
380        }
381
382        // Return early if there is no search widget
383        if (searchWidgetInfo == null) return null;
384
385        // Allocate a new widget id and try and bind the app widget (if that fails, then just skip)
386        int searchWidgetId = host.allocateAppWidgetId();
387        Bundle opts = new Bundle();
388        opts.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
389                AppWidgetProviderInfo.WIDGET_CATEGORY_RECENTS);
390        if (!mAwm.bindAppWidgetIdIfAllowed(searchWidgetId, searchWidgetInfo.provider, opts)) {
391            return null;
392        }
393        return new Pair<Integer, AppWidgetProviderInfo>(searchWidgetId, searchWidgetInfo);
394    }
395
396    /**
397     * Returns the app widget info for the specified app widget id.
398     */
399    public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) {
400        if (mAwm == null) return null;
401
402        return mAwm.getAppWidgetInfo(appWidgetId);
403    }
404
405    /**
406     * Destroys the specified app widget.
407     */
408    public void unbindSearchAppWidget(AppWidgetHost host, int appWidgetId) {
409        if (mAwm == null) return;
410
411        // Delete the app widget
412        host.deleteAppWidgetId(appWidgetId);
413    }
414
415    /**
416     * Returns the window rect.
417     */
418    public Rect getWindowRect() {
419        Rect windowRect = new Rect();
420        if (mWm == null) return windowRect;
421
422        mWm.getDefaultDisplay().getRectSize(windowRect);
423        return windowRect;
424    }
425
426    /**
427     * Locks the current task.
428     */
429    public void lockCurrentTask() {
430        if (mIam == null) return;
431
432        try {
433            mIam.startLockTaskModeOnCurrent();
434        } catch (RemoteException e) {}
435    }
436
437    /**
438     * Takes a screenshot of the current surface.
439     */
440    public Bitmap takeScreenshot() {
441        DisplayInfo di = new DisplayInfo();
442        mDisplay.getDisplayInfo(di);
443        return SurfaceControl.screenshot(di.getNaturalWidth(), di.getNaturalHeight());
444    }
445
446    /**
447     * Takes a screenshot of the current app.
448     */
449    public Bitmap takeAppScreenshot() {
450        return takeScreenshot();
451    }
452}
453