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