PipManager.java revision baa7b721dc8ee22f8e5f8c373fe6ab1630446f0f
1/*
2 * Copyright (C) 2016 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.pip.tv;
18
19import android.app.ActivityManager;
20import android.app.ActivityManager.RunningTaskInfo;
21import android.app.ActivityManager.StackInfo;
22import android.app.IActivityManager;
23import android.content.BroadcastReceiver;
24import android.content.ComponentName;
25import android.content.Context;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.content.pm.ParceledListSlice;
29import android.content.res.Resources;
30import android.graphics.Rect;
31import android.media.session.MediaController;
32import android.media.session.MediaSessionManager;
33import android.media.session.PlaybackState;
34import android.os.Debug;
35import android.os.Handler;
36import android.os.RemoteException;
37import android.os.SystemProperties;
38import android.os.UserHandle;
39import android.text.TextUtils;
40import android.util.Log;
41import android.util.Pair;
42import android.view.Display;
43import android.view.IPinnedStackController;
44import android.view.IPinnedStackListener;
45import android.view.IWindowManager;
46import android.view.WindowManagerGlobal;
47
48import com.android.systemui.Prefs;
49import com.android.systemui.R;
50import com.android.systemui.pip.BasePipManager;
51import com.android.systemui.recents.misc.SystemServicesProxy;
52import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
53
54import java.io.PrintWriter;
55import java.util.ArrayList;
56import java.util.List;
57
58import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
59import static android.view.Display.DEFAULT_DISPLAY;
60
61import static com.android.systemui.Prefs.Key.TV_PICTURE_IN_PICTURE_ONBOARDING_SHOWN;
62
63/**
64 * Manages the picture-in-picture (PIP) UI and states.
65 */
66public class PipManager implements BasePipManager {
67    private static final String TAG = "PipManager";
68    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
69    private static final boolean DEBUG_FORCE_ONBOARDING =
70            SystemProperties.getBoolean("debug.tv.pip_force_onboarding", false);
71    private static final String SETTINGS_PACKAGE_AND_CLASS_DELIMITER = "/";
72
73    private static PipManager sPipManager;
74    private static List<Pair<String, String>> sSettingsPackageAndClassNamePairList;
75
76    /**
77     * State when there's no PIP.
78     */
79    public static final int STATE_NO_PIP = 0;
80    /**
81     * State when PIP is shown with an overlay message on top of it.
82     * This is used as default PIP state.
83     */
84    public static final int STATE_PIP_OVERLAY = 1;
85    /**
86     * State when PIP menu dialog is shown.
87     */
88    public static final int STATE_PIP_MENU = 2;
89    /**
90     * State when PIP is shown in Recents.
91     */
92    public static final int STATE_PIP_RECENTS = 3;
93    /**
94     * State when PIP is shown in Recents and it's focused to allow an user to control.
95     */
96    public static final int STATE_PIP_RECENTS_FOCUSED = 4;
97
98    private static final int TASK_ID_NO_PIP = -1;
99    private static final int INVALID_RESOURCE_TYPE = -1;
100
101    public static final int SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH = 0x1;
102    public static final int SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_OVERLAY_ACTIVITY_FINISH = 0x2;
103
104    /**
105     * PIPed activity is playing a media and it can be paused.
106     */
107    static final int PLAYBACK_STATE_PLAYING = 0;
108    /**
109     * PIPed activity has a paused media and it can be played.
110     */
111    static final int PLAYBACK_STATE_PAUSED = 1;
112    /**
113     * Users are unable to control PIPed activity's media playback.
114     */
115    static final int PLAYBACK_STATE_UNAVAILABLE = 2;
116
117    private static final int CLOSE_PIP_WHEN_MEDIA_SESSION_GONE_TIMEOUT_MS = 3000;
118
119    private int mSuspendPipResizingReason;
120
121    private Context mContext;
122    private PipRecentsOverlayManager mPipRecentsOverlayManager;
123    private IActivityManager mActivityManager;
124    private IWindowManager mWindowManager;
125    private MediaSessionManager mMediaSessionManager;
126    private int mState = STATE_NO_PIP;
127    private final Handler mHandler = new Handler();
128    private List<Listener> mListeners = new ArrayList<>();
129    private List<MediaListener> mMediaListeners = new ArrayList<>();
130    private Rect mCurrentPipBounds;
131    private Rect mPipBounds;
132    private Rect mDefaultPipBounds = new Rect();
133    private Rect mSettingsPipBounds;
134    private Rect mMenuModePipBounds;
135    private Rect mRecentsPipBounds;
136    private Rect mRecentsFocusedPipBounds;
137    private int mRecentsFocusChangedAnimationDurationMs;
138    private boolean mInitialized;
139    private int mPipTaskId = TASK_ID_NO_PIP;
140    private ComponentName mPipComponentName;
141    private MediaController mPipMediaController;
142    private boolean mOnboardingShown;
143    private String[] mLastPackagesResourceGranted;
144
145    private final PinnedStackListener mPinnedStackListener = new PinnedStackListener();
146
147    private final Runnable mResizePinnedStackRunnable = new Runnable() {
148        @Override
149        public void run() {
150            resizePinnedStack(mState);
151        }
152    };
153    private final Runnable mClosePipRunnable = new Runnable() {
154        @Override
155        public void run() {
156            closePip();
157        }
158    };
159
160    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
161        @Override
162        public void onReceive(Context context, Intent intent) {
163            String action = intent.getAction();
164            if (Intent.ACTION_MEDIA_RESOURCE_GRANTED.equals(action)) {
165                String[] packageNames = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
166                int resourceType = intent.getIntExtra(Intent.EXTRA_MEDIA_RESOURCE_TYPE,
167                        INVALID_RESOURCE_TYPE);
168                if (packageNames != null && packageNames.length > 0
169                        && resourceType == Intent.EXTRA_MEDIA_RESOURCE_TYPE_VIDEO_CODEC) {
170                    handleMediaResourceGranted(packageNames);
171                }
172            }
173
174        }
175    };
176    private final MediaSessionManager.OnActiveSessionsChangedListener mActiveMediaSessionListener =
177            new MediaSessionManager.OnActiveSessionsChangedListener() {
178                @Override
179                public void onActiveSessionsChanged(List<MediaController> controllers) {
180                    updateMediaController(controllers);
181                }
182            };
183
184    /**
185     * Handler for messages from the PIP controller.
186     */
187    private class PinnedStackListener extends IPinnedStackListener.Stub {
188
189        @Override
190        public void onListenerRegistered(IPinnedStackController controller) {}
191
192        @Override
193        public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {}
194
195        @Override
196        public void onMinimizedStateChanged(boolean isMinimized) {}
197
198        @Override
199        public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
200                Rect animatingBounds, boolean fromImeAdjustement) {
201            mHandler.post(() -> {
202                mDefaultPipBounds.set(normalBounds);
203            });
204        }
205
206        @Override
207        public void onActionsChanged(ParceledListSlice actions) {}
208    }
209
210    private PipManager() { }
211
212    /**
213     * Initializes {@link PipManager}.
214     */
215    public void initialize(Context context) {
216        if (mInitialized) {
217            return;
218        }
219        mInitialized = true;
220        mContext = context;
221
222        mActivityManager = ActivityManager.getService();
223        mWindowManager = WindowManagerGlobal.getWindowManagerService();
224        SystemServicesProxy.getInstance(context).registerTaskStackListener(mTaskStackListener);
225        IntentFilter intentFilter = new IntentFilter();
226        intentFilter.addAction(Intent.ACTION_MEDIA_RESOURCE_GRANTED);
227        mContext.registerReceiver(mBroadcastReceiver, intentFilter);
228        mOnboardingShown = Prefs.getBoolean(
229                mContext, TV_PICTURE_IN_PICTURE_ONBOARDING_SHOWN, false);
230
231        if (sSettingsPackageAndClassNamePairList == null) {
232            String[] settings = mContext.getResources().getStringArray(
233                    R.array.tv_pip_settings_class_name);
234            sSettingsPackageAndClassNamePairList = new ArrayList<>();
235            if (settings != null) {
236                for (int i = 0; i < settings.length; i++) {
237                    Pair<String, String> entry = null;
238                    String[] packageAndClassName =
239                            settings[i].split(SETTINGS_PACKAGE_AND_CLASS_DELIMITER);
240                    switch (packageAndClassName.length) {
241                        case 1:
242                            entry = Pair.<String, String>create(packageAndClassName[0], null);
243                            break;
244                        case 2:
245                            if (packageAndClassName[1] != null
246                                    && packageAndClassName[1].startsWith(".")) {
247                                entry = Pair.<String, String>create(
248                                        packageAndClassName[0],
249                                        packageAndClassName[0] + packageAndClassName[1]);
250                            }
251                    }
252                    if (entry != null) {
253                        sSettingsPackageAndClassNamePairList.add(entry);
254                    } else {
255                        Log.w(TAG, "Ignoring malformed settings name " + settings[i]);
256                    }
257                }
258            }
259        }
260
261        loadConfigurationsAndApply();
262        mPipRecentsOverlayManager = new PipRecentsOverlayManager(context);
263        mMediaSessionManager =
264                (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
265
266        try {
267            mWindowManager.registerPinnedStackListener(DEFAULT_DISPLAY, mPinnedStackListener);
268        } catch (RemoteException e) {
269            Log.e(TAG, "Failed to register pinned stack listener", e);
270        }
271    }
272
273    private void loadConfigurationsAndApply() {
274        Resources res = mContext.getResources();
275        mSettingsPipBounds = Rect.unflattenFromString(res.getString(
276                R.string.pip_settings_bounds));
277        mMenuModePipBounds = Rect.unflattenFromString(res.getString(
278                R.string.pip_menu_bounds));
279        mRecentsPipBounds = Rect.unflattenFromString(res.getString(
280                R.string.pip_recents_bounds));
281        mRecentsFocusedPipBounds = Rect.unflattenFromString(res.getString(
282                R.string.pip_recents_focused_bounds));
283        mRecentsFocusChangedAnimationDurationMs = res.getInteger(
284                R.integer.recents_tv_pip_focus_anim_duration);
285
286        // Reset the PIP bounds and apply. PIP bounds can be changed by two reasons.
287        //   1. Configuration changed due to the language change (RTL <-> RTL)
288        //   2. SystemUI restarts after the crash
289        mPipBounds = isSettingsShown() ? mSettingsPipBounds : mDefaultPipBounds;
290        resizePinnedStack(getPinnedStackInfo() == null ? STATE_NO_PIP : STATE_PIP_OVERLAY);
291    }
292
293    /**
294     * Updates the PIP per configuration changed.
295     */
296    public void onConfigurationChanged() {
297        loadConfigurationsAndApply();
298        mPipRecentsOverlayManager.onConfigurationChanged(mContext);
299    }
300
301    /**
302     * Shows the picture-in-picture menu if an activity is in picture-in-picture mode.
303     */
304    public void showTvPictureInPictureMenu() {
305        if (mState == STATE_PIP_OVERLAY) {
306            resizePinnedStack(STATE_PIP_MENU);
307        }
308    }
309
310    /**
311     * Closes PIP (PIPed activity and PIP system UI).
312     */
313    public void closePip() {
314        closePipInternal(true);
315    }
316
317    private void closePipInternal(boolean removePipStack) {
318        mState = STATE_NO_PIP;
319        mPipTaskId = TASK_ID_NO_PIP;
320        mPipMediaController = null;
321        mMediaSessionManager.removeOnActiveSessionsChangedListener(mActiveMediaSessionListener);
322        if (removePipStack) {
323            try {
324                mActivityManager.removeStack(PINNED_STACK_ID);
325            } catch (RemoteException e) {
326                Log.e(TAG, "removeStack failed", e);
327            }
328        }
329        for (int i = mListeners.size() - 1; i >= 0; --i) {
330            mListeners.get(i).onPipActivityClosed();
331        }
332        mHandler.removeCallbacks(mClosePipRunnable);
333        updatePipVisibility(false);
334    }
335
336    /**
337     * Moves the PIPed activity to the fullscreen and closes PIP system UI.
338     */
339    void movePipToFullscreen() {
340        mPipTaskId = TASK_ID_NO_PIP;
341        for (int i = mListeners.size() - 1; i >= 0; --i) {
342            mListeners.get(i).onMoveToFullscreen();
343        }
344        resizePinnedStack(STATE_NO_PIP);
345        updatePipVisibility(false);
346    }
347
348    /**
349     * Shows PIP overlay UI by launching {@link PipOverlayActivity}. It also locates the pinned
350     * stack to the default PIP bound {@link com.android.internal.R.string
351     * .config_defaultPictureInPictureBounds}.
352     */
353    private void showPipOverlay() {
354        if (DEBUG) Log.d(TAG, "showPipOverlay()");
355        PipOverlayActivity.showPipOverlay(mContext);
356    }
357
358    /**
359     * Suspends resizing operation on the Pip until {@link #resumePipResizing} is called
360     * @param reason The reason for suspending resizing operations on the Pip.
361     */
362    public void suspendPipResizing(int reason) {
363        if (DEBUG) Log.d(TAG,
364                "suspendPipResizing() reason=" + reason + " callers=" + Debug.getCallers(2));
365        mSuspendPipResizingReason |= reason;
366    }
367
368    /**
369     * Resumes resizing operation on the Pip that was previously suspended.
370     * @param reason The reason resizing operations on the Pip was suspended.
371     */
372    public void resumePipResizing(int reason) {
373        if ((mSuspendPipResizingReason & reason) == 0) {
374            return;
375        }
376        if (DEBUG) Log.d(TAG,
377                "resumePipResizing() reason=" + reason + " callers=" + Debug.getCallers(2));
378        mSuspendPipResizingReason &= ~reason;
379        mHandler.post(mResizePinnedStackRunnable);
380    }
381
382    /**
383     * Resize the Pip to the appropriate size for the input state.
384     * @param state In Pip state also used to determine the new size for the Pip.
385     */
386    void resizePinnedStack(int state) {
387        if (DEBUG) Log.d(TAG, "resizePinnedStack() state=" + state);
388        boolean wasRecentsShown =
389                (mState == STATE_PIP_RECENTS || mState == STATE_PIP_RECENTS_FOCUSED);
390        boolean wasStateNoPip = (mState == STATE_NO_PIP);
391        mState = state;
392        for (int i = mListeners.size() - 1; i >= 0; --i) {
393            mListeners.get(i).onPipResizeAboutToStart();
394        }
395        if (mSuspendPipResizingReason != 0) {
396            if (DEBUG) Log.d(TAG,
397                    "resizePinnedStack() deferring mSuspendPipResizingReason=" +
398                            mSuspendPipResizingReason);
399            return;
400        }
401        switch (mState) {
402            case STATE_NO_PIP:
403                mCurrentPipBounds = null;
404                // If the state was already STATE_NO_PIP, then do not resize the stack below as it
405                // will not exist
406                if (wasStateNoPip) {
407                    return;
408                }
409                break;
410            case STATE_PIP_MENU:
411                mCurrentPipBounds = mMenuModePipBounds;
412                break;
413            case STATE_PIP_OVERLAY:
414                mCurrentPipBounds = mPipBounds;
415                break;
416            case STATE_PIP_RECENTS:
417                mCurrentPipBounds = mRecentsPipBounds;
418                break;
419            case STATE_PIP_RECENTS_FOCUSED:
420                mCurrentPipBounds = mRecentsFocusedPipBounds;
421                break;
422            default:
423                mCurrentPipBounds = mPipBounds;
424                break;
425        }
426        try {
427            int animationDurationMs = -1;
428            if (wasRecentsShown
429                    && (mState == STATE_PIP_RECENTS || mState == STATE_PIP_RECENTS_FOCUSED)) {
430                animationDurationMs = mRecentsFocusChangedAnimationDurationMs;
431            }
432            mActivityManager.resizeStack(PINNED_STACK_ID, mCurrentPipBounds,
433                    true, true, true, animationDurationMs);
434        } catch (RemoteException e) {
435            Log.e(TAG, "resizeStack failed", e);
436        }
437    }
438
439    /**
440     * Returns the default PIP bound.
441     */
442    public Rect getPipBounds() {
443        return mPipBounds;
444    }
445
446    /**
447     * Returns the focused PIP bound while Recents is shown.
448     * This is used to place PIP controls in Recents.
449     */
450    public Rect getRecentsFocusedPipBounds() {
451        return mRecentsFocusedPipBounds;
452    }
453
454    /**
455     * Shows PIP menu UI by launching {@link PipMenuActivity}. It also locates the pinned
456     * stack to the centered PIP bound {@link R.config_centeredPictureInPictureBounds}.
457     */
458    private void showPipMenu() {
459        if (DEBUG) Log.d(TAG, "showPipMenu()");
460        if (mPipRecentsOverlayManager.isRecentsShown()) {
461            if (DEBUG) Log.d(TAG, "Ignore showing PIP menu");
462            return;
463        }
464        mState = STATE_PIP_MENU;
465        for (int i = mListeners.size() - 1; i >= 0; --i) {
466            mListeners.get(i).onShowPipMenu();
467        }
468        Intent intent = new Intent(mContext, PipMenuActivity.class);
469        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
470        mContext.startActivity(intent);
471    }
472
473    /**
474     * Adds a {@link Listener} to PipManager.
475     */
476    public void addListener(Listener listener) {
477        mListeners.add(listener);
478    }
479
480    /**
481     * Removes a {@link Listener} from PipManager.
482     */
483    public void removeListener(Listener listener) {
484        mListeners.remove(listener);
485    }
486
487    /**
488     * Adds a {@link MediaListener} to PipManager.
489     */
490    public void addMediaListener(MediaListener listener) {
491        mMediaListeners.add(listener);
492    }
493
494    /**
495     * Removes a {@link MediaListener} from PipManager.
496     */
497    public void removeMediaListener(MediaListener listener) {
498        mMediaListeners.remove(listener);
499    }
500
501    private void launchPipOnboardingActivityIfNeeded() {
502        if (DEBUG_FORCE_ONBOARDING || !mOnboardingShown) {
503            mOnboardingShown = true;
504            Prefs.putBoolean(mContext, TV_PICTURE_IN_PICTURE_ONBOARDING_SHOWN, true);
505
506            Intent intent = new Intent(mContext, PipOnboardingActivity.class);
507            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
508            mContext.startActivity(intent);
509        }
510    }
511
512    /**
513     * Returns {@code true} if PIP is shown.
514     */
515    public boolean isPipShown() {
516        return mState != STATE_NO_PIP;
517    }
518
519    private StackInfo getPinnedStackInfo() {
520        StackInfo stackInfo = null;
521        try {
522            stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
523        } catch (RemoteException e) {
524            Log.e(TAG, "getStackInfo failed", e);
525        }
526        return stackInfo;
527    }
528
529    private void handleMediaResourceGranted(String[] packageNames) {
530        if (mState == STATE_NO_PIP) {
531            mLastPackagesResourceGranted = packageNames;
532        } else {
533            boolean requestedFromLastPackages = false;
534            if (mLastPackagesResourceGranted != null) {
535                for (String packageName : mLastPackagesResourceGranted) {
536                    for (String newPackageName : packageNames) {
537                        if (TextUtils.equals(newPackageName, packageName)) {
538                            requestedFromLastPackages = true;
539                            break;
540                        }
541                    }
542                }
543            }
544            mLastPackagesResourceGranted = packageNames;
545            if (!requestedFromLastPackages) {
546                closePip();
547            }
548        }
549    }
550
551    private void updateMediaController(List<MediaController> controllers) {
552        MediaController mediaController = null;
553        if (controllers != null && mState != STATE_NO_PIP && mPipComponentName != null) {
554            for (int i = controllers.size() - 1; i >= 0; i--) {
555                MediaController controller = controllers.get(i);
556                // We assumes that an app with PIPable activity
557                // keeps the single instance of media controller especially when PIP is on.
558                if (controller.getPackageName().equals(mPipComponentName.getPackageName())) {
559                    mediaController = controller;
560                    break;
561                }
562            }
563        }
564        if (mPipMediaController != mediaController) {
565            mPipMediaController = mediaController;
566            for (int i = mMediaListeners.size() - 1; i >= 0; i--) {
567                mMediaListeners.get(i).onMediaControllerChanged();
568            }
569            if (mPipMediaController == null) {
570                mHandler.postDelayed(mClosePipRunnable,
571                        CLOSE_PIP_WHEN_MEDIA_SESSION_GONE_TIMEOUT_MS);
572            } else {
573                mHandler.removeCallbacks(mClosePipRunnable);
574            }
575        }
576    }
577
578    /**
579     * Gets the {@link android.media.session.MediaController} for the PIPed activity.
580     */
581    MediaController getMediaController() {
582        return mPipMediaController;
583    }
584
585    /**
586     * Returns the PIPed activity's playback state.
587     * This returns one of {@link PLAYBACK_STATE_PLAYING}, {@link PLAYBACK_STATE_PAUSED},
588     * or {@link PLAYBACK_STATE_UNAVAILABLE}.
589     */
590    int getPlaybackState() {
591        if (mPipMediaController == null || mPipMediaController.getPlaybackState() == null) {
592            return PLAYBACK_STATE_UNAVAILABLE;
593        }
594        int state = mPipMediaController.getPlaybackState().getState();
595        boolean isPlaying = (state == PlaybackState.STATE_BUFFERING
596                || state == PlaybackState.STATE_CONNECTING
597                || state == PlaybackState.STATE_PLAYING
598                || state == PlaybackState.STATE_FAST_FORWARDING
599                || state == PlaybackState.STATE_REWINDING
600                || state == PlaybackState.STATE_SKIPPING_TO_PREVIOUS
601                || state == PlaybackState.STATE_SKIPPING_TO_NEXT);
602        long actions = mPipMediaController.getPlaybackState().getActions();
603        if (!isPlaying && ((actions & PlaybackState.ACTION_PLAY) != 0)) {
604            return PLAYBACK_STATE_PAUSED;
605        } else if (isPlaying && ((actions & PlaybackState.ACTION_PAUSE) != 0)) {
606            return PLAYBACK_STATE_PLAYING;
607        }
608        return PLAYBACK_STATE_UNAVAILABLE;
609    }
610
611    private boolean isSettingsShown() {
612        List<RunningTaskInfo> runningTasks;
613        try {
614            runningTasks = mActivityManager.getTasks(1, 0);
615            if (runningTasks == null || runningTasks.size() == 0) {
616                return false;
617            }
618        } catch (RemoteException e) {
619            Log.d(TAG, "Failed to detect top activity", e);
620            return false;
621        }
622        ComponentName topActivity = runningTasks.get(0).topActivity;
623        for (Pair<String, String> componentName : sSettingsPackageAndClassNamePairList) {
624            String packageName = componentName.first;
625            if (topActivity.getPackageName().equals(packageName)) {
626                String className = componentName.second;
627                if (className == null || topActivity.getClassName().equals(className)) {
628                    return true;
629                }
630            }
631        }
632        return false;
633    }
634
635    private TaskStackListener mTaskStackListener = new TaskStackListener() {
636        @Override
637        public void onTaskStackChanged() {
638            if (DEBUG) Log.d(TAG, "onTaskStackChanged()");
639            if (!checkCurrentUserId(DEBUG)) {
640                return;
641            }
642            if (mState != STATE_NO_PIP) {
643                boolean hasPip = false;
644
645                StackInfo stackInfo = getPinnedStackInfo();
646                if (stackInfo == null || stackInfo.taskIds == null) {
647                    Log.w(TAG, "There is nothing in pinned stack");
648                    closePipInternal(false);
649                    return;
650                }
651                for (int i = stackInfo.taskIds.length - 1; i >= 0; --i) {
652                    if (stackInfo.taskIds[i] == mPipTaskId) {
653                        // PIP task is still alive.
654                        hasPip = true;
655                        break;
656                    }
657                }
658                if (!hasPip) {
659                    // PIP task doesn't exist anymore in PINNED_STACK.
660                    closePipInternal(true);
661                    return;
662                }
663            }
664            if (mState == STATE_PIP_OVERLAY) {
665                Rect bounds = isSettingsShown() ? mSettingsPipBounds : mDefaultPipBounds;
666                if (mPipBounds != bounds) {
667                    mPipBounds = bounds;
668                    resizePinnedStack(STATE_PIP_OVERLAY);
669                }
670            }
671        }
672
673        @Override
674        public void onActivityPinned() {
675            if (DEBUG) Log.d(TAG, "onActivityPinned()");
676            if (!checkCurrentUserId(DEBUG)) {
677                return;
678            }
679            StackInfo stackInfo = getPinnedStackInfo();
680            if (stackInfo == null) {
681                Log.w(TAG, "Cannot find pinned stack");
682                return;
683            }
684            if (DEBUG) Log.d(TAG, "PINNED_STACK:" + stackInfo);
685            mPipTaskId = stackInfo.taskIds[stackInfo.taskIds.length - 1];
686            mPipComponentName = ComponentName.unflattenFromString(
687                    stackInfo.taskNames[stackInfo.taskNames.length - 1]);
688            // Set state to overlay so we show it when the pinned stack animation ends.
689            mState = STATE_PIP_OVERLAY;
690            mCurrentPipBounds = mPipBounds;
691            launchPipOnboardingActivityIfNeeded();
692            mMediaSessionManager.addOnActiveSessionsChangedListener(
693                    mActiveMediaSessionListener, null);
694            updateMediaController(mMediaSessionManager.getActiveSessions(null));
695            if (mPipRecentsOverlayManager.isRecentsShown()) {
696                // If an activity becomes PIPed again after the fullscreen, the Recents is shown
697                // behind so we need to resize the pinned stack and show the correct overlay.
698                resizePinnedStack(STATE_PIP_RECENTS);
699            }
700            for (int i = mListeners.size() - 1; i >= 0; i--) {
701                mListeners.get(i).onPipEntered();
702            }
703            updatePipVisibility(true);
704        }
705
706        @Override
707        public void onPinnedActivityRestartAttempt() {
708            if (DEBUG) Log.d(TAG, "onPinnedActivityRestartAttempt()");
709            if (!checkCurrentUserId(DEBUG)) {
710                return;
711            }
712            // If PIPed activity is launched again by Launcher or intent, make it fullscreen.
713            movePipToFullscreen();
714        }
715
716        @Override
717        public void onPinnedStackAnimationEnded() {
718            if (DEBUG) Log.d(TAG, "onPinnedStackAnimationEnded()");
719            if (!checkCurrentUserId(DEBUG)) {
720                return;
721            }
722            switch (mState) {
723                case STATE_PIP_OVERLAY:
724                    if (!mPipRecentsOverlayManager.isRecentsShown()) {
725                        showPipOverlay();
726                        break;
727                    } else {
728                        // This happens only if an activity is PIPed after the Recents is shown.
729                        // See {@link PipRecentsOverlayManager.requestFocus} for more details.
730                        resizePinnedStack(mState);
731                        break;
732                    }
733                case STATE_PIP_RECENTS:
734                case STATE_PIP_RECENTS_FOCUSED:
735                    mPipRecentsOverlayManager.addPipRecentsOverlayView();
736                    break;
737                case STATE_PIP_MENU:
738                    showPipMenu();
739                    break;
740            }
741        }
742    };
743
744    /**
745     * A listener interface to receive notification on changes in PIP.
746     */
747    public interface Listener {
748        /**
749         * Invoked when an activity is pinned and PIP manager is set corresponding information.
750         * Classes must use this instead of {@link android.app.ITaskStackListener.onActivityPinned}
751         * because there's no guarantee for the PIP manager be return relavent information
752         * correctly. (e.g. {@link isPipShown}).
753         */
754        void onPipEntered();
755        /** Invoked when a PIPed activity is closed. */
756        void onPipActivityClosed();
757        /** Invoked when the PIP menu gets shown. */
758        void onShowPipMenu();
759        /** Invoked when the PIPed activity is about to return back to the fullscreen. */
760        void onMoveToFullscreen();
761        /** Invoked when we are above to start resizing the Pip. */
762        void onPipResizeAboutToStart();
763    }
764
765    /**
766     * A listener interface to receive change in PIP's media controller
767     */
768    public interface MediaListener {
769        /** Invoked when the MediaController on PIPed activity is changed. */
770        void onMediaControllerChanged();
771    }
772
773    /**
774     * Gets an instance of {@link PipManager}.
775     */
776    public static PipManager getInstance() {
777        if (sPipManager == null) {
778            sPipManager = new PipManager();
779        }
780        return sPipManager;
781    }
782
783    /**
784     * Gets an instance of {@link PipRecentsOverlayManager}.
785     */
786    public PipRecentsOverlayManager getPipRecentsOverlayManager() {
787        return mPipRecentsOverlayManager;
788    }
789
790    private void updatePipVisibility(final boolean visible) {
791        SystemServicesProxy.getInstance(mContext).setTvPipVisibility(visible);
792    }
793
794    @Override
795    public void dump(PrintWriter pw) {
796        // Do nothing
797    }
798}
799