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