Home | History | Annotate | Download | only in tv
      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
    175         public void onListenerRegistered(IPinnedStackController controller) {}
    176 
    177         @Override
    178         public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {}
    179 
    180         @Override
    181         public void onMinimizedStateChanged(boolean isMinimized) {}
    182 
    183         @Override
    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
    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 
    202     private PipManager() { }
    203 
    204     /**
    205      * Initializes {@link PipManager}.
    206      */
    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 
    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      */
    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      */
    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      */
    309     public void closePip() {
    310         closePipInternal(true);
    311     }
    312 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    451     public void addListener(Listener listener) {
    452         mListeners.add(listener);
    453     }
    454 
    455     /**
    456      * Removes a {@link Listener} from PipManager.
    457      */
    458     public void removeListener(Listener listener) {
    459         mListeners.remove(listener);
    460     }
    461 
    462     /**
    463      * Adds a {@link MediaListener} to PipManager.
    464      */
    465     public void addMediaListener(MediaListener listener) {
    466         mMediaListeners.add(listener);
    467     }
    468 
    469     /**
    470      * Removes a {@link MediaListener} from PipManager.
    471      */
    472     public void removeMediaListener(MediaListener listener) {
    473         mMediaListeners.remove(listener);
    474     }
    475 
    476     /**
    477      * Returns {@code true} if PIP is shown.
    478      */
    479     public boolean isPipShown() {
    480         return mState != STATE_NO_PIP;
    481     }
    482 
    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 
    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 
    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      */
    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      */
    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 
    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          */
    698         void onPipEntered();
    699         /** Invoked when a PIPed activity is closed. */
    700         void onPipActivityClosed();
    701         /** Invoked when the PIP menu gets shown. */
    702         void onShowPipMenu();
    703         /** Invoked when the PIP menu actions change. */
    704         void onPipMenuActionsChanged(ParceledListSlice actions);
    705         /** Invoked when the PIPed activity is about to return back to the fullscreen. */
    706         void onMoveToFullscreen();
    707         /** Invoked when we are above to start resizing the Pip. */
    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. */
    716         void onMediaControllerChanged();
    717     }
    718 
    719     /**
    720      * Gets an instance of {@link PipManager}.
    721      */
    722     public static PipManager getInstance() {
    723         if (sPipManager == null) {
    724             sPipManager = new PipManager();
    725         }
    726         return sPipManager;
    727     }
    728 
    729     private void updatePipVisibility(final boolean visible) {
    730         SystemServicesProxy.getInstance(mContext).setPipVisibility(visible);
    731     }
    732 
    733     @Override
    734     public void dump(PrintWriter pw) {
    735         // Do nothing
    736     }
    737 }
    738