1 /*
2  * Copyright (C) 2020 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 package com.android.wm.shell.pip2.phone;
17 
18 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE;
19 
20 import android.annotation.Nullable;
21 import android.content.Context;
22 import android.content.res.Resources;
23 import android.graphics.Point;
24 import android.graphics.PointF;
25 import android.graphics.Rect;
26 import android.hardware.input.InputManager;
27 import android.os.Bundle;
28 import android.os.Looper;
29 import android.view.BatchedInputEventReceiver;
30 import android.view.Choreographer;
31 import android.view.InputChannel;
32 import android.view.InputEvent;
33 import android.view.InputEventReceiver;
34 import android.view.InputMonitor;
35 import android.view.MotionEvent;
36 import android.view.SurfaceControl;
37 import android.view.ViewConfiguration;
38 
39 import androidx.annotation.VisibleForTesting;
40 
41 import com.android.internal.util.Preconditions;
42 import com.android.wm.shell.R;
43 import com.android.wm.shell.common.ShellExecutor;
44 import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
45 import com.android.wm.shell.common.pip.PipBoundsState;
46 import com.android.wm.shell.common.pip.PipPerfHintController;
47 import com.android.wm.shell.common.pip.PipPinchResizingAlgorithm;
48 import com.android.wm.shell.common.pip.PipUiEventLogger;
49 import com.android.wm.shell.pip2.animation.PipResizeAnimator;
50 
51 import java.io.PrintWriter;
52 import java.util.function.Consumer;
53 
54 /**
55  * Helper on top of PipTouchHandler that handles inputs OUTSIDE of the PIP window, which is used to
56  * trigger dynamic resize.
57  */
58 public class PipResizeGestureHandler implements
59         PipTransitionState.PipTransitionStateChangedListener {
60 
61     private static final String TAG = "PipResizeGestureHandler";
62     private static final int PINCH_RESIZE_SNAP_DURATION = 250;
63     private static final float PINCH_RESIZE_AUTO_MAX_RATIO = 0.9f;
64     private static final String RESIZE_BOUNDS_CHANGE = "resize_bounds_change";
65 
66     private final Context mContext;
67     private final PipBoundsAlgorithm mPipBoundsAlgorithm;
68     private final PipBoundsState mPipBoundsState;
69     private final PipTouchState mPipTouchState;
70     private final PipScheduler mPipScheduler;
71     private final PipTransitionState mPipTransitionState;
72     private final PhonePipMenuController mPhonePipMenuController;
73     private final PipUiEventLogger mPipUiEventLogger;
74     private final PipPinchResizingAlgorithm mPinchResizingAlgorithm;
75     private final int mDisplayId;
76     private final ShellExecutor mMainExecutor;
77 
78     private final PointF mDownPoint = new PointF();
79     private final PointF mDownSecondPoint = new PointF();
80     private final PointF mLastPoint = new PointF();
81     private final PointF mLastSecondPoint = new PointF();
82     private final Point mMaxSize = new Point();
83     private final Point mMinSize = new Point();
84     private final Rect mLastResizeBounds = new Rect();
85     private final Rect mUserResizeBounds = new Rect();
86     private final Rect mDownBounds = new Rect();
87     private final Rect mStartBoundsAfterRelease = new Rect();
88     private final Runnable mUpdateMovementBoundsRunnable;
89     private final Consumer<Rect> mUpdateResizeBoundsCallback;
90 
91     private float mTouchSlop;
92 
93     private boolean mAllowGesture;
94     private boolean mIsAttached;
95     private boolean mIsEnabled;
96     private boolean mEnablePinchResize;
97     private boolean mIsSysUiStateValid;
98     private boolean mThresholdCrossed;
99     private boolean mOngoingPinchToResize = false;
100     private boolean mWaitingForBoundsChangeTransition = false;
101     private float mAngle = 0;
102     int mFirstIndex = -1;
103     int mSecondIndex = -1;
104 
105     private InputMonitor mInputMonitor;
106     private InputEventReceiver mInputEventReceiver;
107 
108     @Nullable
109     private final PipPerfHintController mPipPerfHintController;
110 
111     @Nullable
112     private PipPerfHintController.PipHighPerfSession mPipHighPerfSession;
113 
114     private int mCtrlType;
115     private int mOhmOffset;
116 
PipResizeGestureHandler(Context context, PipBoundsAlgorithm pipBoundsAlgorithm, PipBoundsState pipBoundsState, PipTouchState pipTouchState, PipScheduler pipScheduler, PipTransitionState pipTransitionState, Runnable updateMovementBoundsRunnable, PipUiEventLogger pipUiEventLogger, PhonePipMenuController menuActivityController, ShellExecutor mainExecutor, @Nullable PipPerfHintController pipPerfHintController)117     public PipResizeGestureHandler(Context context,
118             PipBoundsAlgorithm pipBoundsAlgorithm,
119             PipBoundsState pipBoundsState,
120             PipTouchState pipTouchState,
121             PipScheduler pipScheduler,
122             PipTransitionState pipTransitionState,
123             Runnable updateMovementBoundsRunnable,
124             PipUiEventLogger pipUiEventLogger,
125             PhonePipMenuController menuActivityController,
126             ShellExecutor mainExecutor,
127             @Nullable PipPerfHintController pipPerfHintController) {
128         mContext = context;
129         mDisplayId = context.getDisplayId();
130         mMainExecutor = mainExecutor;
131         mPipPerfHintController = pipPerfHintController;
132         mPipBoundsAlgorithm = pipBoundsAlgorithm;
133         mPipBoundsState = pipBoundsState;
134         mPipTouchState = pipTouchState;
135         mPipScheduler = pipScheduler;
136 
137         mPipTransitionState = pipTransitionState;
138         mPipTransitionState.addPipTransitionStateChangedListener(this);
139 
140         mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
141         mPhonePipMenuController = menuActivityController;
142         mPipUiEventLogger = pipUiEventLogger;
143         mPinchResizingAlgorithm = new PipPinchResizingAlgorithm();
144 
145         mUpdateResizeBoundsCallback = (rect) -> {
146             mUserResizeBounds.set(rect);
147             // mMotionHelper.synchronizePinnedStackBounds();
148             mUpdateMovementBoundsRunnable.run();
149             mPipBoundsState.setBounds(rect);
150             resetState();
151         };
152     }
153 
init()154     void init() {
155         mContext.getDisplay().getRealSize(mMaxSize);
156         reloadResources();
157 
158         final Resources res = mContext.getResources();
159         mEnablePinchResize = res.getBoolean(R.bool.config_pipEnablePinchResize);
160     }
161 
onConfigurationChanged()162     void onConfigurationChanged() {
163         reloadResources();
164     }
165 
166     /**
167      * Called when SysUI state changed.
168      *
169      * @param isSysUiStateValid Is SysUI valid or not.
170      */
onSystemUiStateChanged(boolean isSysUiStateValid)171     public void onSystemUiStateChanged(boolean isSysUiStateValid) {
172         mIsSysUiStateValid = isSysUiStateValid;
173     }
174 
reloadResources()175     private void reloadResources() {
176         mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
177     }
178 
disposeInputChannel()179     private void disposeInputChannel() {
180         if (mInputEventReceiver != null) {
181             mInputEventReceiver.dispose();
182             mInputEventReceiver = null;
183         }
184         if (mInputMonitor != null) {
185             mInputMonitor.dispose();
186             mInputMonitor = null;
187         }
188     }
189 
onActivityPinned()190     void onActivityPinned() {
191         mIsAttached = true;
192         updateIsEnabled();
193     }
194 
onActivityUnpinned()195     void onActivityUnpinned() {
196         mIsAttached = false;
197         mUserResizeBounds.setEmpty();
198         updateIsEnabled();
199     }
200 
updateIsEnabled()201     private void updateIsEnabled() {
202         boolean isEnabled = mIsAttached;
203         if (isEnabled == mIsEnabled) {
204             return;
205         }
206         mIsEnabled = isEnabled;
207         disposeInputChannel();
208 
209         if (mIsEnabled) {
210             // Register input event receiver
211             mInputMonitor = mContext.getSystemService(InputManager.class).monitorGestureInput(
212                     "pip-resize", mDisplayId);
213             try {
214                 mMainExecutor.executeBlocking(() -> {
215                     mInputEventReceiver = new PipResizeInputEventReceiver(
216                             mInputMonitor.getInputChannel(), Looper.myLooper());
217                 });
218             } catch (InterruptedException e) {
219                 throw new RuntimeException("Failed to create input event receiver", e);
220             }
221         }
222     }
223 
224     @VisibleForTesting
onInputEvent(InputEvent ev)225     void onInputEvent(InputEvent ev) {
226         if (!mEnablePinchResize) {
227             // No need to handle anything if resizing isn't enabled.
228             return;
229         }
230 
231         if (!mPipTouchState.getAllowInputEvents()) {
232             // No need to handle anything if touches are not enabled
233             return;
234         }
235 
236         // Don't allow resize when PiP is stashed.
237         if (mPipBoundsState.isStashed()) {
238             return;
239         }
240 
241         if (ev instanceof MotionEvent) {
242             MotionEvent mv = (MotionEvent) ev;
243             int action = mv.getActionMasked();
244             final Rect pipBounds = mPipBoundsState.getBounds();
245             if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
246                 if (!pipBounds.contains((int) mv.getRawX(), (int) mv.getRawY())
247                         && mPhonePipMenuController.isMenuVisible()) {
248                     mPhonePipMenuController.hideMenu();
249                 }
250             }
251 
252             if (mOngoingPinchToResize) {
253                 onPinchResize(mv);
254             }
255         }
256     }
257 
258     /**
259      * Checks if there is currently an on-going gesture, either drag-resize or pinch-resize.
260      */
hasOngoingGesture()261     public boolean hasOngoingGesture() {
262         return mCtrlType != CTRL_NONE || mOngoingPinchToResize;
263     }
264 
isUsingPinchToZoom()265     public boolean isUsingPinchToZoom() {
266         return mEnablePinchResize;
267     }
268 
isResizing()269     public boolean isResizing() {
270         return mAllowGesture;
271     }
272 
willStartResizeGesture(MotionEvent ev)273     boolean willStartResizeGesture(MotionEvent ev) {
274         if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
275             if (mEnablePinchResize && ev.getPointerCount() == 2) {
276                 onPinchResize(ev);
277                 mOngoingPinchToResize = mAllowGesture;
278                 return mAllowGesture;
279             }
280         }
281         return false;
282     }
283 
isInValidSysUiState()284     private boolean isInValidSysUiState() {
285         return mIsSysUiStateValid;
286     }
287 
onHighPerfSessionTimeout(PipPerfHintController.PipHighPerfSession session)288     private void onHighPerfSessionTimeout(PipPerfHintController.PipHighPerfSession session) {}
289 
cleanUpHighPerfSessionMaybe()290     private void cleanUpHighPerfSessionMaybe() {
291         if (mPipHighPerfSession != null) {
292             // Close the high perf session once pointer interactions are over;
293             mPipHighPerfSession.close();
294             mPipHighPerfSession = null;
295         }
296     }
297 
298     @VisibleForTesting
onPinchResize(MotionEvent ev)299     void onPinchResize(MotionEvent ev) {
300         int action = ev.getActionMasked();
301 
302         if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
303             mFirstIndex = -1;
304             mSecondIndex = -1;
305             mAllowGesture = false;
306             finishResize();
307         }
308 
309         if (ev.getPointerCount() != 2) {
310             return;
311         }
312 
313         final Rect pipBounds = mPipBoundsState.getBounds();
314         if (action == MotionEvent.ACTION_POINTER_DOWN) {
315             if (mFirstIndex == -1 && mSecondIndex == -1
316                     && pipBounds.contains((int) ev.getRawX(0), (int) ev.getRawY(0))
317                     && pipBounds.contains((int) ev.getRawX(1), (int) ev.getRawY(1))) {
318                 mAllowGesture = true;
319                 mFirstIndex = 0;
320                 mSecondIndex = 1;
321                 mDownPoint.set(ev.getRawX(mFirstIndex), ev.getRawY(mFirstIndex));
322                 mDownSecondPoint.set(ev.getRawX(mSecondIndex), ev.getRawY(mSecondIndex));
323                 mDownBounds.set(pipBounds);
324 
325                 mLastPoint.set(mDownPoint);
326                 mLastSecondPoint.set(mLastSecondPoint);
327                 mLastResizeBounds.set(mDownBounds);
328 
329                 // start the high perf session as the second pointer gets detected
330                 if (mPipPerfHintController != null) {
331                     mPipHighPerfSession = mPipPerfHintController.startSession(
332                             this::onHighPerfSessionTimeout, "onPinchResize");
333                 }
334             }
335         }
336 
337         if (action == MotionEvent.ACTION_MOVE) {
338             if (mFirstIndex == -1 || mSecondIndex == -1) {
339                 return;
340             }
341 
342             float x0 = ev.getRawX(mFirstIndex);
343             float y0 = ev.getRawY(mFirstIndex);
344             float x1 = ev.getRawX(mSecondIndex);
345             float y1 = ev.getRawY(mSecondIndex);
346             mLastPoint.set(x0, y0);
347             mLastSecondPoint.set(x1, y1);
348 
349             // Capture inputs
350             if (!mThresholdCrossed
351                     && (distanceBetween(mDownSecondPoint, mLastSecondPoint) > mTouchSlop
352                             || distanceBetween(mDownPoint, mLastPoint) > mTouchSlop)) {
353                 pilferPointers();
354                 mThresholdCrossed = true;
355                 // Reset the down to begin resizing from this point
356                 mDownPoint.set(mLastPoint);
357                 mDownSecondPoint.set(mLastSecondPoint);
358 
359                 if (mPhonePipMenuController.isMenuVisible()) {
360                     mPhonePipMenuController.hideMenu();
361                 }
362             }
363 
364             if (mThresholdCrossed) {
365                 mAngle = mPinchResizingAlgorithm.calculateBoundsAndAngle(mDownPoint,
366                         mDownSecondPoint, mLastPoint, mLastSecondPoint, mMinSize, mMaxSize,
367                         mDownBounds, mLastResizeBounds);
368 
369                 mPipScheduler.scheduleUserResizePip(mLastResizeBounds, mAngle);
370                 mPipBoundsState.setHasUserResizedPip(true);
371             }
372         }
373     }
374 
snapToMovementBoundsEdge(Rect bounds, Rect movementBounds)375     private void snapToMovementBoundsEdge(Rect bounds, Rect movementBounds) {
376         final int leftEdge = bounds.left;
377 
378 
379         final int fromLeft = Math.abs(leftEdge - movementBounds.left);
380         final int fromRight = Math.abs(movementBounds.right - leftEdge);
381 
382         // The PIP will be snapped to either the right or left edge, so calculate which one
383         // is closest to the current position.
384         final int newLeft = fromLeft < fromRight
385                 ? movementBounds.left : movementBounds.right;
386 
387         bounds.offsetTo(newLeft, mLastResizeBounds.top);
388     }
389 
390     /**
391      * Resizes the pip window and updates user-resized bounds.
392      *
393      * @param bounds target bounds to resize to
394      * @param snapFraction snap fraction to apply after resizing
395      */
396     void userResizeTo(Rect bounds, float snapFraction) {
397         Rect finalBounds = new Rect(bounds);
398 
399         // get the current movement bounds
400         final Rect movementBounds = mPipBoundsAlgorithm.getMovementBounds(finalBounds);
401 
402         // snap the target bounds to the either left or right edge, by choosing the closer one
403         snapToMovementBoundsEdge(finalBounds, movementBounds);
404 
405         // apply the requested snap fraction onto the target bounds
406         mPipBoundsAlgorithm.applySnapFraction(finalBounds, snapFraction);
407 
408         // resize from current bounds to target bounds without animation
409         // mPipTaskOrganizer.scheduleUserResizePip(mPipBoundsState.getBounds(), finalBounds, null);
410         // set the flag that pip has been resized
411         mPipBoundsState.setHasUserResizedPip(true);
412 
413         // finish the resize operation and update the state of the bounds
414         // mPipTaskOrganizer.scheduleFinishResizePip(finalBounds, mUpdateResizeBoundsCallback);
415     }
416 
417     private void finishResize() {
418         if (mLastResizeBounds.isEmpty()) {
419             resetState();
420         }
421         if (!mOngoingPinchToResize) {
422             return;
423         }
424 
425         // Cache initial bounds after release for animation before mLastResizeBounds are modified.
426         mStartBoundsAfterRelease.set(mLastResizeBounds);
427 
428         // If user resize is pretty close to max size, just auto resize to max.
429         if (mLastResizeBounds.width() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.x
430                 || mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) {
431             resizeRectAboutCenter(mLastResizeBounds, mMaxSize.x, mMaxSize.y);
432         }
433 
434         // If user resize is smaller than min size, auto resize to min
435         if (mLastResizeBounds.width() < mMinSize.x
436                 || mLastResizeBounds.height() < mMinSize.y) {
437             resizeRectAboutCenter(mLastResizeBounds, mMinSize.x, mMinSize.y);
438         }
439 
440         // get the current movement bounds
441         final Rect movementBounds = mPipBoundsAlgorithm
442                 .getMovementBounds(mLastResizeBounds);
443 
444         // snap mLastResizeBounds to the correct edge based on movement bounds
445         snapToMovementBoundsEdge(mLastResizeBounds, movementBounds);
446 
447         final float snapFraction = mPipBoundsAlgorithm.getSnapFraction(
448                 mLastResizeBounds, movementBounds);
449         mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction);
450 
451         // Update the transition state to schedule a resize transition.
452         Bundle extra = new Bundle();
453         extra.putBoolean(RESIZE_BOUNDS_CHANGE, true);
454         mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra);
455 
456         mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_RESIZE);
457     }
458 
resetState()459     private void resetState() {
460         mCtrlType = CTRL_NONE;
461         mAngle = 0;
462         mOngoingPinchToResize = false;
463         mAllowGesture = false;
464         mThresholdCrossed = false;
465     }
466 
setUserResizeBounds(Rect bounds)467     void setUserResizeBounds(Rect bounds) {
468         mUserResizeBounds.set(bounds);
469     }
470 
invalidateUserResizeBounds()471     void invalidateUserResizeBounds() {
472         mUserResizeBounds.setEmpty();
473     }
474 
getUserResizeBounds()475     Rect getUserResizeBounds() {
476         return mUserResizeBounds;
477     }
478 
479     @VisibleForTesting
getLastResizeBounds()480     Rect getLastResizeBounds() {
481         return mLastResizeBounds;
482     }
483 
484     @VisibleForTesting
pilferPointers()485     void pilferPointers() {
486         mInputMonitor.pilferPointers();
487     }
488 
489 
updateMaxSize(int maxX, int maxY)490     void updateMaxSize(int maxX, int maxY) {
491         mMaxSize.set(maxX, maxY);
492     }
493 
updateMinSize(int minX, int minY)494     void updateMinSize(int minX, int minY) {
495         mMinSize.set(minX, minY);
496     }
497 
setOhmOffset(int offset)498     void setOhmOffset(int offset) {
499         mOhmOffset = offset;
500     }
501 
distanceBetween(PointF p1, PointF p2)502     private float distanceBetween(PointF p1, PointF p2) {
503         return (float) Math.hypot(p2.x - p1.x, p2.y - p1.y);
504     }
505 
resizeRectAboutCenter(Rect rect, int w, int h)506     private void resizeRectAboutCenter(Rect rect, int w, int h) {
507         int cx = rect.centerX();
508         int cy = rect.centerY();
509         int l = cx - w / 2;
510         int r = l + w;
511         int t = cy - h / 2;
512         int b = t + h;
513         rect.set(l, t, r, b);
514     }
515 
516     @Override
onPipTransitionStateChanged(@ipTransitionState.TransitionState int oldState, @PipTransitionState.TransitionState int newState, @Nullable Bundle extra)517     public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
518             @PipTransitionState.TransitionState int newState, @Nullable Bundle extra) {
519         switch (newState) {
520             case PipTransitionState.SCHEDULED_BOUNDS_CHANGE:
521                 if (!extra.getBoolean(RESIZE_BOUNDS_CHANGE)) break;
522 
523                 if (mPipBoundsState.getBounds().equals(mLastResizeBounds)) {
524                     // If the bounds are invariant move the destination bounds by a single pixel
525                     // to top/bottom to avoid a no-op transition. This trick helps keep the
526                     // animation a part of the transition.
527                     float snapFraction = mPipBoundsAlgorithm.getSnapFraction(
528                             mPipBoundsState.getBounds());
529 
530                     // Move to the top if closer to the bottom edge and vice versa.
531                     boolean inTopHalf = snapFraction < 1.5 || snapFraction > 3.5;
532                     int offsetY = inTopHalf ? 1 : -1;
533                     mLastResizeBounds.offset(0 /* dx */, offsetY);
534                 }
535                 mWaitingForBoundsChangeTransition = true;
536 
537                 // Schedule PiP resize transition, but delay any config updates until very end.
538                 mPipScheduler.scheduleAnimateResizePip(mLastResizeBounds, true /* configAtEnd */);
539                 break;
540             case PipTransitionState.CHANGING_PIP_BOUNDS:
541                 if (!mWaitingForBoundsChangeTransition) break;
542                 // If resize transition was scheduled from this component, handle leash updates.
543                 mWaitingForBoundsChangeTransition = false;
544 
545                 SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash;
546                 Preconditions.checkState(pipLeash != null,
547                         "No leash cached by mPipTransitionState=" + mPipTransitionState);
548 
549                 SurfaceControl.Transaction startTx = extra.getParcelable(
550                         PipTransition.PIP_START_TX, SurfaceControl.Transaction.class);
551                 SurfaceControl.Transaction finishTx = extra.getParcelable(
552                         PipTransition.PIP_FINISH_TX, SurfaceControl.Transaction.class);
553                 startTx.setWindowCrop(pipLeash, mPipBoundsState.getBounds().width(),
554                         mPipBoundsState.getBounds().height());
555 
556                 PipResizeAnimator animator = new PipResizeAnimator(mContext, pipLeash,
557                         startTx, finishTx, mPipBoundsState.getBounds(), mStartBoundsAfterRelease,
558                         mLastResizeBounds, PINCH_RESIZE_SNAP_DURATION, mAngle);
559                 animator.setAnimationEndCallback(() -> {
560                     // All motion operations have actually finished, so make bounds cache updates.
561                     mUpdateResizeBoundsCallback.accept(mLastResizeBounds);
562                     cleanUpHighPerfSessionMaybe();
563 
564                     // Signal that we are done with resize transition
565                     mPipScheduler.scheduleFinishResizePip(true /* configAtEnd */);
566                 });
567                 animator.start();
568                 break;
569         }
570     }
571 
572     /**
573      * Dumps the {@link PipResizeGestureHandler} state.
574      */
dump(PrintWriter pw, String prefix)575     public void dump(PrintWriter pw, String prefix) {
576         final String innerPrefix = prefix + "  ";
577         pw.println(prefix + TAG);
578         pw.println(innerPrefix + "mAllowGesture=" + mAllowGesture);
579         pw.println(innerPrefix + "mIsAttached=" + mIsAttached);
580         pw.println(innerPrefix + "mIsEnabled=" + mIsEnabled);
581         pw.println(innerPrefix + "mEnablePinchResize=" + mEnablePinchResize);
582         pw.println(innerPrefix + "mThresholdCrossed=" + mThresholdCrossed);
583         pw.println(innerPrefix + "mOhmOffset=" + mOhmOffset);
584         pw.println(innerPrefix + "mMinSize=" + mMinSize);
585         pw.println(innerPrefix + "mMaxSize=" + mMaxSize);
586     }
587 
588     class PipResizeInputEventReceiver extends BatchedInputEventReceiver {
PipResizeInputEventReceiver(InputChannel channel, Looper looper)589         PipResizeInputEventReceiver(InputChannel channel, Looper looper) {
590             super(channel, looper, Choreographer.getInstance());
591         }
592 
onInputEvent(InputEvent event)593         public void onInputEvent(InputEvent event) {
594             PipResizeGestureHandler.this.onInputEvent(event);
595             finishInputEvent(event, true);
596         }
597     }
598 }
599