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