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