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