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