1 /* 2 * Copyright (C) 2020 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.wm.shell.pip.phone; 18 19 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 20 import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; 21 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; 22 import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS; 23 import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS; 24 import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS; 25 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK; 26 27 import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_FULL; 28 import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_NONE; 29 30 import android.animation.Animator; 31 import android.animation.AnimatorListenerAdapter; 32 import android.animation.AnimatorSet; 33 import android.animation.ObjectAnimator; 34 import android.animation.ValueAnimator; 35 import android.annotation.IntDef; 36 import android.app.ActivityManager; 37 import android.app.PendingIntent.CanceledException; 38 import android.app.RemoteAction; 39 import android.app.WindowConfiguration; 40 import android.content.ComponentName; 41 import android.content.Context; 42 import android.content.Intent; 43 import android.graphics.Color; 44 import android.graphics.Rect; 45 import android.graphics.drawable.Drawable; 46 import android.net.Uri; 47 import android.os.Bundle; 48 import android.os.Handler; 49 import android.os.UserHandle; 50 import android.util.Log; 51 import android.util.Pair; 52 import android.util.Size; 53 import android.view.KeyEvent; 54 import android.view.LayoutInflater; 55 import android.view.MotionEvent; 56 import android.view.View; 57 import android.view.ViewGroup; 58 import android.view.accessibility.AccessibilityManager; 59 import android.view.accessibility.AccessibilityNodeInfo; 60 import android.widget.FrameLayout; 61 import android.widget.LinearLayout; 62 63 import com.android.wm.shell.R; 64 import com.android.wm.shell.animation.Interpolators; 65 import com.android.wm.shell.common.ShellExecutor; 66 import com.android.wm.shell.pip.PipUtils; 67 import com.android.wm.shell.splitscreen.SplitScreenController; 68 69 import java.lang.annotation.Retention; 70 import java.lang.annotation.RetentionPolicy; 71 import java.util.ArrayList; 72 import java.util.List; 73 import java.util.Optional; 74 75 /** 76 * Translucent window that gets started on top of a task in PIP to allow the user to control it. 77 */ 78 public class PipMenuView extends FrameLayout { 79 80 private static final String TAG = "PipMenuView"; 81 82 private static final int ANIMATION_NONE_DURATION_MS = 0; 83 private static final int ANIMATION_HIDE_DURATION_MS = 125; 84 85 /** No animation performed during menu hide. */ 86 public static final int ANIM_TYPE_NONE = 0; 87 /** Fade out the menu until it's invisible. Used when the PIP window remains visible. */ 88 public static final int ANIM_TYPE_HIDE = 1; 89 /** Fade out the menu in sync with the PIP window. */ 90 public static final int ANIM_TYPE_DISMISS = 2; 91 92 @IntDef(prefix = { "ANIM_TYPE_" }, value = { 93 ANIM_TYPE_NONE, 94 ANIM_TYPE_HIDE, 95 ANIM_TYPE_DISMISS 96 }) 97 @Retention(RetentionPolicy.SOURCE) 98 public @interface AnimationType {} 99 100 private static final int INITIAL_DISMISS_DELAY = 3500; 101 private static final int POST_INTERACTION_DISMISS_DELAY = 2000; 102 private static final long MENU_SHOW_ON_EXPAND_START_DELAY = 30; 103 104 private static final float MENU_BACKGROUND_ALPHA = 0.3f; 105 private static final float DISABLED_ACTION_ALPHA = 0.54f; 106 107 private int mMenuState; 108 private boolean mAllowMenuTimeout = true; 109 private boolean mAllowTouches = true; 110 private int mDismissFadeOutDurationMs; 111 private boolean mFocusedTaskAllowSplitScreen; 112 113 private final List<RemoteAction> mActions = new ArrayList<>(); 114 115 private AccessibilityManager mAccessibilityManager; 116 private Drawable mBackgroundDrawable; 117 private View mMenuContainer; 118 private LinearLayout mActionsGroup; 119 private int mBetweenActionPaddingLand; 120 121 private AnimatorSet mMenuContainerAnimator; 122 private PhonePipMenuController mController; 123 private Optional<SplitScreenController> mSplitScreenControllerOptional; 124 125 private ValueAnimator.AnimatorUpdateListener mMenuBgUpdateListener = 126 new ValueAnimator.AnimatorUpdateListener() { 127 @Override 128 public void onAnimationUpdate(ValueAnimator animation) { 129 final float alpha = (float) animation.getAnimatedValue(); 130 mBackgroundDrawable.setAlpha((int) (MENU_BACKGROUND_ALPHA * alpha * 255)); 131 } 132 }; 133 134 private ShellExecutor mMainExecutor; 135 private Handler mMainHandler; 136 137 /** 138 * Whether the most recent showing of the menu caused a PIP resize, such as when PIP is too 139 * small and it is resized on menu show to fit the actions. 140 */ 141 private boolean mDidLastShowMenuResize; 142 private final Runnable mHideMenuRunnable = this::hideMenu; 143 144 protected View mViewRoot; 145 protected View mSettingsButton; 146 protected View mDismissButton; 147 protected View mEnterSplitButton; 148 protected View mTopEndContainer; 149 protected PipMenuIconsAlgorithm mPipMenuIconsAlgorithm; 150 PipMenuView(Context context, PhonePipMenuController controller, ShellExecutor mainExecutor, Handler mainHandler, Optional<SplitScreenController> splitScreenController)151 public PipMenuView(Context context, PhonePipMenuController controller, 152 ShellExecutor mainExecutor, Handler mainHandler, 153 Optional<SplitScreenController> splitScreenController) { 154 super(context, null, 0); 155 mContext = context; 156 mController = controller; 157 mMainExecutor = mainExecutor; 158 mMainHandler = mainHandler; 159 mSplitScreenControllerOptional = splitScreenController; 160 161 mAccessibilityManager = context.getSystemService(AccessibilityManager.class); 162 inflate(context, R.layout.pip_menu, this); 163 164 mBackgroundDrawable = mContext.getDrawable(R.drawable.pip_menu_background); 165 mBackgroundDrawable.setAlpha(0); 166 mViewRoot = findViewById(R.id.background); 167 mViewRoot.setBackground(mBackgroundDrawable); 168 mMenuContainer = findViewById(R.id.menu_container); 169 mMenuContainer.setAlpha(0); 170 mTopEndContainer = findViewById(R.id.top_end_container); 171 mSettingsButton = findViewById(R.id.settings); 172 mSettingsButton.setAlpha(0); 173 mSettingsButton.setOnClickListener((v) -> { 174 if (v.getAlpha() != 0) { 175 showSettings(); 176 } 177 }); 178 mDismissButton = findViewById(R.id.dismiss); 179 mDismissButton.setAlpha(0); 180 mDismissButton.setOnClickListener(v -> dismissPip()); 181 findViewById(R.id.expand_button).setOnClickListener(v -> { 182 if (mMenuContainer.getAlpha() != 0) { 183 expandPip(); 184 } 185 }); 186 187 mEnterSplitButton = findViewById(R.id.enter_split); 188 mEnterSplitButton.setAlpha(0); 189 mEnterSplitButton.setOnClickListener(v -> { 190 if (mEnterSplitButton.getAlpha() != 0) { 191 enterSplit(); 192 } 193 }); 194 195 findViewById(R.id.resize_handle).setAlpha(0); 196 197 mActionsGroup = findViewById(R.id.actions_group); 198 mBetweenActionPaddingLand = getResources().getDimensionPixelSize( 199 R.dimen.pip_between_action_padding_land); 200 mPipMenuIconsAlgorithm = new PipMenuIconsAlgorithm(mContext); 201 mPipMenuIconsAlgorithm.bindViews((ViewGroup) mViewRoot, (ViewGroup) mTopEndContainer, 202 findViewById(R.id.resize_handle), mEnterSplitButton, mSettingsButton, 203 mDismissButton); 204 mDismissFadeOutDurationMs = context.getResources() 205 .getInteger(R.integer.config_pipExitAnimationDuration); 206 207 initAccessibility(); 208 } 209 initAccessibility()210 private void initAccessibility() { 211 this.setAccessibilityDelegate(new View.AccessibilityDelegate() { 212 @Override 213 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { 214 super.onInitializeAccessibilityNodeInfo(host, info); 215 String label = getResources().getString(R.string.pip_menu_title); 216 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK, label)); 217 } 218 219 @Override 220 public boolean performAccessibilityAction(View host, int action, Bundle args) { 221 if (action == ACTION_CLICK && mMenuState != MENU_STATE_FULL) { 222 mController.showMenu(); 223 } 224 return super.performAccessibilityAction(host, action, args); 225 } 226 }); 227 } 228 229 @Override onKeyUp(int keyCode, KeyEvent event)230 public boolean onKeyUp(int keyCode, KeyEvent event) { 231 if (keyCode == KeyEvent.KEYCODE_ESCAPE) { 232 hideMenu(); 233 return true; 234 } 235 return super.onKeyUp(keyCode, event); 236 } 237 238 @Override shouldDelayChildPressedState()239 public boolean shouldDelayChildPressedState() { 240 return true; 241 } 242 243 @Override dispatchTouchEvent(MotionEvent ev)244 public boolean dispatchTouchEvent(MotionEvent ev) { 245 if (!mAllowTouches) { 246 return false; 247 } 248 249 if (mAllowMenuTimeout) { 250 repostDelayedHide(POST_INTERACTION_DISMISS_DELAY); 251 } 252 253 return super.dispatchTouchEvent(ev); 254 } 255 256 @Override dispatchGenericMotionEvent(MotionEvent event)257 public boolean dispatchGenericMotionEvent(MotionEvent event) { 258 if (mAllowMenuTimeout) { 259 repostDelayedHide(POST_INTERACTION_DISMISS_DELAY); 260 } 261 262 return super.dispatchGenericMotionEvent(event); 263 } 264 onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo)265 public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) { 266 final boolean isSplitScreen = mSplitScreenControllerOptional.isPresent() 267 && mSplitScreenControllerOptional.get().isTaskInSplitScreen(taskInfo.taskId); 268 mFocusedTaskAllowSplitScreen = isSplitScreen 269 || (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN 270 && taskInfo.supportsSplitScreenMultiWindow 271 && taskInfo.topActivityType != WindowConfiguration.ACTIVITY_TYPE_HOME); 272 } 273 showMenu(int menuState, Rect stackBounds, boolean allowMenuTimeout, boolean resizeMenuOnShow, boolean withDelay, boolean showResizeHandle)274 void showMenu(int menuState, Rect stackBounds, boolean allowMenuTimeout, 275 boolean resizeMenuOnShow, boolean withDelay, boolean showResizeHandle) { 276 mAllowMenuTimeout = allowMenuTimeout; 277 mDidLastShowMenuResize = resizeMenuOnShow; 278 final boolean enableEnterSplit = 279 mContext.getResources().getBoolean(R.bool.config_pipEnableEnterSplitButton); 280 if (mMenuState != menuState) { 281 // Disallow touches if the menu needs to resize while showing, and we are transitioning 282 // to/from a full menu state. 283 boolean disallowTouchesUntilAnimationEnd = resizeMenuOnShow 284 && (mMenuState == MENU_STATE_FULL || menuState == MENU_STATE_FULL); 285 mAllowTouches = !disallowTouchesUntilAnimationEnd; 286 cancelDelayedHide(); 287 if (mMenuContainerAnimator != null) { 288 mMenuContainerAnimator.cancel(); 289 } 290 mMenuContainerAnimator = new AnimatorSet(); 291 ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA, 292 mMenuContainer.getAlpha(), 1f); 293 menuAnim.addUpdateListener(mMenuBgUpdateListener); 294 ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA, 295 mSettingsButton.getAlpha(), 1f); 296 ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA, 297 mDismissButton.getAlpha(), 1f); 298 ObjectAnimator enterSplitAnim = ObjectAnimator.ofFloat(mEnterSplitButton, View.ALPHA, 299 mEnterSplitButton.getAlpha(), 300 enableEnterSplit && mFocusedTaskAllowSplitScreen ? 1f : 0f); 301 if (menuState == MENU_STATE_FULL) { 302 mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim, 303 enterSplitAnim); 304 } else { 305 mMenuContainerAnimator.playTogether(enterSplitAnim); 306 } 307 mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN); 308 mMenuContainerAnimator.setDuration(ANIMATION_HIDE_DURATION_MS); 309 mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() { 310 @Override 311 public void onAnimationEnd(Animator animation) { 312 mAllowTouches = true; 313 notifyMenuStateChangeFinish(menuState); 314 if (allowMenuTimeout) { 315 repostDelayedHide(INITIAL_DISMISS_DELAY); 316 } 317 } 318 319 @Override 320 public void onAnimationCancel(Animator animation) { 321 mAllowTouches = true; 322 } 323 }); 324 if (withDelay) { 325 // starts the menu container animation after window expansion is completed 326 notifyMenuStateChangeStart(menuState, resizeMenuOnShow, () -> { 327 if (mMenuContainerAnimator == null) { 328 return; 329 } 330 mMenuContainerAnimator.setStartDelay(MENU_SHOW_ON_EXPAND_START_DELAY); 331 setVisibility(VISIBLE); 332 mMenuContainerAnimator.start(); 333 }); 334 } else { 335 notifyMenuStateChangeStart(menuState, resizeMenuOnShow, null); 336 setVisibility(VISIBLE); 337 mMenuContainerAnimator.start(); 338 } 339 updateActionViews(menuState, stackBounds); 340 } else { 341 // If we are already visible, then just start the delayed dismiss and unregister any 342 // existing input consumers from the previous drag 343 if (allowMenuTimeout) { 344 repostDelayedHide(POST_INTERACTION_DISMISS_DELAY); 345 } 346 } 347 } 348 349 /** 350 * Different from {@link #hideMenu()}, this function does not try to finish this menu activity 351 * and instead, it fades out the controls by setting the alpha to 0 directly without menu 352 * visibility callbacks invoked. 353 */ fadeOutMenu()354 void fadeOutMenu() { 355 mMenuContainer.setAlpha(0f); 356 mSettingsButton.setAlpha(0f); 357 mDismissButton.setAlpha(0f); 358 mEnterSplitButton.setAlpha(0f); 359 } 360 pokeMenu()361 void pokeMenu() { 362 cancelDelayedHide(); 363 } 364 updateMenuLayout(Rect bounds)365 void updateMenuLayout(Rect bounds) { 366 mPipMenuIconsAlgorithm.onBoundsChanged(bounds); 367 } 368 hideMenu()369 void hideMenu() { 370 hideMenu(null); 371 } 372 hideMenu(Runnable animationEndCallback)373 void hideMenu(Runnable animationEndCallback) { 374 hideMenu(animationEndCallback, true /* notifyMenuVisibility */, mDidLastShowMenuResize, 375 ANIM_TYPE_HIDE); 376 } 377 hideMenu(boolean resize, @AnimationType int animationType)378 void hideMenu(boolean resize, @AnimationType int animationType) { 379 hideMenu(null /* animationFinishedRunnable */, true /* notifyMenuVisibility */, resize, 380 animationType); 381 } 382 hideMenu(final Runnable animationFinishedRunnable, boolean notifyMenuVisibility, boolean resize, @AnimationType int animationType)383 void hideMenu(final Runnable animationFinishedRunnable, boolean notifyMenuVisibility, 384 boolean resize, @AnimationType int animationType) { 385 if (mMenuState != MENU_STATE_NONE) { 386 cancelDelayedHide(); 387 if (notifyMenuVisibility) { 388 notifyMenuStateChangeStart(MENU_STATE_NONE, resize, null); 389 } 390 mMenuContainerAnimator = new AnimatorSet(); 391 ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA, 392 mMenuContainer.getAlpha(), 0f); 393 menuAnim.addUpdateListener(mMenuBgUpdateListener); 394 ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA, 395 mSettingsButton.getAlpha(), 0f); 396 ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA, 397 mDismissButton.getAlpha(), 0f); 398 ObjectAnimator enterSplitAnim = ObjectAnimator.ofFloat(mEnterSplitButton, View.ALPHA, 399 mEnterSplitButton.getAlpha(), 0f); 400 mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim, 401 enterSplitAnim); 402 mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT); 403 mMenuContainerAnimator.setDuration(getFadeOutDuration(animationType)); 404 mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() { 405 @Override 406 public void onAnimationEnd(Animator animation) { 407 setVisibility(GONE); 408 if (notifyMenuVisibility) { 409 notifyMenuStateChangeFinish(MENU_STATE_NONE); 410 } 411 if (animationFinishedRunnable != null) { 412 animationFinishedRunnable.run(); 413 } 414 } 415 }); 416 mMenuContainerAnimator.start(); 417 } 418 } 419 420 /** 421 * @return Estimated minimum {@link Size} to hold the actions. 422 * See also {@link #updateActionViews(Rect)} 423 */ getEstimatedMinMenuSize()424 Size getEstimatedMinMenuSize() { 425 final int pipActionSize = getResources().getDimensionPixelSize(R.dimen.pip_action_size); 426 // the minimum width would be (2 * pipActionSize) since we have settings and dismiss button 427 // on the top action container. 428 final int width = Math.max(2, mActions.size()) * pipActionSize; 429 final int height = getResources().getDimensionPixelSize(R.dimen.pip_expand_action_size) 430 + getResources().getDimensionPixelSize(R.dimen.pip_action_padding) 431 + getResources().getDimensionPixelSize(R.dimen.pip_expand_container_edge_margin); 432 return new Size(width, height); 433 } 434 setActions(Rect stackBounds, List<RemoteAction> actions)435 void setActions(Rect stackBounds, List<RemoteAction> actions) { 436 mActions.clear(); 437 mActions.addAll(actions); 438 if (mMenuState == MENU_STATE_FULL) { 439 updateActionViews(mMenuState, stackBounds); 440 } 441 } 442 updateActionViews(int menuState, Rect stackBounds)443 private void updateActionViews(int menuState, Rect stackBounds) { 444 ViewGroup expandContainer = findViewById(R.id.expand_container); 445 ViewGroup actionsContainer = findViewById(R.id.actions_container); 446 actionsContainer.setOnTouchListener((v, ev) -> { 447 // Do nothing, prevent click through to parent 448 return true; 449 }); 450 451 // Update the expand button only if it should show with the menu 452 expandContainer.setVisibility(menuState == MENU_STATE_FULL 453 ? View.VISIBLE 454 : View.INVISIBLE); 455 456 FrameLayout.LayoutParams expandedLp = 457 (FrameLayout.LayoutParams) expandContainer.getLayoutParams(); 458 if (mActions.isEmpty() || menuState == MENU_STATE_NONE) { 459 actionsContainer.setVisibility(View.INVISIBLE); 460 461 // Update the expand container margin to adjust the center of the expand button to 462 // account for the existence of the action container 463 expandedLp.topMargin = 0; 464 expandedLp.bottomMargin = 0; 465 } else { 466 actionsContainer.setVisibility(View.VISIBLE); 467 if (mActionsGroup != null) { 468 // Ensure we have as many buttons as actions 469 final LayoutInflater inflater = LayoutInflater.from(mContext); 470 while (mActionsGroup.getChildCount() < mActions.size()) { 471 final PipMenuActionView actionView = (PipMenuActionView) inflater.inflate( 472 R.layout.pip_menu_action, mActionsGroup, false); 473 mActionsGroup.addView(actionView); 474 } 475 476 // Update the visibility of all views 477 for (int i = 0; i < mActionsGroup.getChildCount(); i++) { 478 mActionsGroup.getChildAt(i).setVisibility(i < mActions.size() 479 ? View.VISIBLE 480 : View.GONE); 481 } 482 483 // Recreate the layout 484 final boolean isLandscapePip = stackBounds != null 485 && (stackBounds.width() > stackBounds.height()); 486 for (int i = 0; i < mActions.size(); i++) { 487 final RemoteAction action = mActions.get(i); 488 final PipMenuActionView actionView = 489 (PipMenuActionView) mActionsGroup.getChildAt(i); 490 491 // TODO: Check if the action drawable has changed before we reload it 492 action.getIcon().loadDrawableAsync(mContext, d -> { 493 if (d != null) { 494 d.setTint(Color.WHITE); 495 actionView.setImageDrawable(d); 496 } 497 }, mMainHandler); 498 actionView.setContentDescription(action.getContentDescription()); 499 if (action.isEnabled()) { 500 actionView.setOnClickListener(v -> { 501 try { 502 action.getActionIntent().send(); 503 } catch (CanceledException e) { 504 Log.w(TAG, "Failed to send action", e); 505 } 506 }); 507 } 508 actionView.setEnabled(action.isEnabled()); 509 actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA); 510 511 // Update the margin between actions 512 LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) 513 actionView.getLayoutParams(); 514 lp.leftMargin = (isLandscapePip && i > 0) ? mBetweenActionPaddingLand : 0; 515 } 516 } 517 518 // Update the expand container margin to adjust the center of the expand button to 519 // account for the existence of the action container 520 expandedLp.topMargin = getResources().getDimensionPixelSize( 521 R.dimen.pip_action_padding); 522 expandedLp.bottomMargin = getResources().getDimensionPixelSize( 523 R.dimen.pip_expand_container_edge_margin); 524 } 525 expandContainer.requestLayout(); 526 } 527 notifyMenuStateChangeStart(int menuState, boolean resize, Runnable callback)528 private void notifyMenuStateChangeStart(int menuState, boolean resize, Runnable callback) { 529 mController.onMenuStateChangeStart(menuState, resize, callback); 530 } 531 notifyMenuStateChangeFinish(int menuState)532 private void notifyMenuStateChangeFinish(int menuState) { 533 mMenuState = menuState; 534 mController.onMenuStateChangeFinish(menuState); 535 } 536 expandPip()537 private void expandPip() { 538 // Do not notify menu visibility when hiding the menu, the controller will do this when it 539 // handles the message 540 hideMenu(mController::onPipExpand, false /* notifyMenuVisibility */, true /* resize */, 541 ANIM_TYPE_HIDE); 542 } 543 dismissPip()544 private void dismissPip() { 545 if (mMenuState != MENU_STATE_NONE) { 546 // Do not call hideMenu() directly. Instead, let the menu controller handle it just as 547 // any other dismissal that will update the touch state and fade out the PIP task 548 // and the menu view at the same time. 549 mController.onPipDismiss(); 550 } 551 } 552 enterSplit()553 private void enterSplit() { 554 // Do not notify menu visibility when hiding the menu, the controller will do this when it 555 // handles the message 556 hideMenu(mController::onEnterSplit, false /* notifyMenuVisibility */, true /* resize */, 557 ANIM_TYPE_HIDE); 558 } 559 560 showSettings()561 private void showSettings() { 562 final Pair<ComponentName, Integer> topPipActivityInfo = 563 PipUtils.getTopPipActivity(mContext); 564 if (topPipActivityInfo.first != null) { 565 final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS, 566 Uri.fromParts("package", topPipActivityInfo.first.getPackageName(), null)); 567 settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK); 568 mContext.startActivityAsUser(settingsIntent, UserHandle.of(topPipActivityInfo.second)); 569 } 570 } 571 cancelDelayedHide()572 private void cancelDelayedHide() { 573 mMainExecutor.removeCallbacks(mHideMenuRunnable); 574 } 575 repostDelayedHide(int delay)576 private void repostDelayedHide(int delay) { 577 int recommendedTimeout = mAccessibilityManager.getRecommendedTimeoutMillis(delay, 578 FLAG_CONTENT_ICONS | FLAG_CONTENT_CONTROLS); 579 mMainExecutor.removeCallbacks(mHideMenuRunnable); 580 mMainExecutor.executeDelayed(mHideMenuRunnable, recommendedTimeout); 581 } 582 getFadeOutDuration(@nimationType int animationType)583 private long getFadeOutDuration(@AnimationType int animationType) { 584 switch (animationType) { 585 case ANIM_TYPE_NONE: 586 return ANIMATION_NONE_DURATION_MS; 587 case ANIM_TYPE_HIDE: 588 return ANIMATION_HIDE_DURATION_MS; 589 case ANIM_TYPE_DISMISS: 590 return mDismissFadeOutDurationMs; 591 default: 592 throw new IllegalStateException("Invalid animation type " + animationType); 593 } 594 } 595 } 596