PipTouchHandler.java revision be4a808b3d0e3c4a8d530d7f6a7b88856ebbb672
1/* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.systemui.pip.phone; 18 19import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_NONE; 20import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_CLOSE; 21import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_FULL; 22 23import android.animation.Animator; 24import android.animation.AnimatorListenerAdapter; 25import android.animation.ValueAnimator; 26import android.animation.ValueAnimator.AnimatorUpdateListener; 27import android.app.IActivityManager; 28import android.content.Context; 29import android.graphics.Point; 30import android.graphics.PointF; 31import android.graphics.Rect; 32import android.os.Handler; 33import android.os.RemoteException; 34import android.util.Log; 35import android.util.Size; 36import android.view.IPinnedStackController; 37import android.view.MotionEvent; 38import android.view.ViewConfiguration; 39import android.view.accessibility.AccessibilityEvent; 40import android.view.accessibility.AccessibilityManager; 41import android.view.accessibility.AccessibilityNodeInfo; 42 43import com.android.internal.logging.MetricsLogger; 44import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 45import com.android.internal.policy.PipSnapAlgorithm; 46import com.android.systemui.Dependency; 47import com.android.systemui.R; 48import com.android.systemui.statusbar.FlingAnimationUtils; 49import com.android.systemui.tuner.TunerService; 50 51import java.io.PrintWriter; 52 53/** 54 * Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding 55 * the PIP. 56 */ 57public class PipTouchHandler implements TunerService.Tunable { 58 private static final String TAG = "PipTouchHandler"; 59 60 private static final String TUNER_KEY_MINIMIZE = "pip_minimize"; 61 62 // These values are used for metrics and should never change 63 private static final int METRIC_VALUE_DISMISSED_BY_TAP = 0; 64 private static final int METRIC_VALUE_DISMISSED_BY_DRAG = 1; 65 66 private static final int SHOW_DISMISS_AFFORDANCE_DELAY = 200; 67 68 // Allow dragging the PIP to a location to close it 69 private static final boolean ENABLE_DISMISS_DRAG_TO_EDGE = true; 70 71 private final Context mContext; 72 private final IActivityManager mActivityManager; 73 private final ViewConfiguration mViewConfig; 74 private final PipMenuListener mMenuListener = new PipMenuListener(); 75 private IPinnedStackController mPinnedStackController; 76 77 private final PipMenuActivityController mMenuController; 78 private final PipDismissViewController mDismissViewController; 79 private final PipSnapAlgorithm mSnapAlgorithm; 80 private final AccessibilityManager mAccessibilityManager; 81 82 // The current movement bounds 83 private Rect mMovementBounds = new Rect(); 84 85 // The reference bounds used to calculate the normal/expanded target bounds 86 private Rect mNormalBounds = new Rect(); 87 private Rect mNormalMovementBounds = new Rect(); 88 private Rect mExpandedBounds = new Rect(); 89 private Rect mExpandedMovementBounds = new Rect(); 90 private int mExpandedShortestEdgeSize; 91 92 private Handler mHandler = new Handler(); 93 private Runnable mShowDismissAffordance = new Runnable() { 94 @Override 95 public void run() { 96 if (ENABLE_DISMISS_DRAG_TO_EDGE) { 97 mDismissViewController.showDismissTarget(); 98 } 99 } 100 }; 101 private ValueAnimator.AnimatorUpdateListener mUpdateScrimListener = 102 new AnimatorUpdateListener() { 103 @Override 104 public void onAnimationUpdate(ValueAnimator animation) { 105 updateDismissFraction(); 106 } 107 }; 108 109 // Allow the PIP to be dragged to the edge of the screen to be minimized. 110 private boolean mEnableMinimize = false; 111 112 // Behaviour states 113 private int mMenuState; 114 private boolean mIsMinimized; 115 private boolean mIsImeShowing; 116 private int mImeHeight; 117 private float mSavedSnapFraction = -1f; 118 private boolean mSendingHoverAccessibilityEvents; 119 private boolean mMovementWithinMinimize; 120 private boolean mMovementWithinDismiss; 121 122 // Touch state 123 private final PipTouchState mTouchState; 124 private final FlingAnimationUtils mFlingAnimationUtils; 125 private final PipTouchGesture[] mGestures; 126 private final PipMotionHelper mMotionHelper; 127 128 // Temp vars 129 private final Rect mTmpBounds = new Rect(); 130 131 /** 132 * A listener for the PIP menu activity. 133 */ 134 private class PipMenuListener implements PipMenuActivityController.Listener { 135 @Override 136 public void onPipMenuStateChanged(int menuState, boolean resize) { 137 setMenuState(menuState, resize); 138 } 139 140 @Override 141 public void onPipExpand() { 142 if (!mIsMinimized) { 143 mMotionHelper.expandPip(); 144 } 145 } 146 147 @Override 148 public void onPipMinimize() { 149 setMinimizedStateInternal(true); 150 mMotionHelper.animateToClosestMinimizedState(mMovementBounds, null /* updateListener */); 151 } 152 153 @Override 154 public void onPipDismiss() { 155 mMotionHelper.dismissPip(); 156 MetricsLogger.action(mContext, MetricsEvent.ACTION_PICTURE_IN_PICTURE_DISMISSED, 157 METRIC_VALUE_DISMISSED_BY_TAP); 158 } 159 160 @Override 161 public void onPipShowMenu() { 162 mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(), 163 mMovementBounds, true /* allowMenuTimeout */); 164 } 165 } 166 167 public PipTouchHandler(Context context, IActivityManager activityManager, 168 PipMenuActivityController menuController, 169 InputConsumerController inputConsumerController) { 170 171 // Initialize the Pip input consumer 172 mContext = context; 173 mActivityManager = activityManager; 174 mAccessibilityManager = context.getSystemService(AccessibilityManager.class); 175 mViewConfig = ViewConfiguration.get(context); 176 mMenuController = menuController; 177 mMenuController.addListener(mMenuListener); 178 mDismissViewController = new PipDismissViewController(context); 179 mSnapAlgorithm = new PipSnapAlgorithm(mContext); 180 mTouchState = new PipTouchState(mViewConfig); 181 mFlingAnimationUtils = new FlingAnimationUtils(context, 2f); 182 mGestures = new PipTouchGesture[] { 183 mDefaultMovementGesture 184 }; 185 mMotionHelper = new PipMotionHelper(mContext, mActivityManager, mSnapAlgorithm, 186 mFlingAnimationUtils); 187 mExpandedShortestEdgeSize = context.getResources().getDimensionPixelSize( 188 R.dimen.pip_expanded_shortest_edge_size); 189 190 // Register any tuner settings changes 191 Dependency.get(TunerService.class).addTunable(this, TUNER_KEY_MINIMIZE); 192 193 // Register the listener for input consumer touch events 194 inputConsumerController.setTouchListener(this::handleTouchEvent); 195 inputConsumerController.setRegistrationListener(this::onRegistrationChanged); 196 onRegistrationChanged(inputConsumerController.isRegistered()); 197 } 198 199 public void setTouchEnabled(boolean enabled) { 200 mTouchState.setAllowTouches(enabled); 201 } 202 203 public void showPictureInPictureMenu() { 204 // Only show the menu if the user isn't currently interacting with the PiP 205 if (!mTouchState.isUserInteracting()) { 206 mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(), 207 mMovementBounds, false /* allowMenuTimeout */); 208 } 209 } 210 211 public void onActivityPinned() { 212 // Reset some states once we are pinned 213 mMenuState = MENU_STATE_NONE; 214 215 if (mIsMinimized) { 216 setMinimizedStateInternal(false); 217 } 218 mDismissViewController.destroyDismissTarget(); 219 mMenuController.showMenu(MENU_STATE_CLOSE, mMotionHelper.getBounds(), 220 mMovementBounds, true /* allowMenuTimeout */); 221 } 222 223 public void onPinnedStackAnimationEnded() { 224 // Always synchronize the motion helper bounds once PiP animations finish 225 mMotionHelper.synchronizePinnedStackBounds(); 226 } 227 228 @Override 229 public void onTuningChanged(String key, String newValue) { 230 if (newValue == null) { 231 // Reset back to default 232 mEnableMinimize = false; 233 return; 234 } 235 switch (key) { 236 case TUNER_KEY_MINIMIZE: 237 mEnableMinimize = Integer.parseInt(newValue) != 0; 238 break; 239 } 240 } 241 242 public void onConfigurationChanged() { 243 mMotionHelper.onConfigurationChanged(); 244 mMotionHelper.synchronizePinnedStackBounds(); 245 } 246 247 public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) { 248 mIsImeShowing = imeVisible; 249 mImeHeight = imeHeight; 250 } 251 252 public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds, Rect animatingBounds, 253 boolean fromImeAdjustement) { 254 // Re-calculate the expanded bounds 255 mNormalBounds = normalBounds; 256 Rect normalMovementBounds = new Rect(); 257 mSnapAlgorithm.getMovementBounds(mNormalBounds, insetBounds, normalMovementBounds, 258 mIsImeShowing ? mImeHeight : 0); 259 260 // Calculate the expanded size 261 float aspectRatio = (float) normalBounds.width() / normalBounds.height(); 262 Point displaySize = new Point(); 263 mContext.getDisplay().getRealSize(displaySize); 264 Size expandedSize = mSnapAlgorithm.getSizeForAspectRatio(aspectRatio, 265 mExpandedShortestEdgeSize, displaySize.x, displaySize.y); 266 mExpandedBounds.set(0, 0, expandedSize.getWidth(), expandedSize.getHeight()); 267 Rect expandedMovementBounds = new Rect(); 268 mSnapAlgorithm.getMovementBounds(mExpandedBounds, insetBounds, expandedMovementBounds, 269 mIsImeShowing ? mImeHeight : 0); 270 271 272 // If this is from an IME adjustment, then we should move the PiP so that it is not occluded 273 // by the IME 274 if (fromImeAdjustement) { 275 if (mTouchState.isUserInteracting()) { 276 // Defer the update of the current movement bounds until after the user finishes 277 // touching the screen 278 } else { 279 final Rect bounds = new Rect(animatingBounds); 280 final Rect toMovementBounds = mMenuState == MENU_STATE_FULL 281 ? expandedMovementBounds 282 : normalMovementBounds; 283 if (mIsImeShowing) { 284 // IME visible 285 if (bounds.top == mMovementBounds.bottom) { 286 // If the PIP is currently resting on top of the IME, then adjust it with 287 // the hiding IME 288 bounds.offsetTo(bounds.left, toMovementBounds.bottom); 289 } else { 290 bounds.offset(0, Math.min(0, toMovementBounds.bottom - bounds.top)); 291 } 292 } else { 293 // IME hidden 294 if (bounds.top == mMovementBounds.bottom) { 295 // If the PIP is resting on top of the IME, then adjust it with the hiding IME 296 bounds.offsetTo(bounds.left, toMovementBounds.bottom); 297 } 298 } 299 mMotionHelper.animateToIMEOffset(bounds); 300 } 301 } 302 303 // Update the movement bounds after doing the calculations based on the old movement bounds 304 // above 305 mNormalMovementBounds = normalMovementBounds; 306 mExpandedMovementBounds = expandedMovementBounds; 307 updateMovementBounds(mMenuState); 308 } 309 310 private void onRegistrationChanged(boolean isRegistered) { 311 mAccessibilityManager.setPictureInPictureActionReplacingConnection(isRegistered 312 ? new PipAccessibilityInteractionConnection(mMotionHelper, 313 this::onAccessibilityShowMenu, mHandler) : null); 314 } 315 316 private void onAccessibilityShowMenu() { 317 mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(), 318 mMovementBounds, false /* allowMenuTimeout */); 319 } 320 321 private boolean handleTouchEvent(MotionEvent ev) { 322 // Skip touch handling until we are bound to the controller 323 if (mPinnedStackController == null) { 324 return true; 325 } 326 327 // Update the touch state 328 mTouchState.onTouchEvent(ev); 329 330 switch (ev.getAction()) { 331 case MotionEvent.ACTION_DOWN: { 332 mMotionHelper.synchronizePinnedStackBounds(); 333 334 for (PipTouchGesture gesture : mGestures) { 335 gesture.onDown(mTouchState); 336 } 337 break; 338 } 339 case MotionEvent.ACTION_MOVE: { 340 for (PipTouchGesture gesture : mGestures) { 341 if (gesture.onMove(mTouchState)) { 342 break; 343 } 344 } 345 break; 346 } 347 case MotionEvent.ACTION_UP: { 348 // Update the movement bounds again if the state has changed since the user started 349 // dragging (ie. when the IME shows) 350 updateMovementBounds(mMenuState); 351 352 for (PipTouchGesture gesture : mGestures) { 353 if (gesture.onUp(mTouchState)) { 354 break; 355 } 356 } 357 358 // Fall through to clean up 359 } 360 case MotionEvent.ACTION_CANCEL: { 361 mTouchState.reset(); 362 break; 363 } 364 case MotionEvent.ACTION_HOVER_ENTER: 365 case MotionEvent.ACTION_HOVER_MOVE: { 366 if (!mSendingHoverAccessibilityEvents) { 367 AccessibilityEvent event = AccessibilityEvent.obtain( 368 AccessibilityEvent.TYPE_VIEW_HOVER_ENTER); 369 AccessibilityNodeInfo info = 370 PipAccessibilityInteractionConnection.obtainRootAccessibilityNodeInfo(); 371 event.setSource(info); 372 info.recycle(); 373 mAccessibilityManager.sendAccessibilityEvent(event); 374 mSendingHoverAccessibilityEvents = true; 375 } 376 break; 377 } 378 case MotionEvent.ACTION_HOVER_EXIT: { 379 if (mSendingHoverAccessibilityEvents) { 380 AccessibilityEvent event = AccessibilityEvent.obtain( 381 AccessibilityEvent.TYPE_VIEW_HOVER_EXIT); 382 AccessibilityNodeInfo info = 383 PipAccessibilityInteractionConnection.obtainRootAccessibilityNodeInfo(); 384 event.setSource(info); 385 info.recycle(); 386 mAccessibilityManager.sendAccessibilityEvent(event); 387 mSendingHoverAccessibilityEvents = false; 388 } 389 break; 390 } 391 } 392 return mMenuState == MENU_STATE_NONE; 393 } 394 395 /** 396 * Updates the appearance of the menu and scrim on top of the PiP while dismissing. 397 */ 398 void updateDismissFraction() { 399 if (mMenuController != null) { 400 Rect bounds = mMotionHelper.getBounds(); 401 final float target = mMovementBounds.bottom + bounds.height(); 402 float fraction = 0f; 403 if (bounds.bottom > target) { 404 final float distance = bounds.bottom - target; 405 fraction = Math.min(distance / bounds.height(), 1f); 406 } 407 if (Float.compare(fraction, 0f) != 0 || mMenuState != MENU_STATE_NONE) { 408 // Update if the fraction > 0, or if fraction == 0 and the menu was already visible 409 mMenuController.setDismissFraction(fraction); 410 } 411 } 412 } 413 414 /** 415 * Sets the controller to update the system of changes from user interaction. 416 */ 417 void setPinnedStackController(IPinnedStackController controller) { 418 mPinnedStackController = controller; 419 } 420 421 /** 422 * Sets the minimized state. 423 */ 424 void setMinimizedStateInternal(boolean isMinimized) { 425 if (!mEnableMinimize) { 426 return; 427 } 428 setMinimizedState(isMinimized, false /* fromController */); 429 } 430 431 /** 432 * Sets the minimized state. 433 */ 434 void setMinimizedState(boolean isMinimized, boolean fromController) { 435 if (!mEnableMinimize) { 436 return; 437 } 438 if (mIsMinimized != isMinimized) { 439 MetricsLogger.action(mContext, MetricsEvent.ACTION_PICTURE_IN_PICTURE_MINIMIZED, 440 isMinimized); 441 } 442 mIsMinimized = isMinimized; 443 mSnapAlgorithm.setMinimized(isMinimized); 444 445 if (fromController) { 446 if (isMinimized) { 447 // Move the PiP to the new bounds immediately if minimized 448 mMotionHelper.movePip(mMotionHelper.getClosestMinimizedBounds(mNormalBounds, 449 mMovementBounds)); 450 } 451 } else if (mPinnedStackController != null) { 452 try { 453 mPinnedStackController.setIsMinimized(isMinimized); 454 } catch (RemoteException e) { 455 Log.e(TAG, "Could not set minimized state", e); 456 } 457 } 458 } 459 460 /** 461 * Sets the menu visibility. 462 */ 463 void setMenuState(int menuState, boolean resize) { 464 if (menuState == MENU_STATE_FULL) { 465 // Save the current snap fraction and if we do not drag or move the PiP, then 466 // we store back to this snap fraction. Otherwise, we'll reset the snap 467 // fraction and snap to the closest edge 468 Rect expandedBounds = new Rect(mExpandedBounds); 469 if (resize) { 470 mSavedSnapFraction = mMotionHelper.animateToExpandedState(expandedBounds, 471 mMovementBounds, mExpandedMovementBounds); 472 } 473 } else if (menuState == MENU_STATE_NONE) { 474 // Try and restore the PiP to the closest edge, using the saved snap fraction 475 // if possible 476 if (resize) { 477 Rect normalBounds = new Rect(mNormalBounds); 478 mMotionHelper.animateToUnexpandedState(normalBounds, mSavedSnapFraction, 479 mNormalMovementBounds, mMovementBounds, mIsMinimized); 480 } 481 mSavedSnapFraction = -1f; 482 } 483 mMenuState = menuState; 484 updateMovementBounds(menuState); 485 if (menuState != MENU_STATE_CLOSE) { 486 MetricsLogger.visibility(mContext, MetricsEvent.ACTION_PICTURE_IN_PICTURE_MENU, 487 menuState == MENU_STATE_FULL); 488 } 489 } 490 491 /** 492 * @return the motion helper. 493 */ 494 public PipMotionHelper getMotionHelper() { 495 return mMotionHelper; 496 } 497 498 /** 499 * Gesture controlling normal movement of the PIP. 500 */ 501 private PipTouchGesture mDefaultMovementGesture = new PipTouchGesture() { 502 // Whether the PiP was on the left side of the screen at the start of the gesture 503 private boolean mStartedOnLeft; 504 505 @Override 506 public void onDown(PipTouchState touchState) { 507 if (!touchState.isUserInteracting()) { 508 return; 509 } 510 511 mStartedOnLeft = mMotionHelper.getBounds().left < mMovementBounds.centerX(); 512 mMovementWithinMinimize = true; 513 mMovementWithinDismiss = touchState.getDownTouchPosition().y >= mMovementBounds.bottom; 514 515 // If the menu is still visible, and we aren't minimized, then just poke the menu 516 // so that it will timeout after the user stops touching it 517 if (mMenuState != MENU_STATE_NONE && !mIsMinimized) { 518 mMenuController.pokeMenu(); 519 } 520 521 if (ENABLE_DISMISS_DRAG_TO_EDGE) { 522 mDismissViewController.createDismissTarget(); 523 mHandler.postDelayed(mShowDismissAffordance, SHOW_DISMISS_AFFORDANCE_DELAY); 524 } 525 } 526 527 @Override 528 boolean onMove(PipTouchState touchState) { 529 if (!touchState.isUserInteracting()) { 530 return false; 531 } 532 533 if (touchState.startedDragging()) { 534 mSavedSnapFraction = -1f; 535 } 536 537 if (touchState.startedDragging() && ENABLE_DISMISS_DRAG_TO_EDGE) { 538 mHandler.removeCallbacks(mShowDismissAffordance); 539 mDismissViewController.showDismissTarget(); 540 } 541 542 if (touchState.isDragging()) { 543 // Move the pinned stack freely 544 mTmpBounds.set(mMotionHelper.getBounds()); 545 final PointF lastDelta = touchState.getLastTouchDelta(); 546 float left = mTmpBounds.left + lastDelta.x; 547 float top = mTmpBounds.top + lastDelta.y; 548 if (!touchState.allowDraggingOffscreen() || !mEnableMinimize) { 549 left = Math.max(mMovementBounds.left, Math.min(mMovementBounds.right, left)); 550 } 551 if (ENABLE_DISMISS_DRAG_TO_EDGE) { 552 // Allow pip to move past bottom bounds 553 top = Math.max(mMovementBounds.top, top); 554 } else { 555 top = Math.max(mMovementBounds.top, Math.min(mMovementBounds.bottom, top)); 556 } 557 mTmpBounds.offsetTo((int) left, (int) top); 558 mMotionHelper.movePip(mTmpBounds); 559 560 if (ENABLE_DISMISS_DRAG_TO_EDGE) { 561 updateDismissFraction(); 562 } 563 564 final PointF curPos = touchState.getLastTouchPosition(); 565 if (mMovementWithinMinimize) { 566 // Track if movement remains near starting edge to identify swipes to minimize 567 mMovementWithinMinimize = mStartedOnLeft 568 ? curPos.x <= mMovementBounds.left + mTmpBounds.width() 569 : curPos.x >= mMovementBounds.right; 570 } 571 if (mMovementWithinDismiss) { 572 // Track if movement remains near the bottom edge to identify swipe to dismiss 573 mMovementWithinDismiss = curPos.y >= mMovementBounds.bottom; 574 } 575 return true; 576 } 577 return false; 578 } 579 580 @Override 581 public boolean onUp(PipTouchState touchState) { 582 if (!touchState.isUserInteracting()) { 583 return false; 584 } 585 586 final PointF vel = touchState.getVelocity(); 587 final boolean isHorizontal = Math.abs(vel.x) > Math.abs(vel.y); 588 final float velocity = PointF.length(vel.x, vel.y); 589 final boolean isFling = velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond(); 590 final boolean isFlingToBot = isFling 591 && !isHorizontal && mMovementWithinDismiss && vel.y > 0; 592 if (ENABLE_DISMISS_DRAG_TO_EDGE) { 593 try { 594 mHandler.removeCallbacks(mShowDismissAffordance); 595 if (mMotionHelper.shouldDismissPip() || isFlingToBot) { 596 mMotionHelper.animateDismiss(mMotionHelper.getBounds(), vel.x, 597 vel.y, mUpdateScrimListener); 598 MetricsLogger.action(mContext, 599 MetricsEvent.ACTION_PICTURE_IN_PICTURE_DISMISSED, 600 METRIC_VALUE_DISMISSED_BY_DRAG); 601 return true; 602 } 603 } finally { 604 mDismissViewController.destroyDismissTarget(); 605 } 606 } 607 608 if (touchState.isDragging()) { 609 final boolean isFlingToEdge = isFling && isHorizontal && mMovementWithinMinimize 610 && (mStartedOnLeft ? vel.x < 0 : vel.x > 0); 611 if (mEnableMinimize && 612 !mIsMinimized && (mMotionHelper.shouldMinimizePip() || isFlingToEdge)) { 613 // Pip should be minimized 614 setMinimizedStateInternal(true); 615 if (mMenuState == MENU_STATE_FULL) { 616 // If the user dragged the expanded PiP to the edge, then hiding the menu 617 // will trigger the PiP to be scaled back to the normal size with the 618 // minimize offset adjusted 619 mMenuController.hideMenu(); 620 } else { 621 mMotionHelper.animateToClosestMinimizedState(mMovementBounds, 622 mUpdateScrimListener); 623 } 624 return true; 625 } 626 if (mIsMinimized) { 627 // If we're dragging and it wasn't a minimize gesture then we shouldn't be 628 // minimized. 629 setMinimizedStateInternal(false); 630 } 631 632 AnimatorListenerAdapter postAnimationCallback = null; 633 if (mMenuState != MENU_STATE_NONE) { 634 // If the menu is still visible, and we aren't minimized, then just poke the 635 // menu so that it will timeout after the user stops touching it 636 mMenuController.showMenu(mMenuState, mMotionHelper.getBounds(), 637 mMovementBounds, true /* allowMenuTimeout */); 638 } else { 639 // If the menu is not visible, then we can still be showing the activity for the 640 // dismiss overlay, so just finish it after the animation completes 641 postAnimationCallback = new AnimatorListenerAdapter() { 642 @Override 643 public void onAnimationEnd(Animator animation) { 644 mMenuController.hideMenu(); 645 } 646 }; 647 } 648 649 if (isFling) { 650 mMotionHelper.flingToSnapTarget(velocity, vel.x, vel.y, mMovementBounds, 651 mUpdateScrimListener, postAnimationCallback); 652 } else { 653 mMotionHelper.animateToClosestSnapTarget(mMovementBounds, mUpdateScrimListener, 654 postAnimationCallback); 655 } 656 } else if (mIsMinimized) { 657 // This was a tap, so no longer minimized 658 mMotionHelper.animateToClosestSnapTarget(mMovementBounds, null /* updateListener */, 659 null /* animatorListener */); 660 setMinimizedStateInternal(false); 661 } else if (mMenuState != MENU_STATE_FULL) { 662 mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(), 663 mMovementBounds, true /* allowMenuTimeout */); 664 } else { 665 mMenuController.hideMenu(); 666 mMotionHelper.expandPip(); 667 } 668 return true; 669 } 670 }; 671 672 /** 673 * Updates the current movement bounds based on whether the menu is currently visible. 674 */ 675 private void updateMovementBounds(int menuState) { 676 mMovementBounds = menuState == MENU_STATE_FULL 677 ? mExpandedMovementBounds 678 : mNormalMovementBounds; 679 } 680 681 public void dump(PrintWriter pw, String prefix) { 682 final String innerPrefix = prefix + " "; 683 pw.println(prefix + TAG); 684 pw.println(innerPrefix + "mMovementBounds=" + mMovementBounds); 685 pw.println(innerPrefix + "mNormalBounds=" + mNormalBounds); 686 pw.println(innerPrefix + "mNormalMovementBounds=" + mNormalMovementBounds); 687 pw.println(innerPrefix + "mExpandedBounds=" + mExpandedBounds); 688 pw.println(innerPrefix + "mExpandedMovementBounds=" + mExpandedMovementBounds); 689 pw.println(innerPrefix + "mMenuState=" + mMenuState); 690 pw.println(innerPrefix + "mIsMinimized=" + mIsMinimized); 691 pw.println(innerPrefix + "mIsImeShowing=" + mIsImeShowing); 692 pw.println(innerPrefix + "mImeHeight=" + mImeHeight); 693 pw.println(innerPrefix + "mSavedSnapFraction=" + mSavedSnapFraction); 694 pw.println(innerPrefix + "mEnableDragToEdgeDismiss=" + ENABLE_DISMISS_DRAG_TO_EDGE); 695 pw.println(innerPrefix + "mEnableMinimize=" + mEnableMinimize); 696 mSnapAlgorithm.dump(pw, innerPrefix); 697 mTouchState.dump(pw, innerPrefix); 698 mMotionHelper.dump(pw, innerPrefix); 699 } 700 701} 702