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