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