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