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