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