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