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