SystemServicesProxy.java revision e549a8d62108c7c7dabedbf4e77b9a653781723b
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 static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
20import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
21import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
22import static android.app.ActivityManager.StackId.HOME_STACK_ID;
23import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
24import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
25import static android.app.ActivityManager.StackId.RECENTS_STACK_ID;
26import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
27
28import android.annotation.NonNull;
29import android.annotation.Nullable;
30import android.app.ActivityManager;
31import android.app.ActivityManager.StackInfo;
32import android.app.ActivityManager.TaskSnapshot;
33import android.app.ActivityOptions;
34import android.app.AppGlobals;
35import android.app.IActivityManager;
36import android.app.KeyguardManager;
37import android.content.ComponentName;
38import android.content.ContentResolver;
39import android.content.Context;
40import android.content.Intent;
41import android.content.pm.ActivityInfo;
42import android.content.pm.ApplicationInfo;
43import android.content.pm.IPackageManager;
44import android.content.pm.PackageManager;
45import android.content.pm.ResolveInfo;
46import android.content.res.Resources;
47import android.graphics.Bitmap;
48import android.graphics.BitmapFactory;
49import android.graphics.Canvas;
50import android.graphics.Color;
51import android.graphics.Paint;
52import android.graphics.Point;
53import android.graphics.PorterDuff;
54import android.graphics.PorterDuffXfermode;
55import android.graphics.Rect;
56import android.graphics.drawable.BitmapDrawable;
57import android.graphics.drawable.ColorDrawable;
58import android.graphics.drawable.Drawable;
59import android.os.Handler;
60import android.os.IRemoteCallback;
61import android.os.Message;
62import android.os.ParcelFileDescriptor;
63import android.os.RemoteException;
64import android.os.ServiceManager;
65import android.os.SystemProperties;
66import android.os.Trace;
67import android.os.UserHandle;
68import android.os.UserManager;
69import android.provider.Settings;
70import android.provider.Settings.Secure;
71import android.service.dreams.DreamService;
72import android.service.dreams.IDreamManager;
73import android.util.ArraySet;
74import android.util.IconDrawableFactory;
75import android.util.Log;
76import android.util.MutableBoolean;
77import android.view.Display;
78import android.view.IAppTransitionAnimationSpecsFuture;
79import android.view.IDockedStackListener;
80import android.view.IWindowManager;
81import android.view.WindowManager;
82import android.view.WindowManager.KeyboardShortcutsReceiver;
83import android.view.WindowManagerGlobal;
84import android.view.accessibility.AccessibilityManager;
85
86import com.android.internal.app.AssistUtils;
87import com.android.internal.os.BackgroundThread;
88import com.android.keyguard.KeyguardUpdateMonitor;
89import com.android.systemui.Dependency;
90import com.android.systemui.R;
91import com.android.systemui.UiOffloadThread;
92import com.android.systemui.pip.tv.PipMenuActivity;
93import com.android.systemui.recents.Recents;
94import com.android.systemui.recents.RecentsDebugFlags;
95import com.android.systemui.recents.RecentsImpl;
96import com.android.systemui.recents.model.Task;
97import com.android.systemui.recents.model.ThumbnailData;
98
99import java.io.IOException;
100import java.util.ArrayList;
101import java.util.Collections;
102import java.util.Iterator;
103import java.util.List;
104import java.util.Random;
105import java.util.concurrent.ExecutorService;
106import java.util.concurrent.Executors;
107
108/**
109 * Acts as a shim around the real system services that we need to access data from, and provides
110 * a point of injection when testing UI.
111 */
112public class SystemServicesProxy {
113    final static String TAG = "SystemServicesProxy";
114
115    final static BitmapFactory.Options sBitmapOptions;
116    static {
117        sBitmapOptions = new BitmapFactory.Options();
118        sBitmapOptions.inMutable = true;
119        sBitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565;
120    }
121
122    final static List<String> sRecentsBlacklist;
123    static {
124        sRecentsBlacklist = new ArrayList<>();
125        sRecentsBlacklist.add(PipMenuActivity.class.getName());
126    }
127
128    private static SystemServicesProxy sSystemServicesProxy;
129
130    AccessibilityManager mAccm;
131    ActivityManager mAm;
132    IActivityManager mIam;
133    PackageManager mPm;
134    IconDrawableFactory mDrawableFactory;
135    IPackageManager mIpm;
136    private final IDreamManager mDreamManager;
137    private final Context mContext;
138    AssistUtils mAssistUtils;
139    WindowManager mWm;
140    IWindowManager mIwm;
141    KeyguardManager mKgm;
142    UserManager mUm;
143    Display mDisplay;
144    String mRecentsPackage;
145    ComponentName mAssistComponent;
146
147    boolean mIsSafeMode;
148    boolean mHasFreeformWorkspaceSupport;
149
150    Bitmap mDummyIcon;
151    int mDummyThumbnailWidth;
152    int mDummyThumbnailHeight;
153    Paint mBgProtectionPaint;
154    Canvas mBgProtectionCanvas;
155
156    private final Handler mHandler = new H();
157
158    private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
159
160    /**
161     * An abstract class to track task stack changes.
162     * Classes should implement this instead of {@link android.app.ITaskStackListener}
163     * to reduce IPC calls from system services. These callbacks will be called on the main thread.
164     */
165    public abstract static class TaskStackListener {
166        /**
167         * NOTE: This call is made of the thread that the binder call comes in on.
168         */
169        public void onTaskStackChangedBackground() { }
170        public void onTaskStackChanged() { }
171        public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { }
172        public void onActivityPinned(String packageName, int taskId) { }
173        public void onActivityUnpinned() { }
174        public void onPinnedActivityRestartAttempt(boolean clearedTask) { }
175        public void onPinnedStackAnimationStarted() { }
176        public void onPinnedStackAnimationEnded() { }
177        public void onActivityForcedResizable(String packageName, int taskId, int reason) { }
178        public void onActivityDismissingDockedStack() { }
179        public void onActivityLaunchOnSecondaryDisplayFailed() { }
180        public void onTaskProfileLocked(int taskId, int userId) { }
181
182        /**
183         * Checks that the current user matches the user's SystemUI process. Since
184         * {@link android.app.ITaskStackListener} is not multi-user aware, handlers of
185         * TaskStackListener should make this call to verify that we don't act on events from other
186         * user's processes.
187         */
188        protected final boolean checkCurrentUserId(boolean debug) {
189            int processUserId = UserHandle.myUserId();
190            int currentUserId = KeyguardUpdateMonitor.getCurrentUser();
191            if (processUserId != currentUserId) {
192                if (debug) {
193                    Log.d(TAG, "UID mismatch. SystemUI is running uid=" + processUserId
194                            + " and the current user is uid=" + currentUserId);
195                }
196                return false;
197            }
198            return true;
199        }
200    }
201
202    /**
203     * Implementation of {@link android.app.ITaskStackListener} to listen task stack changes from
204     * ActivityManagerService.
205     * This simply passes callbacks to listeners through {@link H}.
206     * */
207    private android.app.TaskStackListener mTaskStackListener = new android.app.TaskStackListener() {
208
209        private final List<SystemServicesProxy.TaskStackListener> mTmpListeners = new ArrayList<>();
210
211        @Override
212        public void onTaskStackChanged() throws RemoteException {
213            // Call the task changed callback for the non-ui thread listeners first
214            synchronized (mTaskStackListeners) {
215                mTmpListeners.clear();
216                mTmpListeners.addAll(mTaskStackListeners);
217            }
218            for (int i = mTmpListeners.size() - 1; i >= 0; i--) {
219                mTmpListeners.get(i).onTaskStackChangedBackground();
220            }
221
222            mHandler.removeMessages(H.ON_TASK_STACK_CHANGED);
223            mHandler.sendEmptyMessage(H.ON_TASK_STACK_CHANGED);
224        }
225
226        @Override
227        public void onActivityPinned(String packageName, int taskId) throws RemoteException {
228            mHandler.removeMessages(H.ON_ACTIVITY_PINNED);
229            mHandler.obtainMessage(H.ON_ACTIVITY_PINNED, taskId, 0, packageName).sendToTarget();
230        }
231
232        @Override
233        public void onActivityUnpinned() throws RemoteException {
234            mHandler.removeMessages(H.ON_ACTIVITY_UNPINNED);
235            mHandler.sendEmptyMessage(H.ON_ACTIVITY_UNPINNED);
236        }
237
238        @Override
239        public void onPinnedActivityRestartAttempt(boolean clearedTask)
240                throws RemoteException{
241            mHandler.removeMessages(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT);
242            mHandler.obtainMessage(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT, clearedTask ? 1 : 0, 0)
243                    .sendToTarget();
244        }
245
246        @Override
247        public void onPinnedStackAnimationStarted() throws RemoteException {
248            mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_STARTED);
249            mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_STARTED);
250        }
251
252        @Override
253        public void onPinnedStackAnimationEnded() throws RemoteException {
254            mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_ENDED);
255            mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_ENDED);
256        }
257
258        @Override
259        public void onActivityForcedResizable(String packageName, int taskId, int reason)
260                throws RemoteException {
261            mHandler.obtainMessage(H.ON_ACTIVITY_FORCED_RESIZABLE, taskId, reason, packageName)
262                    .sendToTarget();
263        }
264
265        @Override
266        public void onActivityDismissingDockedStack() throws RemoteException {
267            mHandler.sendEmptyMessage(H.ON_ACTIVITY_DISMISSING_DOCKED_STACK);
268        }
269
270        @Override
271        public void onActivityLaunchOnSecondaryDisplayFailed() throws RemoteException {
272            mHandler.sendEmptyMessage(H.ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED);
273        }
274
275        @Override
276        public void onTaskProfileLocked(int taskId, int userId) {
277            mHandler.obtainMessage(H.ON_TASK_PROFILE_LOCKED, taskId, userId).sendToTarget();
278        }
279
280        @Override
281        public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot)
282                throws RemoteException {
283            mHandler.obtainMessage(H.ON_TASK_SNAPSHOT_CHANGED, taskId, 0, snapshot).sendToTarget();
284        }
285    };
286
287    /**
288     * List of {@link TaskStackListener} registered from {@link #registerTaskStackListener}.
289     */
290    private List<TaskStackListener> mTaskStackListeners = new ArrayList<>();
291
292    /** Private constructor */
293    private SystemServicesProxy(Context context) {
294        mContext = context.getApplicationContext();
295        mAccm = AccessibilityManager.getInstance(context);
296        mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
297        mIam = ActivityManager.getService();
298        mPm = context.getPackageManager();
299        mDrawableFactory = IconDrawableFactory.newInstance(context);
300        mIpm = AppGlobals.getPackageManager();
301        mAssistUtils = new AssistUtils(context);
302        mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
303        mIwm = WindowManagerGlobal.getWindowManagerService();
304        mKgm = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
305        mUm = UserManager.get(context);
306        mDreamManager = IDreamManager.Stub.asInterface(
307                ServiceManager.checkService(DreamService.DREAM_SERVICE));
308        mDisplay = mWm.getDefaultDisplay();
309        mRecentsPackage = context.getPackageName();
310        mHasFreeformWorkspaceSupport =
311                mPm.hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT) ||
312                        Settings.Global.getInt(context.getContentResolver(),
313                                DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0;
314        mIsSafeMode = mPm.isSafeMode();
315
316        // Get the dummy thumbnail width/heights
317        Resources res = context.getResources();
318        int wId = com.android.internal.R.dimen.thumbnail_width;
319        int hId = com.android.internal.R.dimen.thumbnail_height;
320        mDummyThumbnailWidth = res.getDimensionPixelSize(wId);
321        mDummyThumbnailHeight = res.getDimensionPixelSize(hId);
322
323        // Create the protection paints
324        mBgProtectionPaint = new Paint();
325        mBgProtectionPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));
326        mBgProtectionPaint.setColor(0xFFffffff);
327        mBgProtectionCanvas = new Canvas();
328
329        // Resolve the assist intent
330        mAssistComponent = mAssistUtils.getAssistComponentForUser(UserHandle.myUserId());
331
332        if (RecentsDebugFlags.Static.EnableMockTasks) {
333            // Create a dummy icon
334            mDummyIcon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
335            mDummyIcon.eraseColor(0xFF999999);
336        }
337
338        Collections.addAll(sRecentsBlacklist,
339                res.getStringArray(R.array.recents_blacklist_array));
340    }
341
342    /**
343     * Returns the single instance of the {@link SystemServicesProxy}.
344     * This should only be called on the main thread.
345     */
346    public static synchronized SystemServicesProxy getInstance(Context context) {
347        if (sSystemServicesProxy == null) {
348            sSystemServicesProxy = new SystemServicesProxy(context);
349        }
350        return sSystemServicesProxy;
351    }
352
353    /**
354     * @return whether the provided {@param className} is blacklisted
355     */
356    public boolean isBlackListedActivity(String className) {
357        return sRecentsBlacklist.contains(className);
358    }
359
360    /**
361     * Returns a list of the recents tasks.
362     *
363     * @param includeFrontMostExcludedTask if set, will ensure that the front most excluded task
364     *                                     will be visible, otherwise no excluded tasks will be
365     *                                     visible.
366     */
367    public List<ActivityManager.RecentTaskInfo> getRecentTasks(int numLatestTasks, int userId,
368            boolean includeFrontMostExcludedTask, ArraySet<Integer> quietProfileIds) {
369        if (mAm == null) return null;
370
371        // If we are mocking, then create some recent tasks
372        if (RecentsDebugFlags.Static.EnableMockTasks) {
373            ArrayList<ActivityManager.RecentTaskInfo> tasks =
374                    new ArrayList<ActivityManager.RecentTaskInfo>();
375            int count = Math.min(numLatestTasks, RecentsDebugFlags.Static.MockTaskCount);
376            for (int i = 0; i < count; i++) {
377                // Create a dummy component name
378                int packageIndex = i % RecentsDebugFlags.Static.MockTasksPackageCount;
379                ComponentName cn = new ComponentName("com.android.test" + packageIndex,
380                        "com.android.test" + i + ".Activity");
381                String description = "" + i + " - " +
382                        Long.toString(Math.abs(new Random().nextLong()), 36);
383                // Create the recent task info
384                ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
385                rti.id = rti.persistentId = rti.affiliatedTaskId = i;
386                rti.baseIntent = new Intent();
387                rti.baseIntent.setComponent(cn);
388                rti.description = description;
389                rti.firstActiveTime = rti.lastActiveTime = i;
390                if (i % 2 == 0) {
391                    rti.taskDescription = new ActivityManager.TaskDescription(description,
392                            Bitmap.createBitmap(mDummyIcon), null,
393                            0xFF000000 | (0xFFFFFF & new Random().nextInt()),
394                            0xFF000000 | (0xFFFFFF & new Random().nextInt()),
395                            0, 0);
396                } else {
397                    rti.taskDescription = new ActivityManager.TaskDescription();
398                }
399                tasks.add(rti);
400            }
401            return tasks;
402        }
403
404        // Remove home/recents/excluded tasks
405        int minNumTasksToQuery = 10;
406        int numTasksToQuery = Math.max(minNumTasksToQuery, numLatestTasks);
407        int flags = ActivityManager.RECENT_IGNORE_HOME_AND_RECENTS_STACK_TASKS |
408                ActivityManager.RECENT_INGORE_DOCKED_STACK_TOP_TASK |
409                ActivityManager.RECENT_INGORE_PINNED_STACK_TASKS |
410                ActivityManager.RECENT_IGNORE_UNAVAILABLE |
411                ActivityManager.RECENT_INCLUDE_PROFILES;
412        if (includeFrontMostExcludedTask) {
413            flags |= ActivityManager.RECENT_WITH_EXCLUDED;
414        }
415        List<ActivityManager.RecentTaskInfo> tasks = null;
416        try {
417            tasks = mAm.getRecentTasksForUser(numTasksToQuery, flags, userId);
418        } catch (Exception e) {
419            Log.e(TAG, "Failed to get recent tasks", e);
420        }
421
422        // Break early if we can't get a valid set of tasks
423        if (tasks == null) {
424            return new ArrayList<>();
425        }
426
427        boolean isFirstValidTask = true;
428        Iterator<ActivityManager.RecentTaskInfo> iter = tasks.iterator();
429        while (iter.hasNext()) {
430            ActivityManager.RecentTaskInfo t = iter.next();
431
432            // NOTE: The order of these checks happens in the expected order of the traversal of the
433            // tasks
434
435            // Remove the task if it or it's package are blacklsited
436            if (sRecentsBlacklist.contains(t.realActivity.getClassName()) ||
437                    sRecentsBlacklist.contains(t.realActivity.getPackageName())) {
438                iter.remove();
439                continue;
440            }
441
442            // Remove the task if it is marked as excluded, unless it is the first most task and we
443            // are requested to include it
444            boolean isExcluded = (t.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
445                    == Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
446            isExcluded |= quietProfileIds.contains(t.userId);
447            if (isExcluded && (!isFirstValidTask || !includeFrontMostExcludedTask)) {
448                iter.remove();
449            }
450
451            isFirstValidTask = false;
452        }
453
454        return tasks.subList(0, Math.min(tasks.size(), numLatestTasks));
455    }
456
457    /**
458     * Returns the top running task.
459     */
460    public ActivityManager.RunningTaskInfo getRunningTask() {
461        // Note: The set of running tasks from the system is ordered by recency
462        List<ActivityManager.RunningTaskInfo> tasks = mAm.getRunningTasks(10);
463        if (tasks != null && !tasks.isEmpty()) {
464            // Find the first task in a valid stack, we ignore everything from the Recents and PiP
465            // stacks
466            for (int i = 0; i < tasks.size(); i++) {
467                ActivityManager.RunningTaskInfo task = tasks.get(i);
468                int stackId = task.stackId;
469                if (stackId != RECENTS_STACK_ID && stackId != PINNED_STACK_ID) {
470                    return task;
471                }
472            }
473        }
474        return null;
475    }
476
477    /**
478     * Returns whether the recents activity is currently visible.
479     */
480    public boolean isRecentsActivityVisible() {
481        return isRecentsActivityVisible(null);
482    }
483
484    /**
485     * Returns whether the recents activity is currently visible.
486     *
487     * @param isHomeStackVisible if provided, will return whether the home stack is visible
488     *                           regardless of the recents visibility
489     */
490    public boolean isRecentsActivityVisible(MutableBoolean isHomeStackVisible) {
491        if (mIam == null) return false;
492
493        try {
494            List<StackInfo> stackInfos = mIam.getAllStackInfos();
495            ActivityManager.StackInfo homeStackInfo = null;
496            ActivityManager.StackInfo fullscreenStackInfo = null;
497            ActivityManager.StackInfo recentsStackInfo = null;
498            for (int i = 0; i < stackInfos.size(); i++) {
499                StackInfo stackInfo = stackInfos.get(i);
500                if (stackInfo.stackId == HOME_STACK_ID) {
501                    homeStackInfo = stackInfo;
502                } else if (stackInfo.stackId == FULLSCREEN_WORKSPACE_STACK_ID) {
503                    fullscreenStackInfo = stackInfo;
504                } else if (stackInfo.stackId == RECENTS_STACK_ID) {
505                    recentsStackInfo = stackInfo;
506                }
507            }
508            boolean homeStackVisibleNotOccluded = isStackNotOccluded(homeStackInfo,
509                    fullscreenStackInfo);
510            boolean recentsStackVisibleNotOccluded = isStackNotOccluded(recentsStackInfo,
511                    fullscreenStackInfo);
512            if (isHomeStackVisible != null) {
513                isHomeStackVisible.value = homeStackVisibleNotOccluded;
514            }
515            ComponentName topActivity = recentsStackInfo != null ?
516                    recentsStackInfo.topActivity : null;
517            return (recentsStackVisibleNotOccluded && topActivity != null
518                    && topActivity.getPackageName().equals(RecentsImpl.RECENTS_PACKAGE)
519                    && Recents.RECENTS_ACTIVITIES.contains(topActivity.getClassName()));
520        } catch (RemoteException e) {
521            e.printStackTrace();
522        }
523        return false;
524    }
525
526    private boolean isStackNotOccluded(ActivityManager.StackInfo stackInfo,
527            ActivityManager.StackInfo fullscreenStackInfo) {
528        boolean stackVisibleNotOccluded = stackInfo == null || stackInfo.visible;
529        if (fullscreenStackInfo != null && stackInfo != null) {
530            boolean isFullscreenStackOccludingg = fullscreenStackInfo.visible &&
531                    fullscreenStackInfo.position > stackInfo.position;
532            stackVisibleNotOccluded &= !isFullscreenStackOccludingg;
533        }
534        return stackVisibleNotOccluded;
535    }
536
537    /**
538     * Returns whether this device has freeform workspaces.
539     */
540    public boolean hasFreeformWorkspaceSupport() {
541        return mHasFreeformWorkspaceSupport;
542    }
543
544    /**
545     * Returns whether this device is in the safe mode.
546     */
547    public boolean isInSafeMode() {
548        return mIsSafeMode;
549    }
550
551    /** Docks a task to the side of the screen and starts it. */
552    public boolean startTaskInDockedMode(int taskId, int createMode) {
553        if (mIam == null) return false;
554
555        try {
556            final ActivityOptions options = ActivityOptions.makeBasic();
557            options.setDockCreateMode(createMode);
558            options.setLaunchStackId(DOCKED_STACK_ID);
559            mIam.startActivityFromRecents(taskId, options.toBundle());
560            return true;
561        } catch (Exception e) {
562            Log.e(TAG, "Failed to dock task: " + taskId + " with createMode: " + createMode, e);
563        }
564        return false;
565    }
566
567    /** Docks an already resumed task to the side of the screen. */
568    public boolean moveTaskToDockedStack(int taskId, int createMode, Rect initialBounds) {
569        if (mIam == null) {
570            return false;
571        }
572
573        try {
574            return mIam.moveTaskToDockedStack(taskId, createMode, true /* onTop */,
575                    false /* animate */, initialBounds);
576        } catch (RemoteException e) {
577            e.printStackTrace();
578        }
579        return false;
580    }
581
582    /**
583     * Returns whether the given stack id is the home stack id.
584     */
585    public static boolean isHomeStack(int stackId) {
586        return stackId == HOME_STACK_ID;
587    }
588
589    /**
590     * Returns whether the given stack id is the pinned stack id.
591     */
592    public static boolean isPinnedStack(int stackId){
593        return stackId == PINNED_STACK_ID;
594    }
595
596    /**
597     * Returns whether the given stack id is the docked stack id.
598     */
599    public static boolean isDockedStack(int stackId) {
600        return stackId == DOCKED_STACK_ID;
601    }
602
603    /**
604     * Returns whether the given stack id is the freeform workspace stack id.
605     */
606    public static boolean isFreeformStack(int stackId) {
607        return stackId == FREEFORM_WORKSPACE_STACK_ID;
608    }
609
610    /**
611     * @return whether there are any docked tasks for the current user.
612     */
613    public boolean hasDockedTask() {
614        if (mIam == null) return false;
615
616        ActivityManager.StackInfo stackInfo = null;
617        try {
618            stackInfo = mIam.getStackInfo(DOCKED_STACK_ID);
619        } catch (RemoteException e) {
620            e.printStackTrace();
621        }
622
623        if (stackInfo != null) {
624            int userId = getCurrentUser();
625            boolean hasUserTask = false;
626            for (int i = stackInfo.taskUserIds.length - 1; i >= 0 && !hasUserTask; i--) {
627                hasUserTask = (stackInfo.taskUserIds[i] == userId);
628            }
629            return hasUserTask;
630        }
631        return false;
632    }
633
634    /**
635     * Returns whether there is a soft nav bar.
636     */
637    public boolean hasSoftNavigationBar() {
638        try {
639            return WindowManagerGlobal.getWindowManagerService().hasNavigationBar();
640        } catch (RemoteException e) {
641            e.printStackTrace();
642        }
643        return false;
644    }
645
646    /**
647     * Returns whether the device has a transposed nav bar (on the right of the screen) in the
648     * current display orientation.
649     */
650    public boolean hasTransposedNavigationBar() {
651        Rect insets = new Rect();
652        getStableInsets(insets);
653        return insets.right > 0;
654    }
655
656    /**
657     * Cancels the current window transtion to/from Recents for the given task id.
658     */
659    public void cancelWindowTransition(int taskId) {
660        if (mIam == null) return;
661
662        try {
663            mIam.cancelTaskWindowTransition(taskId);
664        } catch (RemoteException e) {
665            e.printStackTrace();
666        }
667    }
668
669    /**
670     * Cancels the current thumbnail transtion to/from Recents for the given task id.
671     */
672    public void cancelThumbnailTransition(int taskId) {
673        if (mIam == null) return;
674
675        try {
676            mIam.cancelTaskThumbnailTransition(taskId);
677        } catch (RemoteException e) {
678            e.printStackTrace();
679        }
680    }
681
682    /** Returns the top task thumbnail for the given task id */
683    public ThumbnailData getTaskThumbnail(int taskId, boolean reduced) {
684        if (mAm == null) return null;
685
686        // If we are mocking, then just return a dummy thumbnail
687        if (RecentsDebugFlags.Static.EnableMockTasks) {
688            ThumbnailData thumbnailData = new ThumbnailData();
689            thumbnailData.thumbnail = Bitmap.createBitmap(mDummyThumbnailWidth,
690                    mDummyThumbnailHeight, Bitmap.Config.ARGB_8888);
691            thumbnailData.thumbnail.eraseColor(0xff333333);
692            return thumbnailData;
693        }
694
695        ThumbnailData thumbnailData = getThumbnail(taskId, reduced);
696        if (thumbnailData.thumbnail != null && !ActivityManager.ENABLE_TASK_SNAPSHOTS) {
697            thumbnailData.thumbnail.setHasAlpha(false);
698            // We use a dumb heuristic for now, if the thumbnail is purely transparent in the top
699            // left pixel, then assume the whole thumbnail is transparent. Generally, proper
700            // screenshots are always composed onto a bitmap that has no alpha.
701            if (Color.alpha(thumbnailData.thumbnail.getPixel(0, 0)) == 0) {
702                mBgProtectionCanvas.setBitmap(thumbnailData.thumbnail);
703                mBgProtectionCanvas.drawRect(0, 0, thumbnailData.thumbnail.getWidth(),
704                        thumbnailData.thumbnail.getHeight(), mBgProtectionPaint);
705                mBgProtectionCanvas.setBitmap(null);
706                Log.e(TAG, "Invalid screenshot detected from getTaskThumbnail()");
707            }
708        }
709        return thumbnailData;
710    }
711
712    /**
713     * Returns a task thumbnail from the activity manager
714     */
715    public @NonNull ThumbnailData getThumbnail(int taskId, boolean reducedResolution) {
716        if (mAm == null) {
717            return new ThumbnailData();
718        }
719
720        final ThumbnailData thumbnailData;
721        if (ActivityManager.ENABLE_TASK_SNAPSHOTS) {
722            ActivityManager.TaskSnapshot snapshot = null;
723            try {
724                snapshot = ActivityManager.getService().getTaskSnapshot(taskId, reducedResolution);
725            } catch (RemoteException e) {
726                Log.w(TAG, "Failed to retrieve snapshot", e);
727            }
728            if (snapshot != null) {
729                thumbnailData = ThumbnailData.createFromTaskSnapshot(snapshot);
730            } else {
731                return new ThumbnailData();
732            }
733        } else {
734            ActivityManager.TaskThumbnail taskThumbnail = mAm.getTaskThumbnail(taskId);
735            if (taskThumbnail == null) {
736                return new ThumbnailData();
737            }
738
739            Bitmap thumbnail = taskThumbnail.mainThumbnail;
740            ParcelFileDescriptor descriptor = taskThumbnail.thumbnailFileDescriptor;
741            if (thumbnail == null && descriptor != null) {
742                thumbnail = BitmapFactory.decodeFileDescriptor(descriptor.getFileDescriptor(),
743                        null, sBitmapOptions);
744            }
745            if (descriptor != null) {
746                try {
747                    descriptor.close();
748                } catch (IOException e) {
749                }
750            }
751            thumbnailData = new ThumbnailData();
752            thumbnailData.thumbnail = thumbnail;
753            thumbnailData.orientation = taskThumbnail.thumbnailInfo.screenOrientation;
754            thumbnailData.insets.setEmpty();
755        }
756        return thumbnailData;
757    }
758
759    /**
760     * Moves a task into another stack.
761     */
762    public void moveTaskToStack(int taskId, int stackId) {
763        if (mIam == null) return;
764
765        try {
766            mIam.positionTaskInStack(taskId, stackId, 0);
767        } catch (RemoteException | IllegalArgumentException e) {
768            e.printStackTrace();
769        }
770    }
771
772    /** Removes the task */
773    public void removeTask(final int taskId) {
774        if (mAm == null) return;
775        if (RecentsDebugFlags.Static.EnableMockTasks) return;
776
777        // Remove the task.
778        BackgroundThread.getHandler().post(new Runnable() {
779            @Override
780            public void run() {
781                mAm.removeTask(taskId);
782            }
783        });
784    }
785
786    /**
787     * Sends a message to close other system windows.
788     */
789    public void sendCloseSystemWindows(String reason) {
790        mUiOffloadThread.submit(() -> {
791            try {
792                mIam.closeSystemDialogs(reason);
793            } catch (RemoteException e) {
794            }
795        });
796    }
797
798    /**
799     * Returns the activity info for a given component name.
800     *
801     * @param cn The component name of the activity.
802     * @param userId The userId of the user that this is for.
803     */
804    public ActivityInfo getActivityInfo(ComponentName cn, int userId) {
805        if (mIpm == null) return null;
806        if (RecentsDebugFlags.Static.EnableMockTasks) return new ActivityInfo();
807
808        try {
809            return mIpm.getActivityInfo(cn, PackageManager.GET_META_DATA, userId);
810        } catch (RemoteException e) {
811            e.printStackTrace();
812            return null;
813        }
814    }
815
816    /**
817     * Returns the activity info for a given component name.
818     *
819     * @param cn The component name of the activity.
820     */
821    public ActivityInfo getActivityInfo(ComponentName cn) {
822        if (mPm == null) return null;
823        if (RecentsDebugFlags.Static.EnableMockTasks) return new ActivityInfo();
824
825        try {
826            return mPm.getActivityInfo(cn, PackageManager.GET_META_DATA);
827        } catch (PackageManager.NameNotFoundException e) {
828            e.printStackTrace();
829            return null;
830        }
831    }
832
833    /**
834     * Returns the activity label, badging if necessary.
835     */
836    public String getBadgedActivityLabel(ActivityInfo info, int userId) {
837        if (mPm == null) return null;
838
839        // If we are mocking, then return a mock label
840        if (RecentsDebugFlags.Static.EnableMockTasks) {
841            return "Recent Task: " + userId;
842        }
843
844        return getBadgedLabel(info.loadLabel(mPm).toString(), userId);
845    }
846
847    /**
848     * Returns the application label, badging if necessary.
849     */
850    public String getBadgedApplicationLabel(ApplicationInfo appInfo, int userId) {
851        if (mPm == null) return null;
852
853        // If we are mocking, then return a mock label
854        if (RecentsDebugFlags.Static.EnableMockTasks) {
855            return "Recent Task App: " + userId;
856        }
857
858        return getBadgedLabel(appInfo.loadLabel(mPm).toString(), userId);
859    }
860
861    /**
862     * Returns the content description for a given task, badging it if necessary.  The content
863     * description joins the app and activity labels.
864     */
865    public String getBadgedContentDescription(ActivityInfo info, int userId,
866            ActivityManager.TaskDescription td, Resources res) {
867        // If we are mocking, then return a mock label
868        if (RecentsDebugFlags.Static.EnableMockTasks) {
869            return "Recent Task Content Description: " + userId;
870        }
871
872        String activityLabel;
873        if (td != null && td.getLabel() != null) {
874            activityLabel = td.getLabel();
875        } else {
876            activityLabel = info.loadLabel(mPm).toString();
877        }
878        String applicationLabel = info.applicationInfo.loadLabel(mPm).toString();
879        String badgedApplicationLabel = getBadgedLabel(applicationLabel, userId);
880        return applicationLabel.equals(activityLabel) ? badgedApplicationLabel
881                : res.getString(R.string.accessibility_recents_task_header,
882                        badgedApplicationLabel, activityLabel);
883    }
884
885    /**
886     * Returns the activity icon for the ActivityInfo for a user, badging if
887     * necessary.
888     */
889    public Drawable getBadgedActivityIcon(ActivityInfo info, int userId) {
890        if (mPm == null) return null;
891
892        // If we are mocking, then return a mock label
893        if (RecentsDebugFlags.Static.EnableMockTasks) {
894            return new ColorDrawable(0xFF666666);
895        }
896
897        return mDrawableFactory.getBadgedIcon(info, info.applicationInfo, userId);
898    }
899
900    /**
901     * Returns the application icon for the ApplicationInfo for a user, badging if
902     * necessary.
903     */
904    public Drawable getBadgedApplicationIcon(ApplicationInfo appInfo, int userId) {
905        if (mPm == null) return null;
906
907        // If we are mocking, then return a mock label
908        if (RecentsDebugFlags.Static.EnableMockTasks) {
909            return new ColorDrawable(0xFF666666);
910        }
911
912        return mDrawableFactory.getBadgedIcon(appInfo, userId);
913    }
914
915    /**
916     * Returns the task description icon, loading and badging it if it necessary.
917     */
918    public Drawable getBadgedTaskDescriptionIcon(ActivityManager.TaskDescription taskDescription,
919            int userId, Resources res) {
920
921        // If we are mocking, then return a mock label
922        if (RecentsDebugFlags.Static.EnableMockTasks) {
923            return new ColorDrawable(0xFF666666);
924        }
925
926        Bitmap tdIcon = taskDescription.getInMemoryIcon();
927        if (tdIcon == null) {
928            tdIcon = ActivityManager.TaskDescription.loadTaskDescriptionIcon(
929                    taskDescription.getIconFilename(), userId);
930        }
931        if (tdIcon != null) {
932            return getBadgedIcon(new BitmapDrawable(res, tdIcon), userId);
933        }
934        return null;
935    }
936
937    public ActivityManager.TaskDescription getTaskDescription(int taskId) {
938        try {
939            return mIam.getTaskDescription(taskId);
940        } catch (RemoteException e) {
941            return null;
942        }
943    }
944
945    /**
946     * Returns the given icon for a user, badging if necessary.
947     */
948    private Drawable getBadgedIcon(Drawable icon, int userId) {
949        if (userId != UserHandle.myUserId()) {
950            icon = mPm.getUserBadgedIcon(icon, new UserHandle(userId));
951        }
952        return icon;
953    }
954
955    /**
956     * Returns a banner used on TV for the specified Activity.
957     */
958    public Drawable getActivityBanner(ActivityInfo info) {
959        if (mPm == null) return null;
960
961        // If we are mocking, then return a mock banner
962        if (RecentsDebugFlags.Static.EnableMockTasks) {
963            return new ColorDrawable(0xFF666666);
964        }
965
966        Drawable banner = info.loadBanner(mPm);
967        return banner;
968    }
969
970    /**
971     * Returns a logo used on TV for the specified Activity.
972     */
973    public Drawable getActivityLogo(ActivityInfo info) {
974        if (mPm == null) return null;
975
976        // If we are mocking, then return a mock logo
977        if (RecentsDebugFlags.Static.EnableMockTasks) {
978            return new ColorDrawable(0xFF666666);
979        }
980
981        Drawable logo = info.loadLogo(mPm);
982        return logo;
983    }
984
985
986    /**
987     * Returns the given label for a user, badging if necessary.
988     */
989    private String getBadgedLabel(String label, int userId) {
990        if (userId != UserHandle.myUserId()) {
991            label = mPm.getUserBadgedLabel(label, new UserHandle(userId)).toString();
992        }
993        return label;
994    }
995
996    /**
997     * Returns whether the provided {@param userId} is currently locked (and showing Keyguard).
998     */
999    public boolean isDeviceLocked(int userId) {
1000        if (mKgm == null) {
1001            return false;
1002        }
1003        return mKgm.isDeviceLocked(userId);
1004    }
1005
1006    /** Returns the package name of the home activity. */
1007    public String getHomeActivityPackageName() {
1008        if (mPm == null) return null;
1009        if (RecentsDebugFlags.Static.EnableMockTasks) return null;
1010
1011        ArrayList<ResolveInfo> homeActivities = new ArrayList<>();
1012        ComponentName defaultHomeActivity = mPm.getHomeActivities(homeActivities);
1013        if (defaultHomeActivity != null) {
1014            return defaultHomeActivity.getPackageName();
1015        } else if (homeActivities.size() == 1) {
1016            ResolveInfo info = homeActivities.get(0);
1017            if (info.activityInfo != null) {
1018                return info.activityInfo.packageName;
1019            }
1020        }
1021        return null;
1022    }
1023
1024    /**
1025     * Returns whether the provided {@param userId} represents the system user.
1026     */
1027    public boolean isSystemUser(int userId) {
1028        return userId == UserHandle.USER_SYSTEM;
1029    }
1030
1031    /**
1032     * Returns the current user id.
1033     */
1034    public int getCurrentUser() {
1035        return KeyguardUpdateMonitor.getCurrentUser();
1036    }
1037
1038    /**
1039     * Returns the processes user id.
1040     */
1041    public int getProcessUser() {
1042        if (mUm == null) return 0;
1043        return mUm.getUserHandle();
1044    }
1045
1046    /**
1047     * Returns whether touch exploration is currently enabled.
1048     */
1049    public boolean isTouchExplorationEnabled() {
1050        if (mAccm == null) return false;
1051
1052        return mAccm.isEnabled() && mAccm.isTouchExplorationEnabled();
1053    }
1054
1055    /**
1056     * Returns whether the current task is in screen-pinning mode.
1057     */
1058    public boolean isScreenPinningActive() {
1059        if (mIam == null) return false;
1060
1061        try {
1062            return mIam.isInLockTaskMode();
1063        } catch (RemoteException e) {
1064            return false;
1065        }
1066    }
1067
1068    /**
1069     * Returns a global setting.
1070     */
1071    public int getGlobalSetting(Context context, String setting) {
1072        ContentResolver cr = context.getContentResolver();
1073        return Settings.Global.getInt(cr, setting, 0);
1074    }
1075
1076    /**
1077     * Returns a system setting.
1078     */
1079    public int getSystemSetting(Context context, String setting) {
1080        ContentResolver cr = context.getContentResolver();
1081        return Settings.System.getInt(cr, setting, 0);
1082    }
1083
1084    /**
1085     * Returns a system property.
1086     */
1087    public String getSystemProperty(String key) {
1088        return SystemProperties.get(key);
1089    }
1090
1091    /**
1092     * Returns the smallest width/height.
1093     */
1094    public int getDeviceSmallestWidth() {
1095        if (mDisplay == null) return 0;
1096
1097        Point smallestSizeRange = new Point();
1098        Point largestSizeRange = new Point();
1099        mDisplay.getCurrentSizeRange(smallestSizeRange, largestSizeRange);
1100        return smallestSizeRange.x;
1101    }
1102
1103    /**
1104     * Returns the current display rect in the current display orientation.
1105     */
1106    public Rect getDisplayRect() {
1107        Rect displayRect = new Rect();
1108        if (mDisplay == null) return displayRect;
1109
1110        Point p = new Point();
1111        mDisplay.getRealSize(p);
1112        displayRect.set(0, 0, p.x, p.y);
1113        return displayRect;
1114    }
1115
1116    /**
1117     * Returns the window rect for the RecentsActivity, based on the dimensions of the recents stack
1118     */
1119    public Rect getWindowRect() {
1120        Rect windowRect = new Rect();
1121        if (mIam == null) return windowRect;
1122
1123        try {
1124            // Use the recents stack bounds, fallback to fullscreen stack if it is null
1125            ActivityManager.StackInfo stackInfo = mIam.getStackInfo(RECENTS_STACK_ID);
1126            if (stackInfo == null) {
1127                stackInfo = mIam.getStackInfo(FULLSCREEN_WORKSPACE_STACK_ID);
1128            }
1129            if (stackInfo != null) {
1130                windowRect.set(stackInfo.bounds);
1131            }
1132        } catch (RemoteException e) {
1133            e.printStackTrace();
1134        } finally {
1135            return windowRect;
1136        }
1137    }
1138
1139    public void startActivityAsUserAsync(Intent intent, ActivityOptions opts) {
1140        mUiOffloadThread.submit(() -> mContext.startActivityAsUser(intent,
1141                opts != null ? opts.toBundle() : null, UserHandle.CURRENT));
1142    }
1143
1144    /** Starts an activity from recents. */
1145    public void startActivityFromRecents(Context context, Task.TaskKey taskKey, String taskName,
1146            ActivityOptions options, int stackId,
1147            @Nullable final StartActivityFromRecentsResultListener resultListener) {
1148        if (mIam == null) {
1149            return;
1150        }
1151        if (taskKey.stackId == DOCKED_STACK_ID) {
1152            // We show non-visible docked tasks in Recents, but we always want to launch
1153            // them in the fullscreen stack.
1154            if (options == null) {
1155                options = ActivityOptions.makeBasic();
1156            }
1157            options.setLaunchStackId(FULLSCREEN_WORKSPACE_STACK_ID);
1158        } else if (stackId != INVALID_STACK_ID) {
1159            if (options == null) {
1160                options = ActivityOptions.makeBasic();
1161            }
1162            options.setLaunchStackId(stackId);
1163        }
1164        final ActivityOptions finalOptions = options;
1165
1166        // Execute this from another thread such that we can do other things (like caching the
1167        // bitmap for the thumbnail) while AM is busy starting our activity.
1168        mUiOffloadThread.submit(() -> {
1169            try {
1170                mIam.startActivityFromRecents(
1171                        taskKey.id, finalOptions == null ? null : finalOptions.toBundle());
1172                if (resultListener != null) {
1173                    mHandler.post(() -> resultListener.onStartActivityResult(true));
1174                }
1175            } catch (Exception e) {
1176                Log.e(TAG, context.getString(
1177                        R.string.recents_launch_error_message, taskName), e);
1178                if (resultListener != null) {
1179                    mHandler.post(() -> resultListener.onStartActivityResult(false));
1180                }
1181            }
1182        });
1183    }
1184
1185    /** Starts an in-place animation on the front most application windows. */
1186    public void startInPlaceAnimationOnFrontMostApplication(ActivityOptions opts) {
1187        if (mIam == null) return;
1188
1189        try {
1190            mIam.startInPlaceAnimationOnFrontMostApplication(
1191                    opts == null ? null : opts.toBundle());
1192        } catch (Exception e) {
1193            e.printStackTrace();
1194        }
1195    }
1196
1197    /**
1198     * Registers a task stack listener with the system.
1199     * This should be called on the main thread.
1200     */
1201    public void registerTaskStackListener(TaskStackListener listener) {
1202        if (mIam == null) return;
1203
1204        synchronized (mTaskStackListeners) {
1205            mTaskStackListeners.add(listener);
1206            if (mTaskStackListeners.size() == 1) {
1207                // Register mTaskStackListener to IActivityManager only once if needed.
1208                try {
1209                    mIam.registerTaskStackListener(mTaskStackListener);
1210                } catch (Exception e) {
1211                    Log.w(TAG, "Failed to call registerTaskStackListener", e);
1212                }
1213            }
1214        }
1215    }
1216
1217    public void endProlongedAnimations() {
1218        if (mWm == null) {
1219            return;
1220        }
1221        try {
1222            WindowManagerGlobal.getWindowManagerService().endProlongedAnimations();
1223        } catch (Exception e) {
1224            e.printStackTrace();
1225        }
1226    }
1227
1228    public void registerDockedStackListener(IDockedStackListener listener) {
1229        if (mWm == null) return;
1230
1231        try {
1232            WindowManagerGlobal.getWindowManagerService().registerDockedStackListener(listener);
1233        } catch (Exception e) {
1234            e.printStackTrace();
1235        }
1236    }
1237
1238    /**
1239     * Calculates the size of the dock divider in the current orientation.
1240     */
1241    public int getDockedDividerSize(Context context) {
1242        Resources res = context.getResources();
1243        int dividerWindowWidth = res.getDimensionPixelSize(
1244                com.android.internal.R.dimen.docked_stack_divider_thickness);
1245        int dividerInsets = res.getDimensionPixelSize(
1246                com.android.internal.R.dimen.docked_stack_divider_insets);
1247        return dividerWindowWidth - 2 * dividerInsets;
1248    }
1249
1250    public void requestKeyboardShortcuts(
1251            Context context, KeyboardShortcutsReceiver receiver, int deviceId) {
1252        mWm.requestAppKeyboardShortcuts(receiver, deviceId);
1253    }
1254
1255    public void getStableInsets(Rect outStableInsets) {
1256        if (mWm == null) return;
1257
1258        try {
1259            WindowManagerGlobal.getWindowManagerService().getStableInsets(Display.DEFAULT_DISPLAY,
1260                    outStableInsets);
1261        } catch (Exception e) {
1262            e.printStackTrace();
1263        }
1264    }
1265
1266    public void overridePendingAppTransitionMultiThumbFuture(
1267            IAppTransitionAnimationSpecsFuture future, IRemoteCallback animStartedListener,
1268            boolean scaleUp) {
1269        try {
1270            WindowManagerGlobal.getWindowManagerService()
1271                    .overridePendingAppTransitionMultiThumbFuture(future, animStartedListener,
1272                            scaleUp);
1273        } catch (RemoteException e) {
1274            Log.w(TAG, "Failed to override transition: " + e);
1275        }
1276    }
1277
1278    /**
1279     * Updates the visibility of recents.
1280     */
1281    public void setRecentsVisibility(boolean visible) {
1282        try {
1283            mIwm.setRecentsVisibility(visible);
1284        } catch (RemoteException e) {
1285            Log.e(TAG, "Unable to reach window manager", e);
1286        }
1287    }
1288
1289    /**
1290     * Updates the visibility of the picture-in-picture.
1291     */
1292    public void setPipVisibility(boolean visible) {
1293        try {
1294            mIwm.setPipVisibility(visible);
1295        } catch (RemoteException e) {
1296            Log.e(TAG, "Unable to reach window manager", e);
1297        }
1298    }
1299
1300    public boolean isDreaming() {
1301        try {
1302            return mDreamManager.isDreaming();
1303        } catch (RemoteException e) {
1304            Log.e(TAG, "Failed to query dream manager.", e);
1305        }
1306        return false;
1307    }
1308
1309    public void awakenDreamsAsync() {
1310        mUiOffloadThread.submit(() -> {
1311            try {
1312                mDreamManager.awaken();
1313            } catch (RemoteException e) {
1314                e.printStackTrace();
1315            }
1316        });
1317    }
1318
1319    public void updateOverviewLastStackActiveTimeAsync(long newLastStackActiveTime,
1320            int currentUserId) {
1321        mUiOffloadThread.submit(() -> {
1322            Settings.Secure.putLongForUser(mContext.getContentResolver(),
1323                    Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, newLastStackActiveTime, currentUserId);
1324        });
1325    }
1326
1327    public interface StartActivityFromRecentsResultListener {
1328        void onStartActivityResult(boolean succeeded);
1329    }
1330
1331    private final class H extends Handler {
1332        private static final int ON_TASK_STACK_CHANGED = 1;
1333        private static final int ON_TASK_SNAPSHOT_CHANGED = 2;
1334        private static final int ON_ACTIVITY_PINNED = 3;
1335        private static final int ON_PINNED_ACTIVITY_RESTART_ATTEMPT = 4;
1336        private static final int ON_PINNED_STACK_ANIMATION_ENDED = 5;
1337        private static final int ON_ACTIVITY_FORCED_RESIZABLE = 6;
1338        private static final int ON_ACTIVITY_DISMISSING_DOCKED_STACK = 7;
1339        private static final int ON_TASK_PROFILE_LOCKED = 8;
1340        private static final int ON_PINNED_STACK_ANIMATION_STARTED = 9;
1341        private static final int ON_ACTIVITY_UNPINNED = 10;
1342        private static final int ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED = 11;
1343
1344        @Override
1345        public void handleMessage(Message msg) {
1346            synchronized (mTaskStackListeners) {
1347                switch (msg.what) {
1348                    case ON_TASK_STACK_CHANGED: {
1349                    Trace.beginSection("onTaskStackChanged");
1350                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
1351                            mTaskStackListeners.get(i).onTaskStackChanged();
1352                        }
1353                    Trace.endSection();
1354                        break;
1355                    }
1356                    case ON_TASK_SNAPSHOT_CHANGED: {
1357                    Trace.beginSection("onTaskSnapshotChanged");
1358                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
1359                            mTaskStackListeners.get(i).onTaskSnapshotChanged(msg.arg1,
1360                                    (TaskSnapshot) msg.obj);
1361                        }
1362                    Trace.endSection();
1363                        break;
1364                    }
1365                    case ON_ACTIVITY_PINNED: {
1366                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
1367                            mTaskStackListeners.get(i).onActivityPinned((String) msg.obj, msg.arg1);
1368                        }
1369                        break;
1370                    }
1371                    case ON_ACTIVITY_UNPINNED: {
1372                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
1373                            mTaskStackListeners.get(i).onActivityUnpinned();
1374                        }
1375                        break;
1376                    }
1377                    case ON_PINNED_ACTIVITY_RESTART_ATTEMPT: {
1378                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
1379                            mTaskStackListeners.get(i).onPinnedActivityRestartAttempt(
1380                                    msg.arg1 != 0);
1381                        }
1382                        break;
1383                    }
1384                    case ON_PINNED_STACK_ANIMATION_STARTED: {
1385                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
1386                            mTaskStackListeners.get(i).onPinnedStackAnimationStarted();
1387                        }
1388                        break;
1389                    }
1390                    case ON_PINNED_STACK_ANIMATION_ENDED: {
1391                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
1392                            mTaskStackListeners.get(i).onPinnedStackAnimationEnded();
1393                        }
1394                        break;
1395                    }
1396                    case ON_ACTIVITY_FORCED_RESIZABLE: {
1397                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
1398                            mTaskStackListeners.get(i).onActivityForcedResizable(
1399                                    (String) msg.obj, msg.arg1, msg.arg2);
1400                        }
1401                        break;
1402                    }
1403                    case ON_ACTIVITY_DISMISSING_DOCKED_STACK: {
1404                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
1405                            mTaskStackListeners.get(i).onActivityDismissingDockedStack();
1406                        }
1407                        break;
1408                    }
1409                    case ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED: {
1410                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
1411                            mTaskStackListeners.get(i).onActivityLaunchOnSecondaryDisplayFailed();
1412                        }
1413                        break;
1414                    }
1415                    case ON_TASK_PROFILE_LOCKED: {
1416                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
1417                            mTaskStackListeners.get(i).onTaskProfileLocked(msg.arg1, msg.arg2);
1418                        }
1419                        break;
1420                    }
1421                }
1422            }
1423        }
1424    }
1425}
1426