OSDN Git Service

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