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