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<>();
sSettingsPackageAndClassNamePairList.add(new Pair<String, String>( "com.android.tv.settings", null))76         sSettingsPackageAndClassNamePairList.add(new Pair<String, String>(
77                 "com.android.tv.settings", null));
sSettingsPackageAndClassNamePairList.add(new Pair<String, String>( "com.google.android.leanbacklauncher", "com.google.android.leanbacklauncher.settings.HomeScreenSettingsActivity"))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 
PipManager()188     private PipManager() { }
189 
190     /**
191      * Initializes {@link PipManager}.
192      */
initialize(Context context)193     public void initialize(Context context) {
194         if (mInitialized) {
195             return;
196         }
197         mInitialized = true;
198         mContext = context;
199         Resources res = context.getResources();
200         mDefaultPipBounds = Rect.unflattenFromString(res.getString(
201                 com.android.internal.R.string.config_defaultPictureInPictureBounds));
202         mSettingsPipBounds = Rect.unflattenFromString(res.getString(
203                 R.string.pip_settings_bounds));
204         mMenuModePipBounds = Rect.unflattenFromString(res.getString(
205                 R.string.pip_menu_bounds));
206         mRecentsPipBounds = Rect.unflattenFromString(res.getString(
207                 R.string.pip_recents_bounds));
208         mRecentsFocusedPipBounds = Rect.unflattenFromString(res.getString(
209                 R.string.pip_recents_focused_bounds));
210         mRecentsFocusChangedAnimationDurationMs = res.getInteger(
211                 R.integer.recents_tv_pip_focus_anim_duration);
212         mPipBounds = mDefaultPipBounds;
213 
214         mActivityManager = ActivityManagerNative.getDefault();
215         SystemServicesProxy.getInstance(context).registerTaskStackListener(mTaskStackListener);
216         IntentFilter intentFilter = new IntentFilter();
217         intentFilter.addAction(Intent.ACTION_MEDIA_RESOURCE_GRANTED);
218         mContext.registerReceiver(mBroadcastReceiver, intentFilter);
219         mOnboardingShown = Prefs.getBoolean(
220                 mContext, TV_PICTURE_IN_PICTURE_ONBOARDING_SHOWN, false);
221 
222         mPipRecentsOverlayManager = new PipRecentsOverlayManager(context);
223         mMediaSessionManager =
224                 (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
225     }
226 
227     /**
228      * Updates the PIP per configuration changed.
229      */
onConfigurationChanged()230     void onConfigurationChanged() {
231         mPipRecentsOverlayManager.onConfigurationChanged(mContext);
232     }
233 
234     /**
235      * Shows the picture-in-picture menu if an activity is in picture-in-picture mode.
236      */
showTvPictureInPictureMenu()237     public void showTvPictureInPictureMenu() {
238         if (mState == STATE_PIP_OVERLAY) {
239             resizePinnedStack(STATE_PIP_MENU);
240         }
241     }
242 
243     /**
244      * Closes PIP (PIPed activity and PIP system UI).
245      */
closePip()246     public void closePip() {
247         closePipInternal(true);
248     }
249 
closePipInternal(boolean removePipStack)250     private void closePipInternal(boolean removePipStack) {
251         mState = STATE_NO_PIP;
252         mPipTaskId = TASK_ID_NO_PIP;
253         mPipMediaController = null;
254         mMediaSessionManager.removeOnActiveSessionsChangedListener(mActiveMediaSessionListener);
255         if (removePipStack) {
256             try {
257                 mActivityManager.removeStack(PINNED_STACK_ID);
258             } catch (RemoteException e) {
259                 Log.e(TAG, "removeStack failed", e);
260             }
261         }
262         for (int i = mListeners.size() - 1; i >= 0; --i) {
263             mListeners.get(i).onPipActivityClosed();
264         }
265         mHandler.removeCallbacks(mClosePipRunnable);
266         updatePipVisibility(false);
267     }
268 
269     /**
270      * Moves the PIPed activity to the fullscreen and closes PIP system UI.
271      */
movePipToFullscreen()272     void movePipToFullscreen() {
273         mState = STATE_NO_PIP;
274         mPipTaskId = TASK_ID_NO_PIP;
275         for (int i = mListeners.size() - 1; i >= 0; --i) {
276             mListeners.get(i).onMoveToFullscreen();
277         }
278         resizePinnedStack(mState);
279     }
280 
281     /**
282      * Shows PIP overlay UI by launching {@link PipOverlayActivity}. It also locates the pinned
283      * stack to the default PIP bound {@link com.android.internal.R.string
284      * .config_defaultPictureInPictureBounds}.
285      */
showPipOverlay()286     private void showPipOverlay() {
287         if (DEBUG) Log.d(TAG, "showPipOverlay()");
288         PipOverlayActivity.showPipOverlay(mContext);
289     }
290 
291     /**
292      * Suspends resizing operation on the Pip until {@link #resumePipResizing} is called
293      * @param reason The reason for suspending resizing operations on the Pip.
294      */
suspendPipResizing(int reason)295     public void suspendPipResizing(int reason) {
296         if (DEBUG) Log.d(TAG,
297                 "suspendPipResizing() reason=" + reason + " callers=" + Debug.getCallers(2));
298         mSuspendPipResizingReason |= reason;
299     }
300 
301     /**
302      * Resumes resizing operation on the Pip that was previously suspended.
303      * @param reason The reason resizing operations on the Pip was suspended.
304      */
resumePipResizing(int reason)305     public void resumePipResizing(int reason) {
306         if ((mSuspendPipResizingReason & reason) == 0) {
307             return;
308         }
309         if (DEBUG) Log.d(TAG,
310                 "resumePipResizing() reason=" + reason + " callers=" + Debug.getCallers(2));
311         mSuspendPipResizingReason &= ~reason;
312         mHandler.post(mResizePinnedStackRunnable);
313     }
314 
315     /**
316      * Resize the Pip to the appropriate size for the input state.
317      * @param state In Pip state also used to determine the new size for the Pip.
318      */
resizePinnedStack(int state)319     void resizePinnedStack(int state) {
320         if (DEBUG) Log.d(TAG, "resizePinnedStack() state=" + state);
321         boolean wasRecentsShown =
322                 (mState == STATE_PIP_RECENTS || mState == STATE_PIP_RECENTS_FOCUSED);
323         mState = state;
324         for (int i = mListeners.size() - 1; i >= 0; --i) {
325             mListeners.get(i).onPipResizeAboutToStart();
326         }
327         if (mSuspendPipResizingReason != 0) {
328             if (DEBUG) Log.d(TAG,
329                     "resizePinnedStack() deferring mSuspendPipResizingReason=" +
330                             mSuspendPipResizingReason);
331             return;
332         }
333         switch (mState) {
334             case STATE_NO_PIP:
335                 mCurrentPipBounds = null;
336                 break;
337             case STATE_PIP_MENU:
338                 mCurrentPipBounds = mMenuModePipBounds;
339                 break;
340             case STATE_PIP_OVERLAY:
341                 mCurrentPipBounds = mPipBounds;
342                 break;
343             case STATE_PIP_RECENTS:
344                 mCurrentPipBounds = mRecentsPipBounds;
345                 break;
346             case STATE_PIP_RECENTS_FOCUSED:
347                 mCurrentPipBounds = mRecentsFocusedPipBounds;
348                 break;
349             default:
350                 mCurrentPipBounds = mPipBounds;
351                 break;
352         }
353         try {
354             int animationDurationMs = -1;
355             if (wasRecentsShown
356                     && (mState == STATE_PIP_RECENTS || mState == STATE_PIP_RECENTS_FOCUSED)) {
357                 animationDurationMs = mRecentsFocusChangedAnimationDurationMs;
358             }
359             mActivityManager.resizeStack(PINNED_STACK_ID, mCurrentPipBounds,
360                     true, true, true, animationDurationMs);
361         } catch (RemoteException e) {
362             Log.e(TAG, "resizeStack failed", e);
363         }
364     }
365 
366     /**
367      * Returns the default PIP bound.
368      */
getPipBounds()369     public Rect getPipBounds() {
370         return mPipBounds;
371     }
372 
373     /**
374      * Returns the focused PIP bound while Recents is shown.
375      * This is used to place PIP controls in Recents.
376      */
getRecentsFocusedPipBounds()377     public Rect getRecentsFocusedPipBounds() {
378         return mRecentsFocusedPipBounds;
379     }
380 
381     /**
382      * Shows PIP menu UI by launching {@link PipMenuActivity}. It also locates the pinned
383      * stack to the centered PIP bound {@link R.config_centeredPictureInPictureBounds}.
384      */
showPipMenu()385     private void showPipMenu() {
386         if (DEBUG) Log.d(TAG, "showPipMenu()");
387         if (mPipRecentsOverlayManager.isRecentsShown()) {
388             if (DEBUG) Log.d(TAG, "Ignore showing PIP menu");
389             return;
390         }
391         mState = STATE_PIP_MENU;
392         for (int i = mListeners.size() - 1; i >= 0; --i) {
393             mListeners.get(i).onShowPipMenu();
394         }
395         Intent intent = new Intent(mContext, PipMenuActivity.class);
396         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
397         mContext.startActivity(intent);
398     }
399 
400     /**
401      * Adds a {@link Listener} to PipManager.
402      */
addListener(Listener listener)403     public void addListener(Listener listener) {
404         mListeners.add(listener);
405     }
406 
407     /**
408      * Removes a {@link Listener} from PipManager.
409      */
removeListener(Listener listener)410     public void removeListener(Listener listener) {
411         mListeners.remove(listener);
412     }
413 
414     /**
415      * Adds a {@link MediaListener} to PipManager.
416      */
addMediaListener(MediaListener listener)417     public void addMediaListener(MediaListener listener) {
418         mMediaListeners.add(listener);
419     }
420 
421     /**
422      * Removes a {@link MediaListener} from PipManager.
423      */
removeMediaListener(MediaListener listener)424     public void removeMediaListener(MediaListener listener) {
425         mMediaListeners.remove(listener);
426     }
427 
launchPipOnboardingActivityIfNeeded()428     private void launchPipOnboardingActivityIfNeeded() {
429         if (DEBUG_FORCE_ONBOARDING || !mOnboardingShown) {
430             mOnboardingShown = true;
431             Prefs.putBoolean(mContext, TV_PICTURE_IN_PICTURE_ONBOARDING_SHOWN, true);
432 
433             Intent intent = new Intent(mContext, PipOnboardingActivity.class);
434             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
435             mContext.startActivity(intent);
436         }
437     }
438 
439     /**
440      * Returns {@code true} if PIP is shown.
441      */
isPipShown()442     public boolean isPipShown() {
443         return mState != STATE_NO_PIP;
444     }
445 
handleMediaResourceGranted(String[] packageNames)446     private void handleMediaResourceGranted(String[] packageNames) {
447         if (mState == STATE_NO_PIP) {
448             mLastPackagesResourceGranted = packageNames;
449         } else {
450             boolean requestedFromLastPackages = false;
451             if (mLastPackagesResourceGranted != null) {
452                 for (String packageName : mLastPackagesResourceGranted) {
453                     for (String newPackageName : packageNames) {
454                         if (TextUtils.equals(newPackageName, packageName)) {
455                             requestedFromLastPackages = true;
456                             break;
457                         }
458                     }
459                 }
460             }
461             mLastPackagesResourceGranted = packageNames;
462             if (!requestedFromLastPackages) {
463                 closePip();
464             }
465         }
466     }
467 
updateMediaController(List<MediaController> controllers)468     private void updateMediaController(List<MediaController> controllers) {
469         MediaController mediaController = null;
470         if (controllers != null && mState != STATE_NO_PIP && mPipComponentName != null) {
471             for (int i = controllers.size() - 1; i >= 0; i--) {
472                 MediaController controller = controllers.get(i);
473                 // We assumes that an app with PIPable activity
474                 // keeps the single instance of media controller especially when PIP is on.
475                 if (controller.getPackageName().equals(mPipComponentName.getPackageName())) {
476                     mediaController = controller;
477                     break;
478                 }
479             }
480         }
481         if (mPipMediaController != mediaController) {
482             mPipMediaController = mediaController;
483             for (int i = mMediaListeners.size() - 1; i >= 0; i--) {
484                 mMediaListeners.get(i).onMediaControllerChanged();
485             }
486             if (mPipMediaController == null) {
487                 mHandler.postDelayed(mClosePipRunnable,
488                         CLOSE_PIP_WHEN_MEDIA_SESSION_GONE_TIMEOUT_MS);
489             } else {
490                 mHandler.removeCallbacks(mClosePipRunnable);
491             }
492         }
493     }
494 
495     /**
496      * Gets the {@link android.media.session.MediaController} for the PIPed activity.
497      */
getMediaController()498     MediaController getMediaController() {
499         return mPipMediaController;
500     }
501 
502     /**
503      * Returns the PIPed activity's playback state.
504      * This returns one of {@link PLAYBACK_STATE_PLAYING}, {@link PLAYBACK_STATE_PAUSED},
505      * or {@link PLAYBACK_STATE_UNAVAILABLE}.
506      */
getPlaybackState()507     int getPlaybackState() {
508         if (mPipMediaController == null || mPipMediaController.getPlaybackState() == null) {
509             return PLAYBACK_STATE_UNAVAILABLE;
510         }
511         int state = mPipMediaController.getPlaybackState().getState();
512         boolean isPlaying = (state == PlaybackState.STATE_BUFFERING
513                 || state == PlaybackState.STATE_CONNECTING
514                 || state == PlaybackState.STATE_PLAYING
515                 || state == PlaybackState.STATE_FAST_FORWARDING
516                 || state == PlaybackState.STATE_REWINDING
517                 || state == PlaybackState.STATE_SKIPPING_TO_PREVIOUS
518                 || state == PlaybackState.STATE_SKIPPING_TO_NEXT);
519         long actions = mPipMediaController.getPlaybackState().getActions();
520         if (!isPlaying && ((actions & PlaybackState.ACTION_PLAY) != 0)) {
521             return PLAYBACK_STATE_PAUSED;
522         } else if (isPlaying && ((actions & PlaybackState.ACTION_PAUSE) != 0)) {
523             return PLAYBACK_STATE_PLAYING;
524         }
525         return PLAYBACK_STATE_UNAVAILABLE;
526     }
527 
isSettingsShown(ComponentName topActivity)528     private static boolean isSettingsShown(ComponentName topActivity) {
529         for (Pair<String, String> componentName : sSettingsPackageAndClassNamePairList) {
530             String packageName = componentName.first;
531             if (topActivity.getPackageName().equals(componentName.first)) {
532                 String className = componentName.second;
533                 if (className == null || topActivity.getClassName().equals(className)) {
534                     return true;
535                 }
536             }
537         }
538         return false;
539     }
540 
541     private TaskStackListener mTaskStackListener = new TaskStackListener() {
542         @Override
543         public void onTaskStackChanged() {
544             if (mState != STATE_NO_PIP) {
545                 boolean hasPip = false;
546 
547                 StackInfo stackInfo = null;
548                 try {
549                     stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
550                     if (stackInfo == null) {
551                         Log.w(TAG, "There is no pinned stack");
552                         closePipInternal(false);
553                         return;
554                     }
555                 } catch (RemoteException e) {
556                     Log.e(TAG, "getStackInfo failed", e);
557                     return;
558                 }
559                 for (int i = stackInfo.taskIds.length - 1; i >= 0; --i) {
560                     if (stackInfo.taskIds[i] == mPipTaskId) {
561                         // PIP task is still alive.
562                         hasPip = true;
563                         break;
564                     }
565                 }
566                 if (!hasPip) {
567                     // PIP task doesn't exist anymore in PINNED_STACK.
568                     closePipInternal(true);
569                     return;
570                 }
571             }
572             if (mState == STATE_PIP_OVERLAY) {
573                 try {
574                     List<RunningTaskInfo> runningTasks = mActivityManager.getTasks(1, 0);
575                     if (runningTasks == null || runningTasks.size() == 0) {
576                         return;
577                     }
578                     RunningTaskInfo topTask = runningTasks.get(0);
579                     Rect bounds = isSettingsShown(topTask.topActivity)
580                           ? mSettingsPipBounds : mDefaultPipBounds;
581                     if (mPipBounds != bounds) {
582                         mPipBounds = bounds;
583                         resizePinnedStack(STATE_PIP_OVERLAY);
584                     }
585                 } catch (RemoteException e) {
586                     Log.d(TAG, "Failed to detect top activity", e);
587                 }
588             }
589         }
590 
591         @Override
592         public void onActivityPinned() {
593             if (DEBUG) Log.d(TAG, "onActivityPinned()");
594             StackInfo stackInfo = null;
595             try {
596                 stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
597                 if (stackInfo == null) {
598                     Log.w(TAG, "Cannot find pinned stack");
599                     return;
600                 }
601             } catch (RemoteException e) {
602                 Log.e(TAG, "getStackInfo failed", e);
603                 return;
604             }
605             if (DEBUG) Log.d(TAG, "PINNED_STACK:" + stackInfo);
606             mPipTaskId = stackInfo.taskIds[stackInfo.taskIds.length - 1];
607             mPipComponentName = ComponentName.unflattenFromString(
608                     stackInfo.taskNames[stackInfo.taskNames.length - 1]);
609             // Set state to overlay so we show it when the pinned stack animation ends.
610             mState = STATE_PIP_OVERLAY;
611             mCurrentPipBounds = mPipBounds;
612             launchPipOnboardingActivityIfNeeded();
613             mMediaSessionManager.addOnActiveSessionsChangedListener(
614                     mActiveMediaSessionListener, null);
615             updateMediaController(mMediaSessionManager.getActiveSessions(null));
616             if (mPipRecentsOverlayManager.isRecentsShown()) {
617                 // If an activity becomes PIPed again after the fullscreen, the Recents is shown
618                 // behind so we need to resize the pinned stack and show the correct overlay.
619                 resizePinnedStack(STATE_PIP_RECENTS);
620             }
621             for (int i = mListeners.size() - 1; i >= 0; i--) {
622                 mListeners.get(i).onPipEntered();
623             }
624             updatePipVisibility(true);
625         }
626 
627         @Override
628         public void onPinnedActivityRestartAttempt() {
629             if (DEBUG) Log.d(TAG, "onPinnedActivityRestartAttempt()");
630             // If PIPed activity is launched again by Launcher or intent, make it fullscreen.
631             movePipToFullscreen();
632         }
633 
634         @Override
635         public void onPinnedStackAnimationEnded() {
636             if (DEBUG) Log.d(TAG, "onPinnedStackAnimationEnded()");
637             switch (mState) {
638                 case STATE_PIP_OVERLAY:
639                     if (!mPipRecentsOverlayManager.isRecentsShown()) {
640                         showPipOverlay();
641                         break;
642                     } else {
643                         // This happens only if an activity is PIPed after the Recents is shown.
644                         // See {@link PipRecentsOverlayManager.requestFocus} for more details.
645                         resizePinnedStack(mState);
646                         break;
647                     }
648                 case STATE_PIP_RECENTS:
649                 case STATE_PIP_RECENTS_FOCUSED:
650                     mPipRecentsOverlayManager.addPipRecentsOverlayView();
651                     break;
652                 case STATE_PIP_MENU:
653                     showPipMenu();
654                     break;
655             }
656         }
657     };
658 
659     /**
660      * A listener interface to receive notification on changes in PIP.
661      */
662     public interface Listener {
663         /**
664          * Invoked when an activity is pinned and PIP manager is set corresponding information.
665          * Classes must use this instead of {@link android.app.ITaskStackListener.onActivityPinned}
666          * because there's no guarantee for the PIP manager be return relavent information
667          * correctly. (e.g. {@link isPipShown}).
668          */
onPipEntered()669         void onPipEntered();
670         /** Invoked when a PIPed activity is closed. */
onPipActivityClosed()671         void onPipActivityClosed();
672         /** Invoked when the PIP menu gets shown. */
onShowPipMenu()673         void onShowPipMenu();
674         /** Invoked when the PIPed activity is about to return back to the fullscreen. */
onMoveToFullscreen()675         void onMoveToFullscreen();
676         /** Invoked when we are above to start resizing the Pip. */
onPipResizeAboutToStart()677         void onPipResizeAboutToStart();
678     }
679 
680     /**
681      * A listener interface to receive change in PIP's media controller
682      */
683     public interface MediaListener {
684         /** Invoked when the MediaController on PIPed activity is changed. */
onMediaControllerChanged()685         void onMediaControllerChanged();
686     }
687 
688     /**
689      * Gets an instance of {@link PipManager}.
690      */
getInstance()691     public static PipManager getInstance() {
692         if (sPipManager == null) {
693             sPipManager = new PipManager();
694         }
695         return sPipManager;
696     }
697 
698     /**
699      * Gets an instance of {@link PipRecentsOverlayManager}.
700      */
getPipRecentsOverlayManager()701     public PipRecentsOverlayManager getPipRecentsOverlayManager() {
702         return mPipRecentsOverlayManager;
703     }
704 
updatePipVisibility(boolean visible)705     private void updatePipVisibility(boolean visible) {
706         TvStatusBar statusBar = ((SystemUIApplication) mContext).getComponent(TvStatusBar.class);
707         if (statusBar != null) {
708             statusBar.updatePipVisibility(visible);
709         }
710     }
711 }
712