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