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