SystemServicesProxy.java revision 559b8c5f4ef20678d40a2d4161dfa14b378d2d91
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.Pair;
59import android.util.SparseArray;
60import android.view.Display;
61import android.view.WindowManager;
62import android.view.accessibility.AccessibilityManager;
63
64import com.android.internal.app.AssistUtils;
65import com.android.systemui.Prefs;
66import com.android.systemui.R;
67import com.android.systemui.recents.Constants;
68import com.android.systemui.recents.Recents;
69import com.android.systemui.recents.RecentsConfiguration;
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    /** Resize a given task. */
275    public void resizeTask(int taskId, Rect bounds) {
276        if (mIam == null) return;
277
278        try {
279            if (RecentsConfiguration.getInstance().multiStackEnabled) {
280                // In debug mode, we force all task to be resizeable regardless of the
281                // current app configuration.
282                mIam.setTaskResizeable(taskId, true);
283            }
284            mIam.resizeTask(taskId, bounds);
285        } catch (RemoteException e) {
286            e.printStackTrace();
287        }
288    }
289
290    /** Returns the focused stack id. */
291    public int getFocusedStack() {
292        if (mIam == null) return -1;
293
294        try {
295            return mIam.getFocusedStackId();
296        } catch (RemoteException e) {
297            e.printStackTrace();
298            return -1;
299        }
300    }
301
302    /** Returns whether the specified task is in the home stack */
303    public boolean isInHomeStack(int taskId) {
304        if (mAm == null) return false;
305
306        // If we are mocking, then just return false
307        if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
308            return false;
309        }
310
311        return mAm.isInHomeStack(taskId);
312    }
313
314    /** Returns the top task thumbnail for the given task id */
315    public Bitmap getTaskThumbnail(int taskId) {
316        if (mAm == null) return null;
317
318        // If we are mocking, then just return a dummy thumbnail
319        if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
320            Bitmap thumbnail = Bitmap.createBitmap(mDummyThumbnailWidth, mDummyThumbnailHeight,
321                    Bitmap.Config.ARGB_8888);
322            thumbnail.eraseColor(0xff333333);
323            return thumbnail;
324        }
325
326        Bitmap thumbnail = SystemServicesProxy.getThumbnail(mAm, taskId);
327        if (thumbnail != null) {
328            thumbnail.setHasAlpha(false);
329            // We use a dumb heuristic for now, if the thumbnail is purely transparent in the top
330            // left pixel, then assume the whole thumbnail is transparent. Generally, proper
331            // screenshots are always composed onto a bitmap that has no alpha.
332            if (Color.alpha(thumbnail.getPixel(0, 0)) == 0) {
333                mBgProtectionCanvas.setBitmap(thumbnail);
334                mBgProtectionCanvas.drawRect(0, 0, thumbnail.getWidth(), thumbnail.getHeight(),
335                        mBgProtectionPaint);
336                mBgProtectionCanvas.setBitmap(null);
337                Log.e(TAG, "Invalid screenshot detected from getTaskThumbnail()");
338            }
339        }
340        return thumbnail;
341    }
342
343    /**
344     * Returns a task thumbnail from the activity manager
345     */
346    public static Bitmap getThumbnail(ActivityManager activityManager, int taskId) {
347        ActivityManager.TaskThumbnail taskThumbnail = activityManager.getTaskThumbnail(taskId);
348        if (taskThumbnail == null) return null;
349
350        Bitmap thumbnail = taskThumbnail.mainThumbnail;
351        ParcelFileDescriptor descriptor = taskThumbnail.thumbnailFileDescriptor;
352        if (thumbnail == null && descriptor != null) {
353            thumbnail = BitmapFactory.decodeFileDescriptor(descriptor.getFileDescriptor(),
354                    null, sBitmapOptions);
355        }
356        if (descriptor != null) {
357            try {
358                descriptor.close();
359            } catch (IOException e) {
360            }
361        }
362        return thumbnail;
363    }
364
365    /** Moves a task to the front with the specified activity options. */
366    public void moveTaskToFront(int taskId, ActivityOptions opts) {
367        if (mAm == null) return;
368        if (Constants.DebugFlags.App.EnableSystemServicesProxy) return;
369
370        if (opts != null) {
371            mAm.moveTaskToFront(taskId, ActivityManager.MOVE_TASK_WITH_HOME,
372                    opts.toBundle());
373        } else {
374            mAm.moveTaskToFront(taskId, ActivityManager.MOVE_TASK_WITH_HOME);
375        }
376    }
377
378    /** Removes the task */
379    public void removeTask(final int taskId) {
380        if (mAm == null) return;
381        if (Constants.DebugFlags.App.EnableSystemServicesProxy) return;
382
383        // Remove the task.
384        mBgThreadHandler.post(new Runnable() {
385            @Override
386            public void run() {
387                mAm.removeTask(taskId);
388            }
389        });
390    }
391
392    /**
393     * Returns the activity info for a given component name.
394     *
395     * @param cn The component name of the activity.
396     * @param userId The userId of the user that this is for.
397     */
398    public ActivityInfo getActivityInfo(ComponentName cn, int userId) {
399        if (mIpm == null) return null;
400        if (Constants.DebugFlags.App.EnableSystemServicesProxy) return new ActivityInfo();
401
402        try {
403            return mIpm.getActivityInfo(cn, PackageManager.GET_META_DATA, userId);
404        } catch (RemoteException e) {
405            e.printStackTrace();
406            return null;
407        }
408    }
409
410    /**
411     * Returns the activity info for a given component name.
412     *
413     * @param cn The component name of the activity.
414     */
415    public ActivityInfo getActivityInfo(ComponentName cn) {
416        if (mPm == null) return null;
417        if (Constants.DebugFlags.App.EnableSystemServicesProxy) return new ActivityInfo();
418
419        try {
420            return mPm.getActivityInfo(cn, PackageManager.GET_META_DATA);
421        } catch (PackageManager.NameNotFoundException e) {
422            e.printStackTrace();
423            return null;
424        }
425    }
426
427    /** Returns the activity label */
428    public String getActivityLabel(ActivityInfo info) {
429        if (mPm == null) return null;
430
431        // If we are mocking, then return a mock label
432        if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
433            return "Recent Task";
434        }
435
436        return info.loadLabel(mPm).toString();
437    }
438
439    /** Returns the application label */
440    public String getApplicationLabel(Intent baseIntent, int userId) {
441        if (mPm == null) return null;
442
443        // If we are mocking, then return a mock label
444        if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
445            return "Recent Task";
446        }
447
448        ResolveInfo ri = mPm.resolveActivityAsUser(baseIntent, 0, userId);
449        CharSequence label = (ri != null) ? ri.loadLabel(mPm) : null;
450        return (label != null) ? label.toString() : null;
451    }
452
453    /** Returns the content description for a given task */
454    public String getContentDescription(Intent baseIntent, int userId, String activityLabel,
455            Resources res) {
456        String applicationLabel = getApplicationLabel(baseIntent, userId);
457        if (applicationLabel == null) {
458            return getBadgedLabel(activityLabel, userId);
459        }
460        String badgedApplicationLabel = getBadgedLabel(applicationLabel, userId);
461        return applicationLabel.equals(activityLabel) ? badgedApplicationLabel
462                : res.getString(R.string.accessibility_recents_task_header,
463                        badgedApplicationLabel, activityLabel);
464    }
465
466    /**
467     * Returns the activity icon for the ActivityInfo for a user, badging if
468     * necessary.
469     */
470    public Drawable getActivityIcon(ActivityInfo info, int userId) {
471        if (mPm == null) return null;
472
473        // If we are mocking, then return a mock label
474        if (Constants.DebugFlags.App.EnableSystemServicesProxy) {
475            return new ColorDrawable(0xFF666666);
476        }
477
478        Drawable icon = info.loadIcon(mPm);
479        return getBadgedIcon(icon, userId);
480    }
481
482    /**
483     * Returns the given icon for a user, badging if necessary.
484     */
485    public Drawable getBadgedIcon(Drawable icon, int userId) {
486        if (userId != UserHandle.myUserId()) {
487            icon = mPm.getUserBadgedIcon(icon, new UserHandle(userId));
488        }
489        return icon;
490    }
491
492    /**
493     * Returns the given label for a user, badging if necessary.
494     */
495    public String getBadgedLabel(String label, int userId) {
496        if (userId != UserHandle.myUserId()) {
497            label = mPm.getUserBadgedLabel(label, new UserHandle(userId)).toString();
498        }
499        return label;
500    }
501
502    /** Returns the package name of the home activity. */
503    public String getHomeActivityPackageName() {
504        if (mPm == null) return null;
505        if (Constants.DebugFlags.App.EnableSystemServicesProxy) return null;
506
507        ArrayList<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>();
508        ComponentName defaultHomeActivity = mPm.getHomeActivities(homeActivities);
509        if (defaultHomeActivity != null) {
510            return defaultHomeActivity.getPackageName();
511        } else if (homeActivities.size() == 1) {
512            ResolveInfo info = homeActivities.get(0);
513            if (info.activityInfo != null) {
514                return info.activityInfo.packageName;
515            }
516        }
517        return null;
518    }
519
520    /**
521     * Returns whether the foreground user is the owner.
522     */
523    public boolean isForegroundUserSystem() {
524        if (mAm == null) return false;
525
526        return mAm.getCurrentUser() == UserHandle.USER_SYSTEM;
527    }
528
529    /**
530     * Returns the current search widget id.
531     */
532    public int getSearchAppWidgetId(Context context) {
533        return Prefs.getInt(context, Prefs.Key.SEARCH_APP_WIDGET_ID, -1);
534    }
535
536    /**
537     * Returns the current search widget info, binding a new one if necessary.
538     */
539    public AppWidgetProviderInfo getOrBindSearchAppWidget(Context context, AppWidgetHost host) {
540        int searchWidgetId = Prefs.getInt(context, Prefs.Key.SEARCH_APP_WIDGET_ID, -1);
541        AppWidgetProviderInfo searchWidgetInfo = mAwm.getAppWidgetInfo(searchWidgetId);
542        AppWidgetProviderInfo resolvedSearchWidgetInfo = resolveSearchAppWidget();
543
544        // Return the search widget info if it hasn't changed
545        if (searchWidgetInfo != null && resolvedSearchWidgetInfo != null &&
546                searchWidgetInfo.provider.equals(resolvedSearchWidgetInfo.provider)) {
547            if (Prefs.getString(context, Prefs.Key.SEARCH_APP_WIDGET_PACKAGE, null) == null) {
548                Prefs.putString(context, Prefs.Key.SEARCH_APP_WIDGET_PACKAGE,
549                        searchWidgetInfo.provider.getPackageName());
550            }
551            return searchWidgetInfo;
552        }
553
554        // Delete the old widget
555        if (searchWidgetId != -1) {
556            host.deleteAppWidgetId(searchWidgetId);
557        }
558
559        // And rebind a new search widget
560        if (resolvedSearchWidgetInfo != null) {
561            Pair<Integer, AppWidgetProviderInfo> widgetInfo = bindSearchAppWidget(host,
562                    resolvedSearchWidgetInfo);
563            if (widgetInfo != null) {
564                Prefs.putInt(context, Prefs.Key.SEARCH_APP_WIDGET_ID, widgetInfo.first);
565                Prefs.putString(context, Prefs.Key.SEARCH_APP_WIDGET_PACKAGE,
566                        widgetInfo.second.provider.getPackageName());
567                return widgetInfo.second;
568            }
569        }
570
571        // If we fall through here, then there is no resolved search widget, so clear the state
572        Prefs.remove(context, Prefs.Key.SEARCH_APP_WIDGET_ID);
573        Prefs.remove(context, Prefs.Key.SEARCH_APP_WIDGET_PACKAGE);
574        return null;
575    }
576
577    /**
578     * Returns the first Recents widget from the same package as the global assist activity.
579     */
580    private AppWidgetProviderInfo resolveSearchAppWidget() {
581        if (mAssistComponent == null) return null;
582        List<AppWidgetProviderInfo> widgets = mAwm.getInstalledProviders(
583                AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX);
584        for (AppWidgetProviderInfo info : widgets) {
585            if (info.provider.getPackageName().equals(mAssistComponent.getPackageName())) {
586                return info;
587            }
588        }
589        return null;
590    }
591
592    /**
593     * Resolves and binds the search app widget that is to appear in the recents.
594     */
595    private Pair<Integer, AppWidgetProviderInfo> bindSearchAppWidget(AppWidgetHost host,
596            AppWidgetProviderInfo resolvedSearchWidgetInfo) {
597        if (mAwm == null) return null;
598        if (mAssistComponent == null) return null;
599
600        // Allocate a new widget id and try and bind the app widget (if that fails, then just skip)
601        int searchWidgetId = host.allocateAppWidgetId();
602        Bundle opts = new Bundle();
603        opts.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
604                AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX);
605        if (!mAwm.bindAppWidgetIdIfAllowed(searchWidgetId, resolvedSearchWidgetInfo.provider, opts)) {
606            host.deleteAppWidgetId(searchWidgetId);
607            return null;
608        }
609        return new Pair<>(searchWidgetId, resolvedSearchWidgetInfo);
610    }
611
612    /**
613     * Returns whether touch exploration is currently enabled.
614     */
615    public boolean isTouchExplorationEnabled() {
616        if (mAccm == null) return false;
617
618        return mAccm.isEnabled() && mAccm.isTouchExplorationEnabled();
619    }
620
621    /**
622     * Returns a global setting.
623     */
624    public int getGlobalSetting(Context context, String setting) {
625        ContentResolver cr = context.getContentResolver();
626        return Settings.Global.getInt(cr, setting, 0);
627    }
628
629    /**
630     * Returns a system setting.
631     */
632    public int getSystemSetting(Context context, String setting) {
633        ContentResolver cr = context.getContentResolver();
634        return Settings.System.getInt(cr, setting, 0);
635    }
636
637    /**
638     * Returns a system property.
639     */
640    public String getSystemProperty(String key) {
641        return SystemProperties.get(key);
642    }
643
644    /**
645     * Returns the window rect.
646     */
647    public Rect getWindowRect() {
648        Rect windowRect = new Rect();
649        if (mWm == null) return windowRect;
650
651        Point p = new Point();
652        mWm.getDefaultDisplay().getRealSize(p);
653        windowRect.set(0, 0, p.x, p.y);
654        return windowRect;
655    }
656
657    /** Starts an activity from recents. */
658    public boolean startActivityFromRecents(Context context, int taskId, String taskName,
659            ActivityOptions options) {
660        if (mIam != null) {
661            try {
662                mIam.startActivityFromRecents(taskId, options == null ? null : options.toBundle());
663                return true;
664            } catch (Exception e) {
665                Console.logError(context,
666                        context.getString(R.string.recents_launch_error_message, taskName));
667            }
668        }
669        return false;
670    }
671
672    /** Starts an in-place animation on the front most application windows. */
673    public void startInPlaceAnimationOnFrontMostApplication(ActivityOptions opts) {
674        if (mIam == null) return;
675
676        try {
677            mIam.startInPlaceAnimationOnFrontMostApplication(opts);
678        } catch (Exception e) {
679            e.printStackTrace();
680        }
681    }
682
683    /** Registers a task stack listener with the system. */
684    public void registerTaskStackListener(ITaskStackListener listener) {
685        if (mIam == null) return;
686
687        try {
688            mIam.registerTaskStackListener(listener);
689        } catch (Exception e) {
690            e.printStackTrace();
691        }
692    }
693}
694