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