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