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