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