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 
17 package com.android.systemui.recents.misc;
18 
19 import android.app.ActivityManager;
20 import android.app.ActivityManagerNative;
21 import android.app.ActivityOptions;
22 import android.app.AppGlobals;
23 import android.app.IActivityManager;
24 import android.app.ITaskStackListener;
25 import android.app.SearchManager;
26 import android.appwidget.AppWidgetHost;
27 import android.appwidget.AppWidgetManager;
28 import android.appwidget.AppWidgetProviderInfo;
29 import android.content.ComponentName;
30 import android.content.ContentResolver;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.pm.ActivityInfo;
34 import android.content.pm.IPackageManager;
35 import android.content.pm.PackageManager;
36 import android.content.pm.ResolveInfo;
37 import android.content.res.Resources;
38 import android.graphics.Bitmap;
39 import android.graphics.BitmapFactory;
40 import android.graphics.Canvas;
41 import android.graphics.Color;
42 import android.graphics.Paint;
43 import android.graphics.Point;
44 import android.graphics.PorterDuff;
45 import android.graphics.PorterDuffXfermode;
46 import android.graphics.Rect;
47 import android.graphics.drawable.ColorDrawable;
48 import android.graphics.drawable.Drawable;
49 import android.os.Bundle;
50 import android.os.ParcelFileDescriptor;
51 import android.os.RemoteException;
52 import android.os.UserHandle;
53 import android.provider.Settings;
54 import android.util.Log;
55 import android.util.Pair;
56 import android.view.Display;
57 import android.view.DisplayInfo;
58 import android.view.SurfaceControl;
59 import android.view.WindowManager;
60 import android.view.accessibility.AccessibilityManager;
61 import com.android.systemui.R;
62 import com.android.systemui.recents.AlternateRecentsComponent;
63 import com.android.systemui.recents.Constants;
64 
65 import java.io.IOException;
66 import java.util.ArrayList;
67 import java.util.Iterator;
68 import java.util.List;
69 import java.util.Random;
70 import 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  */
76 public 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 */
SystemServicesProxy(Context context)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 */
getRecentTasks(int numLatestTasks, int userId, boolean isTopTaskHome)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 */
getRunningTasks(int numTasks)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. */
getTopMostTask()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 */
isRecentsTopMost(ActivityManager.RunningTaskInfo topTask, AtomicBoolean isHomeTopMost)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 */
isInHomeStack(int taskId)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 */
getTaskThumbnail(int taskId)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      */
getThumbnail(ActivityManager activityManager, int taskId)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 */
moveTaskToFront(int taskId, ActivityOptions opts)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 */
removeTask(int taskId)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      */
getActivityInfo(ComponentName cn, int userId)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      */
getActivityInfo(ComponentName cn)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 */
getActivityLabel(ActivityInfo info)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      */
getActivityIcon(ActivityInfo info, int userId)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      */
getBadgedIcon(Drawable icon, int userId)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. */
getHomeActivityPackageName()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      */
isForegroundUserOwner()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      */
resolveSearchAppWidget()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      */
bindSearchAppWidget(AppWidgetHost host)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      */
getAppWidgetInfo(int appWidgetId)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      */
unbindSearchAppWidget(AppWidgetHost host, int appWidgetId)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      */
isTouchExplorationEnabled()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      */
getGlobalSetting(Context context, String setting)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      */
getSystemSetting(Context context, String setting)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      */
getWindowRect()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      */
takeScreenshot()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      */
takeAppScreenshot()551     public Bitmap takeAppScreenshot() {
552         return takeScreenshot();
553     }
554 
555     /** Starts an activity from recents. */
startActivityFromRecents(Context context, int taskId, String taskName, ActivityOptions options)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. */
startInPlaceAnimationOnFrontMostApplication(ActivityOptions opts)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. */
registerTaskStackListener(ITaskStackListener listener)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