• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.systemui.pip.phone;
18 
19 import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
20 import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_CLOSE;
21 import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_FULL;
22 import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_NONE;
23 
24 import android.annotation.SuppressLint;
25 import android.app.IActivityManager;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.content.res.Resources;
29 import android.graphics.PixelFormat;
30 import android.graphics.Point;
31 import android.graphics.PointF;
32 import android.graphics.Rect;
33 import android.graphics.drawable.TransitionDrawable;
34 import android.os.Handler;
35 import android.os.RemoteException;
36 import android.util.Log;
37 import android.util.Size;
38 import android.view.Gravity;
39 import android.view.IPinnedStackController;
40 import android.view.InputEvent;
41 import android.view.MotionEvent;
42 import android.view.View;
43 import android.view.ViewConfiguration;
44 import android.view.ViewGroup;
45 import android.view.WindowManager;
46 import android.view.accessibility.AccessibilityEvent;
47 import android.view.accessibility.AccessibilityManager;
48 import android.view.accessibility.AccessibilityNodeInfo;
49 import android.view.accessibility.AccessibilityWindowInfo;
50 import android.widget.FrameLayout;
51 
52 import androidx.annotation.NonNull;
53 import androidx.dynamicanimation.animation.DynamicAnimation;
54 import androidx.dynamicanimation.animation.SpringForce;
55 
56 import com.android.internal.annotations.VisibleForTesting;
57 import com.android.systemui.R;
58 import com.android.systemui.model.SysUiState;
59 import com.android.systemui.pip.PipAnimationController;
60 import com.android.systemui.pip.PipBoundsHandler;
61 import com.android.systemui.pip.PipSnapAlgorithm;
62 import com.android.systemui.pip.PipTaskOrganizer;
63 import com.android.systemui.pip.PipUiEventLogger;
64 import com.android.systemui.shared.system.InputConsumerController;
65 import com.android.systemui.util.DeviceConfigProxy;
66 import com.android.systemui.util.DismissCircleView;
67 import com.android.systemui.util.FloatingContentCoordinator;
68 import com.android.systemui.util.animation.PhysicsAnimator;
69 import com.android.systemui.util.magnetictarget.MagnetizedObject;
70 
71 import java.io.PrintWriter;
72 
73 import kotlin.Unit;
74 
75 /**
76  * Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding
77  * the PIP.
78  */
79 public class PipTouchHandler {
80     private static final String TAG = "PipTouchHandler";
81 
82     /** Duration of the dismiss scrim fading in/out. */
83     private static final int DISMISS_TRANSITION_DURATION_MS = 200;
84 
85     /* The multiplier to apply scale the target size by when applying the magnetic field radius */
86     private static final float MAGNETIC_FIELD_RADIUS_MULTIPLIER = 1.25f;
87 
88     // Allow dragging the PIP to a location to close it
89     private final boolean mEnableDismissDragToEdge;
90     // Allow PIP to resize to a slightly bigger state upon touch
91     private final boolean mEnableResize;
92     private final Context mContext;
93     private final WindowManager mWindowManager;
94     private final IActivityManager mActivityManager;
95     private final PipBoundsHandler mPipBoundsHandler;
96     private final PipUiEventLogger mPipUiEventLogger;
97 
98     private PipResizeGestureHandler mPipResizeGestureHandler;
99     private IPinnedStackController mPinnedStackController;
100 
101     private final PipMenuActivityController mMenuController;
102     private final PipSnapAlgorithm mSnapAlgorithm;
103     private final AccessibilityManager mAccessibilityManager;
104     private boolean mShowPipMenuOnAnimationEnd = false;
105 
106     /**
107      * MagnetizedObject wrapper for PIP. This allows the magnetic target library to locate and move
108      * PIP.
109      */
110     private MagnetizedObject<Rect> mMagnetizedPip;
111 
112     /**
113      * Container for the dismiss circle, so that it can be animated within the container via
114      * translation rather than within the WindowManager via slow layout animations.
115      */
116     private ViewGroup mTargetViewContainer;
117 
118     /** Circle view used to render the dismiss target. */
119     private DismissCircleView mTargetView;
120 
121     /**
122      * MagneticTarget instance wrapping the target view and allowing us to set its magnetic radius.
123      */
124     private MagnetizedObject.MagneticTarget mMagneticTarget;
125 
126     /** PhysicsAnimator instance for animating the dismiss target in/out. */
127     private PhysicsAnimator<View> mMagneticTargetAnimator;
128 
129     /** Default configuration to use for springing the dismiss target in/out. */
130     private final PhysicsAnimator.SpringConfig mTargetSpringConfig =
131             new PhysicsAnimator.SpringConfig(
132                     SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
133 
134     // The current movement bounds
135     private Rect mMovementBounds = new Rect();
136 
137     // The reference inset bounds, used to determine the dismiss fraction
138     private Rect mInsetBounds = new Rect();
139     // The reference bounds used to calculate the normal/expanded target bounds
140     private Rect mNormalBounds = new Rect();
141     @VisibleForTesting Rect mNormalMovementBounds = new Rect();
142     private Rect mExpandedBounds = new Rect();
143     @VisibleForTesting Rect mExpandedMovementBounds = new Rect();
144     private int mExpandedShortestEdgeSize;
145 
146     // Used to workaround an issue where the WM rotation happens before we are notified, allowing
147     // us to send stale bounds
148     private int mDeferResizeToNormalBoundsUntilRotation = -1;
149     private int mDisplayRotation;
150 
151     /**
152      * Runnable that can be posted delayed to show the target. This needs to be saved as a member
153      * variable so we can pass it to removeCallbacks.
154      */
155     private Runnable mShowTargetAction = this::showDismissTargetMaybe;
156 
157     private Handler mHandler = new Handler();
158 
159     // Behaviour states
160     private int mMenuState = MENU_STATE_NONE;
161     private boolean mIsImeShowing;
162     private int mImeHeight;
163     private int mImeOffset;
164     private int mDismissAreaHeight;
165     private boolean mIsShelfShowing;
166     private int mShelfHeight;
167     private int mMovementBoundsExtraOffsets;
168     private int mBottomOffsetBufferPx;
169     private float mSavedSnapFraction = -1f;
170     private boolean mSendingHoverAccessibilityEvents;
171     private boolean mMovementWithinDismiss;
172     private PipAccessibilityInteractionConnection mConnection;
173 
174     // Touch state
175     private final PipTouchState mTouchState;
176     private final FloatingContentCoordinator mFloatingContentCoordinator;
177     private PipMotionHelper mMotionHelper;
178     private PipTouchGesture mGesture;
179 
180     // Temp vars
181     private final Rect mTmpBounds = new Rect();
182 
183     /**
184      * A listener for the PIP menu activity.
185      */
186     private class PipMenuListener implements PipMenuActivityController.Listener {
187         @Override
onPipMenuStateChanged(int menuState, boolean resize, Runnable callback)188         public void onPipMenuStateChanged(int menuState, boolean resize, Runnable callback) {
189             setMenuState(menuState, resize, callback);
190         }
191 
192         @Override
onPipExpand()193         public void onPipExpand() {
194             mMotionHelper.expandPipToFullscreen();
195         }
196 
197         @Override
onPipDismiss()198         public void onPipDismiss() {
199             mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_TAP_TO_REMOVE);
200             mTouchState.removeDoubleTapTimeoutCallback();
201             mMotionHelper.dismissPip();
202         }
203 
204         @Override
onPipShowMenu()205         public void onPipShowMenu() {
206             mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(),
207                     true /* allowMenuTimeout */, willResizeMenu(), shouldShowResizeHandle());
208         }
209     }
210 
211     @SuppressLint("InflateParams")
PipTouchHandler(Context context, IActivityManager activityManager, PipMenuActivityController menuController, InputConsumerController inputConsumerController, PipBoundsHandler pipBoundsHandler, PipTaskOrganizer pipTaskOrganizer, FloatingContentCoordinator floatingContentCoordinator, DeviceConfigProxy deviceConfig, PipSnapAlgorithm pipSnapAlgorithm, SysUiState sysUiState, PipUiEventLogger pipUiEventLogger)212     public PipTouchHandler(Context context, IActivityManager activityManager,
213             PipMenuActivityController menuController,
214             InputConsumerController inputConsumerController,
215             PipBoundsHandler pipBoundsHandler,
216             PipTaskOrganizer pipTaskOrganizer,
217             FloatingContentCoordinator floatingContentCoordinator,
218             DeviceConfigProxy deviceConfig,
219             PipSnapAlgorithm pipSnapAlgorithm,
220             SysUiState sysUiState,
221             PipUiEventLogger pipUiEventLogger) {
222         // Initialize the Pip input consumer
223         mContext = context;
224         mActivityManager = activityManager;
225         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
226         mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
227         mMenuController = menuController;
228         mMenuController.addListener(new PipMenuListener());
229         mSnapAlgorithm = pipSnapAlgorithm;
230         mGesture = new DefaultPipTouchGesture();
231         mMotionHelper = new PipMotionHelper(mContext, pipTaskOrganizer, mMenuController,
232                 mSnapAlgorithm, floatingContentCoordinator);
233         mPipResizeGestureHandler =
234                 new PipResizeGestureHandler(context, pipBoundsHandler, mMotionHelper,
235                         deviceConfig, pipTaskOrganizer, menuController, this::getMovementBounds,
236                         this::updateMovementBounds, sysUiState, pipUiEventLogger);
237         mTouchState = new PipTouchState(ViewConfiguration.get(context), mHandler,
238                 () -> mMenuController.showMenuWithDelay(MENU_STATE_FULL, mMotionHelper.getBounds(),
239                         true /* allowMenuTimeout */, willResizeMenu(), shouldShowResizeHandle()),
240                         menuController::hideMenu);
241 
242         Resources res = context.getResources();
243         mEnableDismissDragToEdge = res.getBoolean(R.bool.config_pipEnableDismissDragToEdge);
244         mEnableResize = res.getBoolean(R.bool.config_pipEnableResizeForMenu);
245         reloadResources();
246 
247         // Register the listener for input consumer touch events
248         inputConsumerController.setInputListener(this::handleTouchEvent);
249         inputConsumerController.setRegistrationListener(this::onRegistrationChanged);
250 
251         mPipBoundsHandler = pipBoundsHandler;
252         mFloatingContentCoordinator = floatingContentCoordinator;
253         mConnection = new PipAccessibilityInteractionConnection(mContext, mMotionHelper,
254                 pipTaskOrganizer, pipSnapAlgorithm, this::onAccessibilityShowMenu,
255                 this::updateMovementBounds, mHandler);
256 
257         mPipUiEventLogger = pipUiEventLogger;
258 
259         mTargetView = new DismissCircleView(context);
260         mTargetViewContainer = new FrameLayout(context);
261         mTargetViewContainer.setBackgroundDrawable(
262                 context.getDrawable(R.drawable.floating_dismiss_gradient_transition));
263         mTargetViewContainer.setClipChildren(false);
264         mTargetViewContainer.addView(mTargetView);
265 
266         mMagnetizedPip = mMotionHelper.getMagnetizedPip();
267         mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0);
268         updateMagneticTargetSize();
269 
270         mMagnetizedPip.setAnimateStuckToTarget(
271                 (target, velX, velY, flung, after) -> {
272                     mMotionHelper.animateIntoDismissTarget(target, velX, velY, flung, after);
273                     return Unit.INSTANCE;
274                 });
275         mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() {
276             @Override
277             public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
278                 // Show the dismiss target, in case the initial touch event occurred within the
279                 // magnetic field radius.
280                 showDismissTargetMaybe();
281             }
282 
283             @Override
284             public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
285                     float velX, float velY, boolean wasFlungOut) {
286                 if (wasFlungOut) {
287                     mMotionHelper.flingToSnapTarget(velX, velY, null, null);
288                     hideDismissTarget();
289                 } else {
290                     mMotionHelper.setSpringingToTouch(true);
291                 }
292             }
293 
294             @Override
295             public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
296                 mMotionHelper.notifyDismissalPending();
297 
298                 mHandler.post(() -> {
299                     mMotionHelper.animateDismiss();
300                     hideDismissTarget();
301                 });
302 
303                 mPipUiEventLogger.log(
304                         PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_DRAG_TO_REMOVE);
305             }
306         });
307 
308         mMagneticTargetAnimator = PhysicsAnimator.getInstance(mTargetView);
309     }
310 
reloadResources()311     private void reloadResources() {
312         final Resources res = mContext.getResources();
313         mBottomOffsetBufferPx = res.getDimensionPixelSize(R.dimen.pip_bottom_offset_buffer);
314         mExpandedShortestEdgeSize = res.getDimensionPixelSize(
315                 R.dimen.pip_expanded_shortest_edge_size);
316         mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);
317         mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height);
318         updateMagneticTargetSize();
319     }
320 
updateMagneticTargetSize()321     private void updateMagneticTargetSize() {
322         if (mTargetView == null) {
323             return;
324         }
325 
326         final Resources res = mContext.getResources();
327         final int targetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size);
328         final FrameLayout.LayoutParams newParams =
329                 new FrameLayout.LayoutParams(targetSize, targetSize);
330         newParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
331         newParams.bottomMargin = mContext.getResources().getDimensionPixelSize(
332                 R.dimen.floating_dismiss_bottom_margin);
333         mTargetView.setLayoutParams(newParams);
334 
335         // Set the magnetic field radius equal to the target size from the center of the target
336         mMagneticTarget.setMagneticFieldRadiusPx(
337                 (int) (targetSize * MAGNETIC_FIELD_RADIUS_MULTIPLIER));
338     }
339 
shouldShowResizeHandle()340     private boolean shouldShowResizeHandle() {
341             return !mPipBoundsHandler.hasSaveReentryBounds();
342     }
343 
setTouchGesture(PipTouchGesture gesture)344     public void setTouchGesture(PipTouchGesture gesture) {
345         mGesture = gesture;
346     }
347 
setTouchEnabled(boolean enabled)348     public void setTouchEnabled(boolean enabled) {
349         mTouchState.setAllowTouches(enabled);
350     }
351 
showPictureInPictureMenu()352     public void showPictureInPictureMenu() {
353         // Only show the menu if the user isn't currently interacting with the PiP
354         if (!mTouchState.isUserInteracting()) {
355             mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(),
356                     false /* allowMenuTimeout */, willResizeMenu(),
357                     shouldShowResizeHandle());
358         }
359     }
360 
onActivityPinned()361     public void onActivityPinned() {
362         createOrUpdateDismissTarget();
363 
364         mShowPipMenuOnAnimationEnd = true;
365         mPipResizeGestureHandler.onActivityPinned();
366         mFloatingContentCoordinator.onContentAdded(mMotionHelper);
367     }
368 
onActivityUnpinned(ComponentName topPipActivity)369     public void onActivityUnpinned(ComponentName topPipActivity) {
370         if (topPipActivity == null) {
371             // Clean up state after the last PiP activity is removed
372             cleanUpDismissTarget();
373 
374             mFloatingContentCoordinator.onContentRemoved(mMotionHelper);
375         }
376         mPipResizeGestureHandler.onActivityUnpinned();
377     }
378 
onPinnedStackAnimationEnded( @ipAnimationController.TransitionDirection int direction)379     public void onPinnedStackAnimationEnded(
380             @PipAnimationController.TransitionDirection int direction) {
381         // Always synchronize the motion helper bounds once PiP animations finish
382         mMotionHelper.synchronizePinnedStackBounds();
383         updateMovementBounds();
384         if (direction == TRANSITION_DIRECTION_TO_PIP) {
385             // Set the initial bounds as the user resize bounds.
386             mPipResizeGestureHandler.setUserResizeBounds(mMotionHelper.getBounds());
387         }
388 
389         if (mShowPipMenuOnAnimationEnd) {
390             mMenuController.showMenu(MENU_STATE_CLOSE, mMotionHelper.getBounds(),
391                     true /* allowMenuTimeout */, false /* willResizeMenu */,
392                     shouldShowResizeHandle());
393             mShowPipMenuOnAnimationEnd = false;
394         }
395     }
396 
onConfigurationChanged()397     public void onConfigurationChanged() {
398         mPipResizeGestureHandler.onConfigurationChanged();
399         mMotionHelper.synchronizePinnedStackBounds();
400         reloadResources();
401 
402         // Recreate the dismiss target for the new orientation.
403         createOrUpdateDismissTarget();
404     }
405 
onImeVisibilityChanged(boolean imeVisible, int imeHeight)406     public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
407         mIsImeShowing = imeVisible;
408         mImeHeight = imeHeight;
409     }
410 
onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight)411     public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
412         mIsShelfShowing = shelfVisible;
413         mShelfHeight = shelfHeight;
414     }
415 
adjustBoundsForRotation(Rect outBounds, Rect curBounds, Rect insetBounds)416     public void adjustBoundsForRotation(Rect outBounds, Rect curBounds, Rect insetBounds) {
417         final Rect toMovementBounds = new Rect();
418         mSnapAlgorithm.getMovementBounds(outBounds, insetBounds, toMovementBounds, 0);
419         final int prevBottom = mMovementBounds.bottom - mMovementBoundsExtraOffsets;
420         if ((prevBottom - mBottomOffsetBufferPx) <= curBounds.top) {
421             outBounds.offsetTo(outBounds.left, toMovementBounds.bottom);
422         }
423     }
424 
425     /**
426      * Responds to IPinnedStackListener on resetting aspect ratio for the pinned window.
427      */
onAspectRatioChanged()428     public void onAspectRatioChanged() {
429         mPipResizeGestureHandler.invalidateUserResizeBounds();
430     }
431 
onMovementBoundsChanged(Rect insetBounds, Rect normalBounds, Rect curBounds, boolean fromImeAdjustment, boolean fromShelfAdjustment, int displayRotation)432     public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds, Rect curBounds,
433             boolean fromImeAdjustment, boolean fromShelfAdjustment, int displayRotation) {
434         // Set the user resized bounds equal to the new normal bounds in case they were
435         // invalidated (e.g. by an aspect ratio change).
436         if (mPipResizeGestureHandler.getUserResizeBounds().isEmpty()) {
437             mPipResizeGestureHandler.setUserResizeBounds(normalBounds);
438         }
439 
440         final int bottomOffset = mIsImeShowing ? mImeHeight : 0;
441         final boolean fromDisplayRotationChanged = (mDisplayRotation != displayRotation);
442         if (fromDisplayRotationChanged) {
443             mTouchState.reset();
444         }
445 
446         // Re-calculate the expanded bounds
447         mNormalBounds.set(normalBounds);
448         Rect normalMovementBounds = new Rect();
449         mSnapAlgorithm.getMovementBounds(mNormalBounds, insetBounds, normalMovementBounds,
450                 bottomOffset);
451 
452         if (mMovementBounds.isEmpty()) {
453             // mMovementBounds is not initialized yet and a clean movement bounds without
454             // bottom offset shall be used later in this function.
455             mSnapAlgorithm.getMovementBounds(curBounds, insetBounds, mMovementBounds,
456                     0 /* bottomOffset */);
457         }
458 
459         // Calculate the expanded size
460         float aspectRatio = (float) normalBounds.width() / normalBounds.height();
461         Point displaySize = new Point();
462         mContext.getDisplay().getRealSize(displaySize);
463         Size expandedSize = mSnapAlgorithm.getSizeForAspectRatio(aspectRatio,
464                 mExpandedShortestEdgeSize, displaySize.x, displaySize.y);
465         mExpandedBounds.set(0, 0, expandedSize.getWidth(), expandedSize.getHeight());
466         Rect expandedMovementBounds = new Rect();
467         mSnapAlgorithm.getMovementBounds(mExpandedBounds, insetBounds, expandedMovementBounds,
468                 bottomOffset);
469 
470         mPipResizeGestureHandler.updateMinSize(mNormalBounds.width(), mNormalBounds.height());
471         mPipResizeGestureHandler.updateMaxSize(mExpandedBounds.width(), mExpandedBounds.height());
472 
473         // The extra offset does not really affect the movement bounds, but are applied based on the
474         // current state (ime showing, or shelf offset) when we need to actually shift
475         int extraOffset = Math.max(
476                 mIsImeShowing ? mImeOffset : 0,
477                 !mIsImeShowing && mIsShelfShowing ? mShelfHeight : 0);
478 
479         // If this is from an IME or shelf adjustment, then we should move the PiP so that it is not
480         // occluded by the IME or shelf.
481         if (fromImeAdjustment || fromShelfAdjustment) {
482             if (mTouchState.isUserInteracting()) {
483                 // Defer the update of the current movement bounds until after the user finishes
484                 // touching the screen
485             } else {
486                 final boolean isExpanded = mMenuState == MENU_STATE_FULL && willResizeMenu();
487                 final Rect toMovementBounds = new Rect();
488                 mSnapAlgorithm.getMovementBounds(curBounds, insetBounds,
489                         toMovementBounds, mIsImeShowing ? mImeHeight : 0);
490                 final int prevBottom = mMovementBounds.bottom - mMovementBoundsExtraOffsets;
491                 // This is to handle landscape fullscreen IMEs, don't apply the extra offset in this
492                 // case
493                 final int toBottom = toMovementBounds.bottom < toMovementBounds.top
494                         ? toMovementBounds.bottom
495                         : toMovementBounds.bottom - extraOffset;
496 
497                 if (isExpanded) {
498                     curBounds.set(mExpandedBounds);
499                     mSnapAlgorithm.applySnapFraction(curBounds, toMovementBounds,
500                             mSavedSnapFraction);
501                 }
502 
503                 if (prevBottom < toBottom) {
504                     // The movement bounds are expanding
505                     if (curBounds.top > prevBottom - mBottomOffsetBufferPx) {
506                         mMotionHelper.animateToOffset(curBounds, toBottom - curBounds.top);
507                     }
508                 } else if (prevBottom > toBottom) {
509                     // The movement bounds are shrinking
510                     if (curBounds.top > toBottom - mBottomOffsetBufferPx) {
511                         mMotionHelper.animateToOffset(curBounds, toBottom - curBounds.top);
512                     }
513                 }
514             }
515         }
516 
517         // Update the movement bounds after doing the calculations based on the old movement bounds
518         // above
519         mNormalMovementBounds.set(normalMovementBounds);
520         mExpandedMovementBounds.set(expandedMovementBounds);
521         mDisplayRotation = displayRotation;
522         mInsetBounds.set(insetBounds);
523         updateMovementBounds();
524         mMovementBoundsExtraOffsets = extraOffset;
525         mConnection.onMovementBoundsChanged(mNormalBounds, mExpandedBounds, mNormalMovementBounds,
526                 mExpandedMovementBounds);
527 
528         // If we have a deferred resize, apply it now
529         if (mDeferResizeToNormalBoundsUntilRotation == displayRotation) {
530             mMotionHelper.animateToUnexpandedState(normalBounds, mSavedSnapFraction,
531                     mNormalMovementBounds, mMovementBounds, true /* immediate */);
532             mSavedSnapFraction = -1f;
533             mDeferResizeToNormalBoundsUntilRotation = -1;
534         }
535     }
536 
537     /** Adds the magnetic target view to the WindowManager so it's ready to be animated in. */
createOrUpdateDismissTarget()538     private void createOrUpdateDismissTarget() {
539         if (!mTargetViewContainer.isAttachedToWindow()) {
540             mHandler.removeCallbacks(mShowTargetAction);
541             mMagneticTargetAnimator.cancel();
542 
543             mTargetViewContainer.setVisibility(View.INVISIBLE);
544 
545             try {
546                 mWindowManager.addView(mTargetViewContainer, getDismissTargetLayoutParams());
547             } catch (IllegalStateException e) {
548                 // This shouldn't happen, but if the target is already added, just update its layout
549                 // params.
550                 mWindowManager.updateViewLayout(
551                         mTargetViewContainer, getDismissTargetLayoutParams());
552             }
553         } else {
554             mWindowManager.updateViewLayout(mTargetViewContainer, getDismissTargetLayoutParams());
555         }
556     }
557 
558     /** Returns layout params for the dismiss target, using the latest display metrics. */
getDismissTargetLayoutParams()559     private WindowManager.LayoutParams getDismissTargetLayoutParams() {
560         final Point windowSize = new Point();
561         mWindowManager.getDefaultDisplay().getRealSize(windowSize);
562 
563         final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
564                 WindowManager.LayoutParams.MATCH_PARENT,
565                 mDismissAreaHeight,
566                 0, windowSize.y - mDismissAreaHeight,
567                 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
568                 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
569                         | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
570                         | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
571                 PixelFormat.TRANSLUCENT);
572 
573         lp.setTitle("pip-dismiss-overlay");
574         lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
575         lp.setFitInsetsTypes(0 /* types */);
576 
577         return lp;
578     }
579 
580     /** Makes the dismiss target visible and animates it in, if it isn't already visible. */
showDismissTargetMaybe()581     private void showDismissTargetMaybe() {
582         createOrUpdateDismissTarget();
583 
584         if (mTargetViewContainer.getVisibility() != View.VISIBLE) {
585 
586             mTargetView.setTranslationY(mTargetViewContainer.getHeight());
587             mTargetViewContainer.setVisibility(View.VISIBLE);
588 
589             // Cancel in case we were in the middle of animating it out.
590             mMagneticTargetAnimator.cancel();
591             mMagneticTargetAnimator
592                     .spring(DynamicAnimation.TRANSLATION_Y, 0f, mTargetSpringConfig)
593                     .start();
594 
595             ((TransitionDrawable) mTargetViewContainer.getBackground()).startTransition(
596                     DISMISS_TRANSITION_DURATION_MS);
597         }
598     }
599 
600     /** Animates the magnetic dismiss target out and then sets it to GONE. */
hideDismissTarget()601     private void hideDismissTarget() {
602         mHandler.removeCallbacks(mShowTargetAction);
603         mMagneticTargetAnimator
604                 .spring(DynamicAnimation.TRANSLATION_Y,
605                         mTargetViewContainer.getHeight(),
606                         mTargetSpringConfig)
607                 .withEndActions(() ->  mTargetViewContainer.setVisibility(View.GONE))
608                 .start();
609 
610         ((TransitionDrawable) mTargetViewContainer.getBackground()).reverseTransition(
611                 DISMISS_TRANSITION_DURATION_MS);
612     }
613 
614     /**
615      * Removes the dismiss target and cancels any pending callbacks to show it.
616      */
cleanUpDismissTarget()617     private void cleanUpDismissTarget() {
618         mHandler.removeCallbacks(mShowTargetAction);
619 
620         if (mTargetViewContainer.isAttachedToWindow()) {
621             mWindowManager.removeViewImmediate(mTargetViewContainer);
622         }
623     }
624 
onRegistrationChanged(boolean isRegistered)625     private void onRegistrationChanged(boolean isRegistered) {
626         mAccessibilityManager.setPictureInPictureActionReplacingConnection(isRegistered
627                 ? mConnection : null);
628         if (!isRegistered && mTouchState.isUserInteracting()) {
629             // If the input consumer is unregistered while the user is interacting, then we may not
630             // get the final TOUCH_UP event, so clean up the dismiss target as well
631             cleanUpDismissTarget();
632         }
633     }
634 
onAccessibilityShowMenu()635     private void onAccessibilityShowMenu() {
636         mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(),
637                 true /* allowMenuTimeout */, willResizeMenu(),
638                 shouldShowResizeHandle());
639     }
640 
handleTouchEvent(InputEvent inputEvent)641     private boolean handleTouchEvent(InputEvent inputEvent) {
642         // Skip any non motion events
643         if (!(inputEvent instanceof MotionEvent)) {
644             return true;
645         }
646         // Skip touch handling until we are bound to the controller
647         if (mPinnedStackController == null) {
648             return true;
649         }
650 
651         MotionEvent ev = (MotionEvent) inputEvent;
652         if (ev.getActionMasked() == MotionEvent.ACTION_DOWN
653                 && mPipResizeGestureHandler.willStartResizeGesture(ev)) {
654             // Initialize the touch state for the gesture, but immediately reset to invalidate the
655             // gesture
656             mTouchState.onTouchEvent(ev);
657             mTouchState.reset();
658             return true;
659         }
660 
661         if ((ev.getAction() == MotionEvent.ACTION_DOWN || mTouchState.isUserInteracting())
662                 && mMagnetizedPip.maybeConsumeMotionEvent(ev)) {
663             // If the first touch event occurs within the magnetic field, pass the ACTION_DOWN event
664             // to the touch state. Touch state needs a DOWN event in order to later process MOVE
665             // events it'll receive if the object is dragged out of the magnetic field.
666             if (ev.getAction() == MotionEvent.ACTION_DOWN) {
667                 mTouchState.onTouchEvent(ev);
668             }
669 
670             // Continue tracking velocity when the object is in the magnetic field, since we want to
671             // respect touch input velocity if the object is dragged out and then flung.
672             mTouchState.addMovementToVelocityTracker(ev);
673 
674             return true;
675         }
676 
677         // Update the touch state
678         mTouchState.onTouchEvent(ev);
679 
680         boolean shouldDeliverToMenu = mMenuState != MENU_STATE_NONE;
681 
682         switch (ev.getAction()) {
683             case MotionEvent.ACTION_DOWN: {
684                 mGesture.onDown(mTouchState);
685                 break;
686             }
687             case MotionEvent.ACTION_MOVE: {
688                 if (mGesture.onMove(mTouchState)) {
689                     break;
690                 }
691 
692                 shouldDeliverToMenu = !mTouchState.isDragging();
693                 break;
694             }
695             case MotionEvent.ACTION_UP: {
696                 // Update the movement bounds again if the state has changed since the user started
697                 // dragging (ie. when the IME shows)
698                 updateMovementBounds();
699 
700                 if (mGesture.onUp(mTouchState)) {
701                     break;
702                 }
703 
704                 // Fall through to clean up
705             }
706             case MotionEvent.ACTION_CANCEL: {
707                 shouldDeliverToMenu = !mTouchState.startedDragging() && !mTouchState.isDragging();
708                 mTouchState.reset();
709                 break;
710             }
711             case MotionEvent.ACTION_HOVER_ENTER:
712                 // If Touch Exploration is enabled, some a11y services (e.g. Talkback) is probably
713                 // on and changing MotionEvents into HoverEvents.
714                 // Let's not enable menu show/hide for a11y services.
715                 if (!mAccessibilityManager.isTouchExplorationEnabled()) {
716                     mTouchState.removeHoverExitTimeoutCallback();
717                     mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(),
718                             false /* allowMenuTimeout */, false /* willResizeMenu */,
719                             shouldShowResizeHandle());
720                 }
721             case MotionEvent.ACTION_HOVER_MOVE: {
722                 if (!shouldDeliverToMenu && !mSendingHoverAccessibilityEvents) {
723                     sendAccessibilityHoverEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
724                     mSendingHoverAccessibilityEvents = true;
725                 }
726                 break;
727             }
728             case MotionEvent.ACTION_HOVER_EXIT: {
729                 // If Touch Exploration is enabled, some a11y services (e.g. Talkback) is probably
730                 // on and changing MotionEvents into HoverEvents.
731                 // Let's not enable menu show/hide for a11y services.
732                 if (!mAccessibilityManager.isTouchExplorationEnabled()) {
733                     mTouchState.scheduleHoverExitTimeoutCallback();
734                 }
735                 if (!shouldDeliverToMenu && mSendingHoverAccessibilityEvents) {
736                     sendAccessibilityHoverEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
737                     mSendingHoverAccessibilityEvents = false;
738                 }
739                 break;
740             }
741         }
742 
743         // Deliver the event to PipMenuActivity to handle button click if the menu has shown.
744         if (shouldDeliverToMenu) {
745             final MotionEvent cloneEvent = MotionEvent.obtain(ev);
746             // Send the cancel event and cancel menu timeout if it starts to drag.
747             if (mTouchState.startedDragging()) {
748                 cloneEvent.setAction(MotionEvent.ACTION_CANCEL);
749                 mMenuController.pokeMenu();
750             }
751 
752             mMenuController.handlePointerEvent(cloneEvent);
753         }
754 
755         return true;
756     }
757 
sendAccessibilityHoverEvent(int type)758     private void sendAccessibilityHoverEvent(int type) {
759         if (!mAccessibilityManager.isEnabled()) {
760             return;
761         }
762 
763         AccessibilityEvent event = AccessibilityEvent.obtain(type);
764         event.setImportantForAccessibility(true);
765         event.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID);
766         event.setWindowId(
767                 AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID);
768         mAccessibilityManager.sendAccessibilityEvent(event);
769     }
770 
771     /**
772      * Updates the appearance of the menu and scrim on top of the PiP while dismissing.
773      */
updateDismissFraction()774     private void updateDismissFraction() {
775         // Skip updating the dismiss fraction when the IME is showing. This is to work around an
776         // issue where starting the menu activity for the dismiss overlay will steal the window
777         // focus, which closes the IME.
778         if (mMenuController != null && !mIsImeShowing) {
779             Rect bounds = mMotionHelper.getBounds();
780             final float target = mInsetBounds.bottom;
781             float fraction = 0f;
782             if (bounds.bottom > target) {
783                 final float distance = bounds.bottom - target;
784                 fraction = Math.min(distance / bounds.height(), 1f);
785             }
786             if (Float.compare(fraction, 0f) != 0 || mMenuController.isMenuActivityVisible()) {
787                 // Update if the fraction > 0, or if fraction == 0 and the menu was already visible
788                 mMenuController.setDismissFraction(fraction);
789             }
790         }
791     }
792 
793     /**
794      * Sets the controller to update the system of changes from user interaction.
795      */
setPinnedStackController(IPinnedStackController controller)796     void setPinnedStackController(IPinnedStackController controller) {
797         mPinnedStackController = controller;
798     }
799 
800     /**
801      * Sets the menu visibility.
802      */
setMenuState(int menuState, boolean resize, Runnable callback)803     private void setMenuState(int menuState, boolean resize, Runnable callback) {
804         if (mMenuState == menuState && !resize) {
805             return;
806         }
807 
808         if (menuState == MENU_STATE_FULL && mMenuState != MENU_STATE_FULL) {
809             // Save the current snap fraction and if we do not drag or move the PiP, then
810             // we store back to this snap fraction.  Otherwise, we'll reset the snap
811             // fraction and snap to the closest edge.
812             if (resize) {
813                 Rect expandedBounds = new Rect(mExpandedBounds);
814                 mSavedSnapFraction = mMotionHelper.animateToExpandedState(expandedBounds,
815                         mMovementBounds, mExpandedMovementBounds, callback);
816             }
817         } else if (menuState == MENU_STATE_NONE && mMenuState == MENU_STATE_FULL) {
818             // Try and restore the PiP to the closest edge, using the saved snap fraction
819             // if possible
820             if (resize) {
821                 if (mDeferResizeToNormalBoundsUntilRotation == -1) {
822                     // This is a very special case: when the menu is expanded and visible,
823                     // navigating to another activity can trigger auto-enter PiP, and if the
824                     // revealed activity has a forced rotation set, then the controller will get
825                     // updated with the new rotation of the display. However, at the same time,
826                     // SystemUI will try to hide the menu by creating an animation to the normal
827                     // bounds which are now stale.  In such a case we defer the animation to the
828                     // normal bounds until after the next onMovementBoundsChanged() call to get the
829                     // bounds in the new orientation
830                     try {
831                         int displayRotation = mPinnedStackController.getDisplayRotation();
832                         if (mDisplayRotation != displayRotation) {
833                             mDeferResizeToNormalBoundsUntilRotation = displayRotation;
834                         }
835                     } catch (RemoteException e) {
836                         Log.e(TAG, "Could not get display rotation from controller");
837                     }
838                 }
839 
840                 if (mDeferResizeToNormalBoundsUntilRotation == -1) {
841                     Rect restoreBounds = new Rect(getUserResizeBounds());
842                     Rect restoredMovementBounds = new Rect();
843                     mSnapAlgorithm.getMovementBounds(restoreBounds, mInsetBounds,
844                             restoredMovementBounds, mIsImeShowing ? mImeHeight : 0);
845                     mMotionHelper.animateToUnexpandedState(restoreBounds, mSavedSnapFraction,
846                             restoredMovementBounds, mMovementBounds, false /* immediate */);
847                     mSavedSnapFraction = -1f;
848                 }
849             } else {
850                 mSavedSnapFraction = -1f;
851             }
852         }
853         mMenuState = menuState;
854         updateMovementBounds();
855         // If pip menu has dismissed, we should register the A11y ActionReplacingConnection for pip
856         // as well, or it can't handle a11y focus and pip menu can't perform any action.
857         onRegistrationChanged(menuState == MENU_STATE_NONE);
858         if (menuState == MENU_STATE_NONE) {
859             mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_HIDE_MENU);
860         } else if (menuState == MENU_STATE_FULL) {
861             mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_SHOW_MENU);
862         }
863     }
864 
865     /**
866      * @return the motion helper.
867      */
getMotionHelper()868     public PipMotionHelper getMotionHelper() {
869         return mMotionHelper;
870     }
871 
872     @VisibleForTesting
getPipResizeGestureHandler()873     PipResizeGestureHandler getPipResizeGestureHandler() {
874         return mPipResizeGestureHandler;
875     }
876 
877     @VisibleForTesting
setPipResizeGestureHandler(PipResizeGestureHandler pipResizeGestureHandler)878     void setPipResizeGestureHandler(PipResizeGestureHandler pipResizeGestureHandler) {
879         mPipResizeGestureHandler = pipResizeGestureHandler;
880     }
881 
882     @VisibleForTesting
setPipMotionHelper(PipMotionHelper pipMotionHelper)883     void setPipMotionHelper(PipMotionHelper pipMotionHelper) {
884         mMotionHelper = pipMotionHelper;
885     }
886 
887     /**
888      * @return the unexpanded bounds.
889      */
getNormalBounds()890     public Rect getNormalBounds() {
891         return mNormalBounds;
892     }
893 
getUserResizeBounds()894     Rect getUserResizeBounds() {
895         return mPipResizeGestureHandler.getUserResizeBounds();
896     }
897 
898     /**
899      * Gesture controlling normal movement of the PIP.
900      */
901     private class DefaultPipTouchGesture extends PipTouchGesture {
902         private final Point mStartPosition = new Point();
903         private final PointF mDelta = new PointF();
904         private boolean mShouldHideMenuAfterFling;
905 
906         @Override
onDown(PipTouchState touchState)907         public void onDown(PipTouchState touchState) {
908             if (!touchState.isUserInteracting()) {
909                 return;
910             }
911 
912             Rect bounds = mMotionHelper.getPossiblyAnimatingBounds();
913             mDelta.set(0f, 0f);
914             mStartPosition.set(bounds.left, bounds.top);
915             mMovementWithinDismiss = touchState.getDownTouchPosition().y >= mMovementBounds.bottom;
916             mMotionHelper.setSpringingToTouch(false);
917 
918             // If the menu is still visible then just poke the menu
919             // so that it will timeout after the user stops touching it
920             if (mMenuState != MENU_STATE_NONE) {
921                 mMenuController.pokeMenu();
922             }
923         }
924 
925         @Override
onMove(PipTouchState touchState)926         public boolean onMove(PipTouchState touchState) {
927             if (!touchState.isUserInteracting()) {
928                 return false;
929             }
930 
931             if (touchState.startedDragging()) {
932                 mSavedSnapFraction = -1f;
933 
934                 if (mEnableDismissDragToEdge) {
935                     if (mTargetViewContainer.getVisibility() != View.VISIBLE) {
936                         mHandler.removeCallbacks(mShowTargetAction);
937                         showDismissTargetMaybe();
938                     }
939                 }
940             }
941 
942             if (touchState.isDragging()) {
943                 // Move the pinned stack freely
944                 final PointF lastDelta = touchState.getLastTouchDelta();
945                 float lastX = mStartPosition.x + mDelta.x;
946                 float lastY = mStartPosition.y + mDelta.y;
947                 float left = lastX + lastDelta.x;
948                 float top = lastY + lastDelta.y;
949 
950                 // Add to the cumulative delta after bounding the position
951                 mDelta.x += left - lastX;
952                 mDelta.y += top - lastY;
953 
954                 mTmpBounds.set(mMotionHelper.getPossiblyAnimatingBounds());
955                 mTmpBounds.offsetTo((int) left, (int) top);
956                 mMotionHelper.movePip(mTmpBounds, true /* isDragging */);
957 
958                 final PointF curPos = touchState.getLastTouchPosition();
959                 if (mMovementWithinDismiss) {
960                     // Track if movement remains near the bottom edge to identify swipe to dismiss
961                     mMovementWithinDismiss = curPos.y >= mMovementBounds.bottom;
962                 }
963                 return true;
964             }
965             return false;
966         }
967 
968         @Override
onUp(PipTouchState touchState)969         public boolean onUp(PipTouchState touchState) {
970             if (mEnableDismissDragToEdge) {
971                 hideDismissTarget();
972             }
973 
974             if (!touchState.isUserInteracting()) {
975                 return false;
976             }
977 
978             final PointF vel = touchState.getVelocity();
979 
980             if (touchState.isDragging()) {
981                 if (mMenuState != MENU_STATE_NONE) {
982                     // If the menu is still visible, then just poke the menu so that
983                     // it will timeout after the user stops touching it
984                     mMenuController.showMenu(mMenuState, mMotionHelper.getBounds(),
985                             true /* allowMenuTimeout */, willResizeMenu(),
986                             shouldShowResizeHandle());
987                 }
988                 mShouldHideMenuAfterFling = mMenuState == MENU_STATE_NONE;
989 
990                 // Reset the touch state on up before the fling settles
991                 mTouchState.reset();
992                 mMotionHelper.flingToSnapTarget(vel.x, vel.y,
993                         PipTouchHandler.this::updateDismissFraction /* updateAction */,
994                         this::flingEndAction /* endAction */);
995             } else if (mTouchState.isDoubleTap()) {
996                 // Expand to fullscreen if this is a double tap
997                 // the PiP should be frozen until the transition ends
998                 setTouchEnabled(false);
999                 mMotionHelper.expandPipToFullscreen();
1000             } else if (mMenuState != MENU_STATE_FULL) {
1001                 if (!mTouchState.isWaitingForDoubleTap()) {
1002                     // User has stalled long enough for this not to be a drag or a double tap, just
1003                     // expand the menu
1004                     mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(),
1005                             true /* allowMenuTimeout */, willResizeMenu(),
1006                             shouldShowResizeHandle());
1007                 } else {
1008                     // Next touch event _may_ be the second tap for the double-tap, schedule a
1009                     // fallback runnable to trigger the menu if no touch event occurs before the
1010                     // next tap
1011                     mTouchState.scheduleDoubleTapTimeoutCallback();
1012                 }
1013             }
1014             return true;
1015         }
1016 
flingEndAction()1017         private void flingEndAction() {
1018             if (mShouldHideMenuAfterFling) {
1019                 // If the menu is not visible, then we can still be showing the activity for the
1020                 // dismiss overlay, so just finish it after the animation completes
1021                 mMenuController.hideMenu();
1022             }
1023         }
1024     };
1025 
1026     /**
1027      * Updates the current movement bounds based on whether the menu is currently visible and
1028      * resized.
1029      */
updateMovementBounds()1030     private void updateMovementBounds() {
1031         mSnapAlgorithm.getMovementBounds(mMotionHelper.getBounds(), mInsetBounds,
1032                 mMovementBounds, mIsImeShowing ? mImeHeight : 0);
1033         mMotionHelper.setCurrentMovementBounds(mMovementBounds);
1034 
1035         boolean isMenuExpanded = mMenuState == MENU_STATE_FULL;
1036         mPipBoundsHandler.setMinEdgeSize(
1037                 isMenuExpanded  && willResizeMenu() ? mExpandedShortestEdgeSize : 0);
1038     }
1039 
getMovementBounds(Rect curBounds)1040     private Rect getMovementBounds(Rect curBounds) {
1041         Rect movementBounds = new Rect();
1042         mSnapAlgorithm.getMovementBounds(curBounds, mInsetBounds,
1043                 movementBounds, mIsImeShowing ? mImeHeight : 0);
1044         return movementBounds;
1045     }
1046 
1047     /**
1048      * @return whether the menu will resize as a part of showing the full menu.
1049      */
willResizeMenu()1050     private boolean willResizeMenu() {
1051         if (!mEnableResize) {
1052             return false;
1053         }
1054         return mExpandedBounds.width() != mNormalBounds.width()
1055                 || mExpandedBounds.height() != mNormalBounds.height();
1056     }
1057 
dump(PrintWriter pw, String prefix)1058     public void dump(PrintWriter pw, String prefix) {
1059         final String innerPrefix = prefix + "  ";
1060         pw.println(prefix + TAG);
1061         pw.println(innerPrefix + "mMovementBounds=" + mMovementBounds);
1062         pw.println(innerPrefix + "mNormalBounds=" + mNormalBounds);
1063         pw.println(innerPrefix + "mNormalMovementBounds=" + mNormalMovementBounds);
1064         pw.println(innerPrefix + "mExpandedBounds=" + mExpandedBounds);
1065         pw.println(innerPrefix + "mExpandedMovementBounds=" + mExpandedMovementBounds);
1066         pw.println(innerPrefix + "mMenuState=" + mMenuState);
1067         pw.println(innerPrefix + "mIsImeShowing=" + mIsImeShowing);
1068         pw.println(innerPrefix + "mImeHeight=" + mImeHeight);
1069         pw.println(innerPrefix + "mIsShelfShowing=" + mIsShelfShowing);
1070         pw.println(innerPrefix + "mShelfHeight=" + mShelfHeight);
1071         pw.println(innerPrefix + "mSavedSnapFraction=" + mSavedSnapFraction);
1072         pw.println(innerPrefix + "mEnableDragToEdgeDismiss=" + mEnableDismissDragToEdge);
1073         pw.println(innerPrefix + "mMovementBoundsExtraOffsets=" + mMovementBoundsExtraOffsets);
1074         mTouchState.dump(pw, innerPrefix);
1075         mMotionHelper.dump(pw, innerPrefix);
1076         if (mPipResizeGestureHandler != null) {
1077             mPipResizeGestureHandler.dump(pw, innerPrefix);
1078         }
1079     }
1080 
1081 }
1082