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