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.pip.phone; 17 18 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_PINCH_RESIZE; 19 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM; 20 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT; 21 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE; 22 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT; 23 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP; 24 import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_NONE; 25 26 import android.content.Context; 27 import android.content.res.Resources; 28 import android.graphics.Point; 29 import android.graphics.PointF; 30 import android.graphics.Rect; 31 import android.graphics.Region; 32 import android.hardware.input.InputManager; 33 import android.os.Looper; 34 import android.provider.DeviceConfig; 35 import android.view.BatchedInputEventReceiver; 36 import android.view.Choreographer; 37 import android.view.InputChannel; 38 import android.view.InputEvent; 39 import android.view.InputEventReceiver; 40 import android.view.InputMonitor; 41 import android.view.MotionEvent; 42 import android.view.ViewConfiguration; 43 44 import androidx.annotation.VisibleForTesting; 45 46 import com.android.internal.policy.TaskResizingAlgorithm; 47 import com.android.wm.shell.R; 48 import com.android.wm.shell.common.ShellExecutor; 49 import com.android.wm.shell.pip.PipAnimationController; 50 import com.android.wm.shell.pip.PipBoundsAlgorithm; 51 import com.android.wm.shell.pip.PipBoundsState; 52 import com.android.wm.shell.pip.PipTaskOrganizer; 53 import com.android.wm.shell.pip.PipUiEventLogger; 54 55 import java.io.PrintWriter; 56 import java.util.function.Consumer; 57 import java.util.function.Function; 58 59 /** 60 * Helper on top of PipTouchHandler that handles inputs OUTSIDE of the PIP window, which is used to 61 * trigger dynamic resize. 62 */ 63 public class PipResizeGestureHandler { 64 65 private static final String TAG = "PipResizeGestureHandler"; 66 private static final int PINCH_RESIZE_SNAP_DURATION = 250; 67 private static final float PINCH_RESIZE_AUTO_MAX_RATIO = 0.9f; 68 69 private final Context mContext; 70 private final PipBoundsAlgorithm mPipBoundsAlgorithm; 71 private final PipMotionHelper mMotionHelper; 72 private final PipBoundsState mPipBoundsState; 73 private final PipTaskOrganizer mPipTaskOrganizer; 74 private final PhonePipMenuController mPhonePipMenuController; 75 private final PipDismissTargetHandler mPipDismissTargetHandler; 76 private final PipUiEventLogger mPipUiEventLogger; 77 private final PipPinchResizingAlgorithm mPinchResizingAlgorithm; 78 private final int mDisplayId; 79 private final ShellExecutor mMainExecutor; 80 private final Region mTmpRegion = new Region(); 81 82 private final PointF mDownPoint = new PointF(); 83 private final PointF mDownSecondPoint = new PointF(); 84 private final PointF mLastPoint = new PointF(); 85 private final PointF mLastSecondPoint = new PointF(); 86 private final Point mMaxSize = new Point(); 87 private final Point mMinSize = new Point(); 88 private final Rect mLastResizeBounds = new Rect(); 89 private final Rect mUserResizeBounds = new Rect(); 90 private final Rect mDownBounds = new Rect(); 91 private final Rect mDragCornerSize = new Rect(); 92 private final Rect mTmpTopLeftCorner = new Rect(); 93 private final Rect mTmpTopRightCorner = new Rect(); 94 private final Rect mTmpBottomLeftCorner = new Rect(); 95 private final Rect mTmpBottomRightCorner = new Rect(); 96 private final Rect mDisplayBounds = new Rect(); 97 private final Function<Rect, Rect> mMovementBoundsSupplier; 98 private final Runnable mUpdateMovementBoundsRunnable; 99 100 private int mDelta; 101 private float mTouchSlop; 102 103 private boolean mAllowGesture; 104 private boolean mIsAttached; 105 private boolean mIsEnabled; 106 private boolean mEnablePinchResize; 107 private boolean mEnableDragCornerResize; 108 private boolean mIsSysUiStateValid; 109 private boolean mThresholdCrossed; 110 private boolean mOngoingPinchToResize = false; 111 private float mAngle = 0; 112 int mFirstIndex = -1; 113 int mSecondIndex = -1; 114 115 private InputMonitor mInputMonitor; 116 private InputEventReceiver mInputEventReceiver; 117 118 private int mCtrlType; 119 private int mOhmOffset; 120 PipResizeGestureHandler(Context context, PipBoundsAlgorithm pipBoundsAlgorithm, PipBoundsState pipBoundsState, PipMotionHelper motionHelper, PipTaskOrganizer pipTaskOrganizer, PipDismissTargetHandler pipDismissTargetHandler, Function<Rect, Rect> movementBoundsSupplier, Runnable updateMovementBoundsRunnable, PipUiEventLogger pipUiEventLogger, PhonePipMenuController menuActivityController, ShellExecutor mainExecutor)121 public PipResizeGestureHandler(Context context, PipBoundsAlgorithm pipBoundsAlgorithm, 122 PipBoundsState pipBoundsState, PipMotionHelper motionHelper, 123 PipTaskOrganizer pipTaskOrganizer, PipDismissTargetHandler pipDismissTargetHandler, 124 Function<Rect, Rect> movementBoundsSupplier, Runnable updateMovementBoundsRunnable, 125 PipUiEventLogger pipUiEventLogger, PhonePipMenuController menuActivityController, 126 ShellExecutor mainExecutor) { 127 mContext = context; 128 mDisplayId = context.getDisplayId(); 129 mMainExecutor = mainExecutor; 130 mPipBoundsAlgorithm = pipBoundsAlgorithm; 131 mPipBoundsState = pipBoundsState; 132 mMotionHelper = motionHelper; 133 mPipTaskOrganizer = pipTaskOrganizer; 134 mPipDismissTargetHandler = pipDismissTargetHandler; 135 mMovementBoundsSupplier = movementBoundsSupplier; 136 mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable; 137 mPhonePipMenuController = menuActivityController; 138 mPipUiEventLogger = pipUiEventLogger; 139 mPinchResizingAlgorithm = new PipPinchResizingAlgorithm(); 140 } 141 init()142 public void init() { 143 mContext.getDisplay().getRealSize(mMaxSize); 144 reloadResources(); 145 146 mEnablePinchResize = DeviceConfig.getBoolean( 147 DeviceConfig.NAMESPACE_SYSTEMUI, 148 PIP_PINCH_RESIZE, 149 /* defaultValue = */ true); 150 DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, 151 mMainExecutor, 152 new DeviceConfig.OnPropertiesChangedListener() { 153 @Override 154 public void onPropertiesChanged(DeviceConfig.Properties properties) { 155 if (properties.getKeyset().contains(PIP_PINCH_RESIZE)) { 156 mEnablePinchResize = properties.getBoolean( 157 PIP_PINCH_RESIZE, /* defaultValue = */ true); 158 } 159 } 160 }); 161 } 162 onConfigurationChanged()163 public void onConfigurationChanged() { 164 reloadResources(); 165 } 166 167 /** 168 * Called when SysUI state changed. 169 * 170 * @param isSysUiStateValid Is SysUI valid or not. 171 */ onSystemUiStateChanged(boolean isSysUiStateValid)172 public void onSystemUiStateChanged(boolean isSysUiStateValid) { 173 mIsSysUiStateValid = isSysUiStateValid; 174 } 175 reloadResources()176 private void reloadResources() { 177 final Resources res = mContext.getResources(); 178 mDelta = res.getDimensionPixelSize(R.dimen.pip_resize_edge_size); 179 mEnableDragCornerResize = res.getBoolean(R.bool.config_pipEnableDragCornerResize); 180 mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop(); 181 } 182 resetDragCorners()183 private void resetDragCorners() { 184 mDragCornerSize.set(0, 0, mDelta, mDelta); 185 mTmpTopLeftCorner.set(mDragCornerSize); 186 mTmpTopRightCorner.set(mDragCornerSize); 187 mTmpBottomLeftCorner.set(mDragCornerSize); 188 mTmpBottomRightCorner.set(mDragCornerSize); 189 } 190 disposeInputChannel()191 private void disposeInputChannel() { 192 if (mInputEventReceiver != null) { 193 mInputEventReceiver.dispose(); 194 mInputEventReceiver = null; 195 } 196 if (mInputMonitor != null) { 197 mInputMonitor.dispose(); 198 mInputMonitor = null; 199 } 200 } 201 onActivityPinned()202 void onActivityPinned() { 203 mIsAttached = true; 204 updateIsEnabled(); 205 } 206 onActivityUnpinned()207 void onActivityUnpinned() { 208 mIsAttached = false; 209 mUserResizeBounds.setEmpty(); 210 updateIsEnabled(); 211 } 212 updateIsEnabled()213 private void updateIsEnabled() { 214 boolean isEnabled = mIsAttached; 215 if (isEnabled == mIsEnabled) { 216 return; 217 } 218 mIsEnabled = isEnabled; 219 disposeInputChannel(); 220 221 if (mIsEnabled) { 222 // Register input event receiver 223 mInputMonitor = InputManager.getInstance().monitorGestureInput( 224 "pip-resize", mDisplayId); 225 try { 226 mMainExecutor.executeBlocking(() -> { 227 mInputEventReceiver = new PipResizeInputEventReceiver( 228 mInputMonitor.getInputChannel(), Looper.myLooper()); 229 }); 230 } catch (InterruptedException e) { 231 throw new RuntimeException("Failed to create input event receiver", e); 232 } 233 } 234 } 235 236 @VisibleForTesting onInputEvent(InputEvent ev)237 void onInputEvent(InputEvent ev) { 238 if (!mEnableDragCornerResize && !mEnablePinchResize) { 239 // No need to handle anything if neither form of resizing is enabled. 240 return; 241 } 242 243 // Don't allow resize when PiP is stashed. 244 if (mPipBoundsState.isStashed()) { 245 return; 246 } 247 248 if (ev instanceof MotionEvent) { 249 MotionEvent mv = (MotionEvent) ev; 250 int action = mv.getActionMasked(); 251 final Rect pipBounds = mPipBoundsState.getBounds(); 252 if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { 253 if (!pipBounds.contains((int) mv.getRawX(), (int) mv.getRawY()) 254 && mPhonePipMenuController.isMenuVisible()) { 255 mPhonePipMenuController.hideMenu(); 256 } 257 } 258 259 if (mEnablePinchResize && mOngoingPinchToResize) { 260 onPinchResize(mv); 261 } else if (mEnableDragCornerResize) { 262 onDragCornerResize(mv); 263 } 264 } 265 } 266 267 /** 268 * Checks if there is currently an on-going gesture, either drag-resize or pinch-resize. 269 */ hasOngoingGesture()270 public boolean hasOngoingGesture() { 271 return mCtrlType != CTRL_NONE || mOngoingPinchToResize; 272 } 273 274 /** 275 * Check whether the current x,y coordinate is within the region in which drag-resize should 276 * start. 277 * This consists of 4 small squares on the 4 corners of the PIP window, a quarter of which 278 * overlaps with the PIP window while the rest goes outside of the PIP window. 279 * _ _ _ _ 280 * |_|_|_________|_|_| 281 * |_|_| |_|_| 282 * | PIP | 283 * | WINDOW | 284 * _|_ _|_ 285 * |_|_|_________|_|_| 286 * |_|_| |_|_| 287 */ isWithinDragResizeRegion(int x, int y)288 public boolean isWithinDragResizeRegion(int x, int y) { 289 if (!mEnableDragCornerResize) { 290 return false; 291 } 292 293 final Rect currentPipBounds = mPipBoundsState.getBounds(); 294 if (currentPipBounds == null) { 295 return false; 296 } 297 resetDragCorners(); 298 mTmpTopLeftCorner.offset(currentPipBounds.left - mDelta / 2, 299 currentPipBounds.top - mDelta / 2); 300 mTmpTopRightCorner.offset(currentPipBounds.right - mDelta / 2, 301 currentPipBounds.top - mDelta / 2); 302 mTmpBottomLeftCorner.offset(currentPipBounds.left - mDelta / 2, 303 currentPipBounds.bottom - mDelta / 2); 304 mTmpBottomRightCorner.offset(currentPipBounds.right - mDelta / 2, 305 currentPipBounds.bottom - mDelta / 2); 306 307 mTmpRegion.setEmpty(); 308 mTmpRegion.op(mTmpTopLeftCorner, Region.Op.UNION); 309 mTmpRegion.op(mTmpTopRightCorner, Region.Op.UNION); 310 mTmpRegion.op(mTmpBottomLeftCorner, Region.Op.UNION); 311 mTmpRegion.op(mTmpBottomRightCorner, Region.Op.UNION); 312 313 return mTmpRegion.contains(x, y); 314 } 315 isUsingPinchToZoom()316 public boolean isUsingPinchToZoom() { 317 return mEnablePinchResize; 318 } 319 isResizing()320 public boolean isResizing() { 321 return mAllowGesture; 322 } 323 willStartResizeGesture(MotionEvent ev)324 public boolean willStartResizeGesture(MotionEvent ev) { 325 if (isInValidSysUiState()) { 326 switch (ev.getActionMasked()) { 327 case MotionEvent.ACTION_DOWN: 328 if (isWithinDragResizeRegion((int) ev.getRawX(), (int) ev.getRawY())) { 329 return true; 330 } 331 break; 332 333 case MotionEvent.ACTION_POINTER_DOWN: 334 if (mEnablePinchResize && ev.getPointerCount() == 2) { 335 onPinchResize(ev); 336 mOngoingPinchToResize = mAllowGesture; 337 return mAllowGesture; 338 } 339 break; 340 341 default: 342 break; 343 } 344 } 345 return false; 346 } 347 setCtrlType(int x, int y)348 private void setCtrlType(int x, int y) { 349 final Rect currentPipBounds = mPipBoundsState.getBounds(); 350 351 Rect movementBounds = mMovementBoundsSupplier.apply(currentPipBounds); 352 353 mDisplayBounds.set(movementBounds.left, 354 movementBounds.top, 355 movementBounds.right + currentPipBounds.width(), 356 movementBounds.bottom + currentPipBounds.height()); 357 358 if (mTmpTopLeftCorner.contains(x, y) && currentPipBounds.top != mDisplayBounds.top 359 && currentPipBounds.left != mDisplayBounds.left) { 360 mCtrlType |= CTRL_LEFT; 361 mCtrlType |= CTRL_TOP; 362 } 363 if (mTmpTopRightCorner.contains(x, y) && currentPipBounds.top != mDisplayBounds.top 364 && currentPipBounds.right != mDisplayBounds.right) { 365 mCtrlType |= CTRL_RIGHT; 366 mCtrlType |= CTRL_TOP; 367 } 368 if (mTmpBottomRightCorner.contains(x, y) 369 && currentPipBounds.bottom != mDisplayBounds.bottom 370 && currentPipBounds.right != mDisplayBounds.right) { 371 mCtrlType |= CTRL_RIGHT; 372 mCtrlType |= CTRL_BOTTOM; 373 } 374 if (mTmpBottomLeftCorner.contains(x, y) 375 && currentPipBounds.bottom != mDisplayBounds.bottom 376 && currentPipBounds.left != mDisplayBounds.left) { 377 mCtrlType |= CTRL_LEFT; 378 mCtrlType |= CTRL_BOTTOM; 379 } 380 } 381 isInValidSysUiState()382 private boolean isInValidSysUiState() { 383 return mIsSysUiStateValid; 384 } 385 386 @VisibleForTesting onPinchResize(MotionEvent ev)387 void onPinchResize(MotionEvent ev) { 388 int action = ev.getActionMasked(); 389 390 if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { 391 mFirstIndex = -1; 392 mSecondIndex = -1; 393 mAllowGesture = false; 394 finishResize(); 395 } 396 397 if (ev.getPointerCount() != 2) { 398 return; 399 } 400 401 final Rect pipBounds = mPipBoundsState.getBounds(); 402 if (action == MotionEvent.ACTION_POINTER_DOWN) { 403 if (mFirstIndex == -1 && mSecondIndex == -1 404 && pipBounds.contains((int) ev.getRawX(0), (int) ev.getRawY(0)) 405 && pipBounds.contains((int) ev.getRawX(1), (int) ev.getRawY(1))) { 406 mAllowGesture = true; 407 mFirstIndex = 0; 408 mSecondIndex = 1; 409 mDownPoint.set(ev.getRawX(mFirstIndex), ev.getRawY(mFirstIndex)); 410 mDownSecondPoint.set(ev.getRawX(mSecondIndex), ev.getRawY(mSecondIndex)); 411 mDownBounds.set(pipBounds); 412 413 mLastPoint.set(mDownPoint); 414 mLastSecondPoint.set(mLastSecondPoint); 415 mLastResizeBounds.set(mDownBounds); 416 } 417 } 418 419 if (action == MotionEvent.ACTION_MOVE) { 420 if (mFirstIndex == -1 || mSecondIndex == -1) { 421 return; 422 } 423 424 float x0 = ev.getRawX(mFirstIndex); 425 float y0 = ev.getRawY(mFirstIndex); 426 float x1 = ev.getRawX(mSecondIndex); 427 float y1 = ev.getRawY(mSecondIndex); 428 mLastPoint.set(x0, y0); 429 mLastSecondPoint.set(x1, y1); 430 431 // Capture inputs 432 if (!mThresholdCrossed 433 && (distanceBetween(mDownSecondPoint, mLastSecondPoint) > mTouchSlop 434 || distanceBetween(mDownPoint, mLastPoint) > mTouchSlop)) { 435 pilferPointers(); 436 mThresholdCrossed = true; 437 // Reset the down to begin resizing from this point 438 mDownPoint.set(mLastPoint); 439 mDownSecondPoint.set(mLastSecondPoint); 440 441 if (mPhonePipMenuController.isMenuVisible()) { 442 mPhonePipMenuController.hideMenu(); 443 } 444 } 445 446 if (mThresholdCrossed) { 447 mAngle = mPinchResizingAlgorithm.calculateBoundsAndAngle(mDownPoint, 448 mDownSecondPoint, mLastPoint, mLastSecondPoint, mMinSize, mMaxSize, 449 mDownBounds, mLastResizeBounds); 450 451 mPipTaskOrganizer.scheduleUserResizePip(mDownBounds, mLastResizeBounds, 452 mAngle, null); 453 mPipBoundsState.setHasUserResizedPip(true); 454 } 455 } 456 } 457 onDragCornerResize(MotionEvent ev)458 private void onDragCornerResize(MotionEvent ev) { 459 int action = ev.getActionMasked(); 460 float x = ev.getX(); 461 float y = ev.getY() - mOhmOffset; 462 if (action == MotionEvent.ACTION_DOWN) { 463 mLastResizeBounds.setEmpty(); 464 mAllowGesture = isInValidSysUiState() && isWithinDragResizeRegion((int) x, (int) y); 465 if (mAllowGesture) { 466 setCtrlType((int) x, (int) y); 467 mDownPoint.set(x, y); 468 mDownBounds.set(mPipBoundsState.getBounds()); 469 } 470 } else if (mAllowGesture) { 471 switch (action) { 472 case MotionEvent.ACTION_POINTER_DOWN: 473 // We do not support multi touch for resizing via drag 474 mAllowGesture = false; 475 break; 476 case MotionEvent.ACTION_MOVE: 477 // Capture inputs 478 if (!mThresholdCrossed 479 && Math.hypot(x - mDownPoint.x, y - mDownPoint.y) > mTouchSlop) { 480 mThresholdCrossed = true; 481 // Reset the down to begin resizing from this point 482 mDownPoint.set(x, y); 483 mInputMonitor.pilferPointers(); 484 } 485 if (mThresholdCrossed) { 486 if (mPhonePipMenuController.isMenuVisible()) { 487 mPhonePipMenuController.hideMenu(ANIM_TYPE_NONE, 488 false /* resize */); 489 } 490 final Rect currentPipBounds = mPipBoundsState.getBounds(); 491 mLastResizeBounds.set(TaskResizingAlgorithm.resizeDrag(x, y, 492 mDownPoint.x, mDownPoint.y, currentPipBounds, mCtrlType, mMinSize.x, 493 mMinSize.y, mMaxSize, true, 494 mDownBounds.width() > mDownBounds.height())); 495 mPipBoundsAlgorithm.transformBoundsToAspectRatio(mLastResizeBounds, 496 mPipBoundsState.getAspectRatio(), false /* useCurrentMinEdgeSize */, 497 true /* useCurrentSize */); 498 mPipTaskOrganizer.scheduleUserResizePip(mDownBounds, mLastResizeBounds, 499 null); 500 mPipBoundsState.setHasUserResizedPip(true); 501 } 502 break; 503 case MotionEvent.ACTION_UP: 504 case MotionEvent.ACTION_CANCEL: 505 finishResize(); 506 break; 507 } 508 } 509 } 510 finishResize()511 private void finishResize() { 512 if (!mLastResizeBounds.isEmpty()) { 513 final Consumer<Rect> callback = (rect) -> { 514 mUserResizeBounds.set(mLastResizeBounds); 515 mMotionHelper.synchronizePinnedStackBounds(); 516 mUpdateMovementBoundsRunnable.run(); 517 resetState(); 518 }; 519 520 // Pinch-to-resize needs to re-calculate snap fraction and animate to the snapped 521 // position correctly. Drag-resize does not need to move, so just finalize resize. 522 if (mOngoingPinchToResize) { 523 final Rect startBounds = new Rect(mLastResizeBounds); 524 // If user resize is pretty close to max size, just auto resize to max. 525 if (mLastResizeBounds.width() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.x 526 || mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) { 527 resizeRectAboutCenter(mLastResizeBounds, mMaxSize.x, mMaxSize.y); 528 } 529 final int leftEdge = mLastResizeBounds.left; 530 final Rect movementBounds = 531 mPipBoundsAlgorithm.getMovementBounds(mLastResizeBounds); 532 final int fromLeft = Math.abs(leftEdge - movementBounds.left); 533 final int fromRight = Math.abs(movementBounds.right - leftEdge); 534 // The PIP will be snapped to either the right or left edge, so calculate which one 535 // is closest to the current position. 536 final int newLeft = fromLeft < fromRight 537 ? movementBounds.left : movementBounds.right; 538 mLastResizeBounds.offsetTo(newLeft, mLastResizeBounds.top); 539 final float snapFraction = mPipBoundsAlgorithm.getSnapFraction( 540 mLastResizeBounds, movementBounds); 541 mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction); 542 mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds, 543 PINCH_RESIZE_SNAP_DURATION, mAngle, callback); 544 } else { 545 mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds, 546 PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE, callback); 547 } 548 final float magnetRadiusPercent = (float) mLastResizeBounds.width() / mMinSize.x / 2.f; 549 mPipDismissTargetHandler 550 .setMagneticFieldRadiusPercent(magnetRadiusPercent); 551 mPipUiEventLogger.log( 552 PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_RESIZE); 553 } else { 554 resetState(); 555 } 556 } 557 558 private void resetState() { 559 mCtrlType = CTRL_NONE; 560 mAngle = 0; 561 mOngoingPinchToResize = false; 562 mAllowGesture = false; 563 mThresholdCrossed = false; 564 } 565 566 void setUserResizeBounds(Rect bounds) { 567 mUserResizeBounds.set(bounds); 568 } 569 570 void invalidateUserResizeBounds() { 571 mUserResizeBounds.setEmpty(); 572 } 573 574 Rect getUserResizeBounds() { 575 return mUserResizeBounds; 576 } 577 578 @VisibleForTesting 579 Rect getLastResizeBounds() { 580 return mLastResizeBounds; 581 } 582 583 @VisibleForTesting 584 void pilferPointers() { 585 mInputMonitor.pilferPointers(); 586 } 587 588 589 @VisibleForTesting public void updateMaxSize(int maxX, int maxY) { 590 mMaxSize.set(maxX, maxY); 591 } 592 593 @VisibleForTesting public void updateMinSize(int minX, int minY) { 594 mMinSize.set(minX, minY); 595 } 596 597 void setOhmOffset(int offset) { 598 mOhmOffset = offset; 599 } 600 601 private float distanceBetween(PointF p1, PointF p2) { 602 return (float) Math.hypot(p2.x - p1.x, p2.y - p1.y); 603 } 604 605 private void resizeRectAboutCenter(Rect rect, int w, int h) { 606 int cx = rect.centerX(); 607 int cy = rect.centerY(); 608 int l = cx - w / 2; 609 int r = l + w; 610 int t = cy - h / 2; 611 int b = t + h; 612 rect.set(l, t, r, b); 613 } 614 615 public void dump(PrintWriter pw, String prefix) { 616 final String innerPrefix = prefix + " "; 617 pw.println(prefix + TAG); 618 pw.println(innerPrefix + "mAllowGesture=" + mAllowGesture); 619 pw.println(innerPrefix + "mIsAttached=" + mIsAttached); 620 pw.println(innerPrefix + "mIsEnabled=" + mIsEnabled); 621 pw.println(innerPrefix + "mEnablePinchResize=" + mEnablePinchResize); 622 pw.println(innerPrefix + "mThresholdCrossed=" + mThresholdCrossed); 623 pw.println(innerPrefix + "mOhmOffset=" + mOhmOffset); 624 } 625 626 class PipResizeInputEventReceiver extends BatchedInputEventReceiver { 627 PipResizeInputEventReceiver(InputChannel channel, Looper looper) { 628 super(channel, looper, Choreographer.getSfInstance()); 629 } 630 631 public void onInputEvent(InputEvent event) { 632 PipResizeGestureHandler.this.onInputEvent(event); 633 finishInputEvent(event, true); 634 } 635 } 636 } 637