Loading libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java +6 −3 Original line number Diff line number Diff line Loading @@ -94,11 +94,14 @@ public class PipSurfaceTransactionHelper { public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceBounds, Rect destinationBounds, float degrees) { mTmpSourceRectF.set(sourceBounds); // We want the matrix to position the surface relative to the screen coordinates so offset // the source to 0,0 mTmpSourceRectF.offsetTo(0, 0); mTmpDestinationRectF.set(destinationBounds); mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL); mTmpTransform.postRotate(degrees); tx.setMatrix(leash, mTmpTransform, mTmpFloat9) .setPosition(leash, mTmpDestinationRectF.left, mTmpDestinationRectF.top); mTmpTransform.postRotate(degrees, mTmpDestinationRectF.centerX(), mTmpDestinationRectF.centerY()); tx.setMatrix(leash, mTmpTransform, mTmpFloat9); return this; } Loading libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java +99 −95 Original line number Diff line number Diff line Loading @@ -16,111 +16,115 @@ package com.android.wm.shell.pip.phone; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; /** * Helper class to calculate the new size given two-fingers pinch to resize. */ public class PipPinchResizingAlgorithm { private static final Rect TMP_RECT = new Rect(); private static final int PINCH_RESIZE_MAX_ANGLE_ROTATION = 45; private static final float OVERROTATE_DAMP_FACTOR = 0.4f; private static final float ANGLE_THRESHOLD = 5f; private final PointF mTmpDownVector = new PointF(); private final PointF mTmpLastVector = new PointF(); private final PointF mTmpDownCentroid = new PointF(); private final PointF mTmpLastCentroid = new PointF(); /** * Given inputs and requirements and current PiP bounds, return the new size. * * @param x0 x-coordinate of the primary input. * @param y0 y-coordinate of the primary input. * @param x1 x-coordinate of the secondary input. * @param y1 y-coordinate of the secondary input. * @param downx0 x-coordinate of the original down point of the primary input. * @param downy0 y-coordinate of the original down ponit of the primary input. * @param downx1 x-coordinate of the original down point of the secondary input. * @param downy1 y-coordinate of the original down point of the secondary input. * @param currentPipBounds current PiP bounds. * @param minVisibleWidth minimum visible width. * @param minVisibleHeight minimum visible height. * @param maxSize max size. * @return The new resized PiP bounds, sharing the same center. * Updates {@param resizeBoundsOut} with the new bounds of the PIP, and returns the angle in * degrees that the PIP should be rotated. */ public static Rect pinchResize(float x0, float y0, float x1, float y1, float downx0, float downy0, float downx1, float downy1, Rect currentPipBounds, int minVisibleWidth, int minVisibleHeight, Point maxSize) { int width = currentPipBounds.width(); int height = currentPipBounds.height(); int left = currentPipBounds.left; int top = currentPipBounds.top; int right = currentPipBounds.right; int bottom = currentPipBounds.bottom; final float aspect = (float) width / (float) height; final int widthDelta = Math.round(Math.abs(x0 - x1) - Math.abs(downx0 - downx1)); final int heightDelta = Math.round(Math.abs(y0 - y1) - Math.abs(downy0 - downy1)); final int dx = (int) ((x0 - downx0 + x1 - downx1) / 2); final int dy = (int) ((y0 - downy0 + y1 - downy1) / 2); width = Math.max(minVisibleWidth, Math.min(width + widthDelta, maxSize.x)); height = Math.max(minVisibleHeight, Math.min(height + heightDelta, maxSize.y)); // Calculate 2 rectangles fulfilling all requirements for either X or Y being the major // drag axis. What ever is producing the bigger rectangle will be chosen. int width1; int width2; int height1; int height2; if (aspect > 1.0f) { // Assuming that the width is our target we calculate the height. width1 = Math.max(minVisibleWidth, Math.min(maxSize.x, width)); height1 = Math.round((float) width1 / aspect); if (height1 < minVisibleHeight) { // If the resulting height is too small we adjust to the minimal size. height1 = minVisibleHeight; width1 = Math.max(minVisibleWidth, Math.min(maxSize.x, Math.round((float) height1 * aspect))); public float calculateBoundsAndAngle(PointF downPoint, PointF downSecondPoint, PointF lastPoint, PointF lastSecondPoint, Point minSize, Point maxSize, Rect initialBounds, Rect resizeBoundsOut) { float downDist = (float) Math.hypot(downSecondPoint.x - downPoint.x, downSecondPoint.y - downPoint.y); float dist = (float) Math.hypot(lastSecondPoint.x - lastPoint.x, lastSecondPoint.y - lastPoint.y); float minScale = getMinScale(initialBounds, minSize); float maxScale = getMaxScale(initialBounds, maxSize); float scale = Math.max(minScale, Math.min(maxScale, dist / downDist)); // Scale the bounds by the change in distance between the points resizeBoundsOut.set(initialBounds); scaleRectAboutCenter(resizeBoundsOut, scale); // Translate by the centroid movement getCentroid(downPoint, downSecondPoint, mTmpDownCentroid); getCentroid(lastPoint, lastSecondPoint, mTmpLastCentroid); resizeBoundsOut.offset((int) (mTmpLastCentroid.x - mTmpDownCentroid.x), (int) (mTmpLastCentroid.y - mTmpDownCentroid.y)); // Calculate the angle mTmpDownVector.set(downSecondPoint.x - downPoint.x, downSecondPoint.y - downPoint.y); mTmpLastVector.set(lastSecondPoint.x - lastPoint.x, lastSecondPoint.y - lastPoint.y); float angle = (float) Math.atan2(cross(mTmpDownVector, mTmpLastVector), dot(mTmpDownVector, mTmpLastVector)); return constrainRotationAngle((float) Math.toDegrees(angle)); } // Assuming that the height is our target we calculate the width. height2 = Math.max(minVisibleHeight, Math.min(maxSize.y, height)); width2 = Math.round((float) height2 * aspect); if (width2 < minVisibleWidth) { // If the resulting width is too small we adjust to the minimal size. width2 = minVisibleWidth; height2 = Math.max(minVisibleHeight, Math.min(maxSize.y, Math.round((float) width2 / aspect))); private float getMinScale(Rect bounds, Point minSize) { return Math.max((float) minSize.x / bounds.width(), (float) minSize.y / bounds.height()); } } else { // Assuming that the width is our target we calculate the height. width1 = Math.max(minVisibleWidth, Math.min(maxSize.x, width)); height1 = Math.round((float) width1 / aspect); if (height1 < minVisibleHeight) { // If the resulting height is too small we adjust to the minimal size. height1 = minVisibleHeight; width1 = Math.max(minVisibleWidth, Math.min(maxSize.x, Math.round((float) height1 * aspect))); private float getMaxScale(Rect bounds, Point maxSize) { return Math.min((float) maxSize.x / bounds.width(), (float) maxSize.y / bounds.height()); } // Assuming that the height is our target we calculate the width. height2 = Math.max(minVisibleHeight, Math.min(maxSize.y, height)); width2 = Math.round((float) height2 * aspect); if (width2 < minVisibleWidth) { // If the resulting width is too small we adjust to the minimal size. width2 = minVisibleWidth; height2 = Math.max(minVisibleHeight, Math.min(maxSize.y, Math.round((float) width2 / aspect))); private float constrainRotationAngle(float angle) { // Remove some degrees so that user doesn't immediately start rotating until a threshold return Math.signum(angle) * Math.max(0, (Math.abs(dampedRotate(angle)) - ANGLE_THRESHOLD)); } /** * Given the current rotation angle, dampen it so that as it approaches the maximum angle, * dampen it. */ private float dampedRotate(float amount) { if (Float.compare(amount, 0) == 0) return 0; float f = amount / PINCH_RESIZE_MAX_ANGLE_ROTATION; f = f / (Math.abs(f)) * (overRotateInfluenceCurve(Math.abs(f))); // Clamp this factor, f, to -1 < f < 1 if (Math.abs(f) >= 1) { f /= Math.abs(f); } return OVERROTATE_DAMP_FACTOR * f * PINCH_RESIZE_MAX_ANGLE_ROTATION; } /** * Returns a value that corresponds to y = (f - 1)^3 + 1. */ private float overRotateInfluenceCurve(float f) { f -= 1.0f; return f * f * f + 1.0f; } private void getCentroid(PointF p1, PointF p2, PointF centroidOut) { centroidOut.set((p2.x + p1.x) / 2, (p2.y + p1.y) / 2); } // Use the bigger of the two rectangles if the major change was positive, otherwise // do the opposite. final boolean grows = width > (right - left) || height > (bottom - top); if (grows == (width1 * height1 > width2 * height2)) { width = width1; height = height1; } else { width = width2; height = height2; private float dot(PointF p1, PointF p2) { return p1.x * p2.x + p1.y * p2.y; } TMP_RECT.set(currentPipBounds.centerX() - width / 2, currentPipBounds.centerY() - height / 2, currentPipBounds.centerX() + width / 2, currentPipBounds.centerY() + height / 2); TMP_RECT.offset(dx, dy); return TMP_RECT; private float cross(PointF p1, PointF p2) { return p1.x * p2.y - p1.y * p2.x; } private void scaleRectAboutCenter(Rect r, float scale) { if (scale != 1.0f) { int cx = r.centerX(); int cy = r.centerY(); r.offset(-cx, -cy); r.scale(scale); r.offset(cx, cy); } } } libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java +51 −130 Original line number Diff line number Diff line Loading @@ -63,10 +63,7 @@ public class PipResizeGestureHandler { private static final String TAG = "PipResizeGestureHandler"; private static final int PINCH_RESIZE_SNAP_DURATION = 250; private static final int PINCH_RESIZE_MAX_ANGLE_ROTATION = 45; private static final float PINCH_RESIZE_AUTO_MAX_RATIO = 0.9f; private static final float OVERROTATE_DAMP_FACTOR = 0.4f; private static final float ANGLE_THRESHOLD = 5f; private final Context mContext; private final PipBoundsAlgorithm mPipBoundsAlgorithm; Loading @@ -75,17 +72,20 @@ public class PipResizeGestureHandler { private final PipTaskOrganizer mPipTaskOrganizer; private final PhonePipMenuController mPhonePipMenuController; private final PipUiEventLogger mPipUiEventLogger; private final PipPinchResizingAlgorithm mPinchResizingAlgorithm; private final int mDisplayId; private final ShellExecutor mMainExecutor; private final Region mTmpRegion = new Region(); private final PointF mDownPoint = new PointF(); private final PointF mDownSecondaryPoint = new PointF(); private final PointF mDownSecondPoint = new PointF(); private final PointF mLastPoint = new PointF(); private final PointF mLastSecondPoint = new PointF(); private final Point mMaxSize = new Point(); private final Point mMinSize = new Point(); private final Rect mLastResizeBounds = new Rect(); private final Rect mUserResizeBounds = new Rect(); private final Rect mLastDownBounds = new Rect(); private final Rect mDownBounds = new Rect(); private final Rect mDragCornerSize = new Rect(); private final Rect mTmpTopLeftCorner = new Rect(); private final Rect mTmpTopRightCorner = new Rect(); Loading @@ -103,11 +103,7 @@ public class PipResizeGestureHandler { private boolean mIsEnabled; private boolean mEnablePinchResize; private boolean mIsSysUiStateValid; // For drag-resize private boolean mThresholdCrossed; // For pinch-resize private boolean mThresholdCrossed0; private boolean mThresholdCrossed1; private boolean mOngoingPinchToResize = false; private float mAngle = 0; int mFirstIndex = -1; Loading Loading @@ -135,6 +131,7 @@ public class PipResizeGestureHandler { mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable; mPhonePipMenuController = menuActivityController; mPipUiEventLogger = pipUiEventLogger; mPinchResizingAlgorithm = new PipPinchResizingAlgorithm(); } public void init() { Loading Loading @@ -294,6 +291,10 @@ public class PipResizeGestureHandler { return mEnablePinchResize; } public boolean isResizing() { return mAllowGesture; } public boolean willStartResizeGesture(MotionEvent ev) { if (isInValidSysUiState()) { switch (ev.getActionMasked()) { Loading Loading @@ -362,6 +363,7 @@ public class PipResizeGestureHandler { if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { mFirstIndex = -1; mSecondIndex = -1; mAllowGesture = false; finishResize(); } Loading @@ -371,14 +373,16 @@ public class PipResizeGestureHandler { if (action == MotionEvent.ACTION_POINTER_DOWN) { if (mFirstIndex == -1 && mSecondIndex == -1) { mAllowGesture = true; mFirstIndex = 0; mSecondIndex = 1; mDownPoint.set(ev.getRawX(mFirstIndex), ev.getRawY(mFirstIndex)); mDownSecondaryPoint.set(ev.getRawX(mSecondIndex), ev.getRawY(mSecondIndex)); mDownSecondPoint.set(ev.getRawX(mSecondIndex), ev.getRawY(mSecondIndex)); mDownBounds.set(mPipBoundsState.getBounds()); mLastDownBounds.set(mPipBoundsState.getBounds()); mLastResizeBounds.set(mLastDownBounds); mLastPoint.set(mDownPoint); mLastSecondPoint.set(mLastSecondPoint); mLastResizeBounds.set(mDownBounds); } } Loading @@ -391,131 +395,34 @@ public class PipResizeGestureHandler { float y0 = ev.getRawY(mFirstIndex); float x1 = ev.getRawX(mSecondIndex); float y1 = ev.getRawY(mSecondIndex); mLastPoint.set(x0, y0); mLastSecondPoint.set(x1, y1); double hypot0 = Math.hypot(x0 - mDownPoint.x, y0 - mDownPoint.y); double hypot1 = Math.hypot(x1 - mDownSecondaryPoint.x, y1 - mDownSecondaryPoint.y); // Capture inputs if (hypot0 > mTouchSlop && !mThresholdCrossed0) { mInputMonitor.pilferPointers(); mThresholdCrossed0 = true; // Reset the down to begin resizing from this point mDownPoint.set(x0, y0); } if (hypot1 > mTouchSlop && !mThresholdCrossed1) { if (!mThresholdCrossed && (distanceBetween(mDownSecondPoint, mLastSecondPoint) > mTouchSlop || distanceBetween(mDownPoint, mLastPoint) > mTouchSlop)) { mInputMonitor.pilferPointers(); mThresholdCrossed1 = true; mThresholdCrossed = true; // Reset the down to begin resizing from this point mDownSecondaryPoint.set(x1, y1); mDownPoint.set(mLastPoint); mDownSecondPoint.set(mLastSecondPoint); } if (mThresholdCrossed0 || mThresholdCrossed1) { if (mThresholdCrossed) { if (mPhonePipMenuController.isMenuVisible()) { mPhonePipMenuController.hideMenu(); } x0 = mThresholdCrossed0 ? x0 : mDownPoint.x; y0 = mThresholdCrossed0 ? y0 : mDownPoint.y; x1 = mThresholdCrossed1 ? x1 : mDownSecondaryPoint.x; y1 = mThresholdCrossed1 ? y1 : mDownSecondaryPoint.y; final Rect originalPipBounds = mPipBoundsState.getBounds(); int focusX = (int) originalPipBounds.centerX(); int focusY = (int) originalPipBounds.centerY(); float down0X = mDownPoint.x; float down0Y = mDownPoint.y; float down1X = mDownSecondaryPoint.x; float down1Y = mDownSecondaryPoint.y; float angle = 0; if (down0X > focusX && down0Y < focusY && down1X < focusX && down1Y > focusY) { // Top right + Bottom left pinch to zoom. angle = calculateRotationAngle(mLastResizeBounds.centerX(), mLastResizeBounds.centerY(), x0, y0, x1, y1, true); } else if (down1X > focusX && down1Y < focusY && down0X < focusX && down0Y > focusY) { // Top right + Bottom left pinch to zoom. angle = calculateRotationAngle(mLastResizeBounds.centerX(), mLastResizeBounds.centerY(), x1, y1, x0, y0, true); } else if (down0X < focusX && down0Y < focusY && down1X > focusX && down1Y > focusY) { // Top left + bottom right pinch to zoom. angle = calculateRotationAngle(mLastResizeBounds.centerX(), mLastResizeBounds.centerY(), x0, y0, x1, y1, false); } else if (down1X < focusX && down1Y < focusY && down0X > focusX && down0Y > focusY) { // Top left + bottom right pinch to zoom. angle = calculateRotationAngle(mLastResizeBounds.centerX(), mLastResizeBounds.centerY(), x1, y1, x0, y0, false); } mAngle = angle; mLastResizeBounds.set(PipPinchResizingAlgorithm.pinchResize(x0, y0, x1, y1, mDownPoint.x, mDownPoint.y, mDownSecondaryPoint.x, mDownSecondaryPoint.y, originalPipBounds, mMinSize.x, mMinSize.y, mMaxSize)); mPipTaskOrganizer.scheduleUserResizePip(mLastDownBounds, mLastResizeBounds, (float) -mAngle, null); mPipBoundsState.setHasUserResizedPip(true); } } } private float calculateRotationAngle(int pivotX, int pivotY, float topX, float topY, float bottomX, float bottomY, boolean positive) { // The base angle is the angle formed by taking the angle between the center horizontal // and one of the corners. double baseAngle = Math.toDegrees(Math.atan2(Math.abs(mLastResizeBounds.top - pivotY), Math.abs(mLastResizeBounds.right - pivotX))); double angle0 = mThresholdCrossed0 ? Math.toDegrees(Math.atan2(pivotY - topY, topX - pivotX)) : baseAngle; double angle1 = mThresholdCrossed0 ? Math.toDegrees(Math.atan2(pivotY - bottomY, bottomX - pivotX)) : baseAngle; if (positive) { angle1 = angle1 < 0 ? 180 + angle1 : angle1 - 180; } else { angle0 = angle0 < 0 ? -angle0 - 180 : 180 - angle0; angle1 = -angle1; } // Calculate the percentage difference of [0, 90] compare to the base angle. double diff0 = (Math.max(-90, Math.min(angle0, 90)) - baseAngle) / 90; double diff1 = (Math.max(-90, Math.min(angle1, 90)) - baseAngle) / 90; final float angle = (float) (diff0 + diff1) / 2 * PINCH_RESIZE_MAX_ANGLE_ROTATION * (positive ? 1 : -1); // Remove some degrees so that user doesn't immediately start rotating until a threshold return angle / Math.abs(angle) * Math.max(0, (Math.abs(dampedRotate(angle)) - ANGLE_THRESHOLD)); } /** * Given the current rotation angle, dampen it so that as it approaches the maximum angle, * dampen it. */ private float dampedRotate(float amount) { if (Float.compare(amount, 0) == 0) return 0; mAngle = mPinchResizingAlgorithm.calculateBoundsAndAngle(mDownPoint, mDownSecondPoint, mLastPoint, mLastSecondPoint, mMinSize, mMaxSize, mDownBounds, mLastResizeBounds); float f = amount / PINCH_RESIZE_MAX_ANGLE_ROTATION; f = f / (Math.abs(f)) * (overRotateInfluenceCurve(Math.abs(f))); // Clamp this factor, f, to -1 < f < 1 if (Math.abs(f) >= 1) { f /= Math.abs(f); mPipTaskOrganizer.scheduleUserResizePip(mDownBounds, mLastResizeBounds, mAngle, null); mPipBoundsState.setHasUserResizedPip(true); } return OVERROTATE_DAMP_FACTOR * f * PINCH_RESIZE_MAX_ANGLE_ROTATION; } /** * Returns a value that corresponds to y = (f - 1)^3 + 1. */ private float overRotateInfluenceCurve(float f) { f -= 1.0f; return f * f * f + 1.0f; } private void onDragCornerResize(MotionEvent ev) { Loading @@ -529,7 +436,7 @@ public class PipResizeGestureHandler { if (mAllowGesture) { setCtrlType((int) x, (int) y); mDownPoint.set(x, y); mLastDownBounds.set(mPipBoundsState.getBounds()); mDownBounds.set(mPipBoundsState.getBounds()); } if (!currentPipBounds.contains((int) ev.getX(), (int) ev.getY()) && mPhonePipMenuController.isMenuVisible()) { Loading Loading @@ -560,11 +467,11 @@ public class PipResizeGestureHandler { mLastResizeBounds.set(TaskResizingAlgorithm.resizeDrag(x, y, mDownPoint.x, mDownPoint.y, currentPipBounds, mCtrlType, mMinSize.x, mMinSize.y, mMaxSize, true, mLastDownBounds.width() > mLastDownBounds.height())); mDownBounds.width() > mDownBounds.height())); mPipBoundsAlgorithm.transformBoundsToAspectRatio(mLastResizeBounds, mPipBoundsState.getAspectRatio(), false /* useCurrentMinEdgeSize */, true /* useCurrentSize */); mPipTaskOrganizer.scheduleUserResizePip(mLastDownBounds, mLastResizeBounds, mPipTaskOrganizer.scheduleUserResizePip(mDownBounds, mLastResizeBounds, null); mPipBoundsState.setHasUserResizedPip(true); } Loading Loading @@ -593,12 +500,12 @@ public class PipResizeGestureHandler { // If user resize is pretty close to max size, just auto resize to max. if (mLastResizeBounds.width() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.x || mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) { mLastResizeBounds.set(0, 0, mMaxSize.x, mMaxSize.y); resizeRectAboutCenter(mLastResizeBounds, mMaxSize.x, mMaxSize.y); } final float snapFraction = mPipBoundsAlgorithm.getSnapFraction(mLastResizeBounds); mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction); mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds, PINCH_RESIZE_SNAP_DURATION, -mAngle, callback); PINCH_RESIZE_SNAP_DURATION, mAngle, callback); } else { mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds, PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE, callback); Loading Loading @@ -642,6 +549,20 @@ public class PipResizeGestureHandler { mOhmOffset = offset; } private float distanceBetween(PointF p1, PointF p2) { return (float) Math.hypot(p2.x - p1.x, p2.y - p1.y); } private void resizeRectAboutCenter(Rect rect, int w, int h) { int cx = rect.centerX(); int cy = rect.centerY(); int l = cx - w / 2; int r = l + w; int t = cy - h / 2; int b = t + h; rect.set(l, t, r, b); } public void dump(PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; pw.println(prefix + TAG); Loading libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +1 −1 Original line number Diff line number Diff line Loading @@ -664,7 +664,7 @@ public class PipTouchHandler { } else if (menuState == MENU_STATE_NONE && mMenuState == MENU_STATE_FULL) { // Try and restore the PiP to the closest edge, using the saved snap fraction // if possible if (resize) { if (resize && !mPipResizeGestureHandler.isResizing()) { if (mDeferResizeToNormalBoundsUntilRotation == -1) { // This is a very special case: when the menu is expanded and visible, // navigating to another activity can trigger auto-enter PiP, and if the Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java +6 −3 Original line number Diff line number Diff line Loading @@ -94,11 +94,14 @@ public class PipSurfaceTransactionHelper { public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceBounds, Rect destinationBounds, float degrees) { mTmpSourceRectF.set(sourceBounds); // We want the matrix to position the surface relative to the screen coordinates so offset // the source to 0,0 mTmpSourceRectF.offsetTo(0, 0); mTmpDestinationRectF.set(destinationBounds); mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL); mTmpTransform.postRotate(degrees); tx.setMatrix(leash, mTmpTransform, mTmpFloat9) .setPosition(leash, mTmpDestinationRectF.left, mTmpDestinationRectF.top); mTmpTransform.postRotate(degrees, mTmpDestinationRectF.centerX(), mTmpDestinationRectF.centerY()); tx.setMatrix(leash, mTmpTransform, mTmpFloat9); return this; } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipPinchResizingAlgorithm.java +99 −95 Original line number Diff line number Diff line Loading @@ -16,111 +16,115 @@ package com.android.wm.shell.pip.phone; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; /** * Helper class to calculate the new size given two-fingers pinch to resize. */ public class PipPinchResizingAlgorithm { private static final Rect TMP_RECT = new Rect(); private static final int PINCH_RESIZE_MAX_ANGLE_ROTATION = 45; private static final float OVERROTATE_DAMP_FACTOR = 0.4f; private static final float ANGLE_THRESHOLD = 5f; private final PointF mTmpDownVector = new PointF(); private final PointF mTmpLastVector = new PointF(); private final PointF mTmpDownCentroid = new PointF(); private final PointF mTmpLastCentroid = new PointF(); /** * Given inputs and requirements and current PiP bounds, return the new size. * * @param x0 x-coordinate of the primary input. * @param y0 y-coordinate of the primary input. * @param x1 x-coordinate of the secondary input. * @param y1 y-coordinate of the secondary input. * @param downx0 x-coordinate of the original down point of the primary input. * @param downy0 y-coordinate of the original down ponit of the primary input. * @param downx1 x-coordinate of the original down point of the secondary input. * @param downy1 y-coordinate of the original down point of the secondary input. * @param currentPipBounds current PiP bounds. * @param minVisibleWidth minimum visible width. * @param minVisibleHeight minimum visible height. * @param maxSize max size. * @return The new resized PiP bounds, sharing the same center. * Updates {@param resizeBoundsOut} with the new bounds of the PIP, and returns the angle in * degrees that the PIP should be rotated. */ public static Rect pinchResize(float x0, float y0, float x1, float y1, float downx0, float downy0, float downx1, float downy1, Rect currentPipBounds, int minVisibleWidth, int minVisibleHeight, Point maxSize) { int width = currentPipBounds.width(); int height = currentPipBounds.height(); int left = currentPipBounds.left; int top = currentPipBounds.top; int right = currentPipBounds.right; int bottom = currentPipBounds.bottom; final float aspect = (float) width / (float) height; final int widthDelta = Math.round(Math.abs(x0 - x1) - Math.abs(downx0 - downx1)); final int heightDelta = Math.round(Math.abs(y0 - y1) - Math.abs(downy0 - downy1)); final int dx = (int) ((x0 - downx0 + x1 - downx1) / 2); final int dy = (int) ((y0 - downy0 + y1 - downy1) / 2); width = Math.max(minVisibleWidth, Math.min(width + widthDelta, maxSize.x)); height = Math.max(minVisibleHeight, Math.min(height + heightDelta, maxSize.y)); // Calculate 2 rectangles fulfilling all requirements for either X or Y being the major // drag axis. What ever is producing the bigger rectangle will be chosen. int width1; int width2; int height1; int height2; if (aspect > 1.0f) { // Assuming that the width is our target we calculate the height. width1 = Math.max(minVisibleWidth, Math.min(maxSize.x, width)); height1 = Math.round((float) width1 / aspect); if (height1 < minVisibleHeight) { // If the resulting height is too small we adjust to the minimal size. height1 = minVisibleHeight; width1 = Math.max(minVisibleWidth, Math.min(maxSize.x, Math.round((float) height1 * aspect))); public float calculateBoundsAndAngle(PointF downPoint, PointF downSecondPoint, PointF lastPoint, PointF lastSecondPoint, Point minSize, Point maxSize, Rect initialBounds, Rect resizeBoundsOut) { float downDist = (float) Math.hypot(downSecondPoint.x - downPoint.x, downSecondPoint.y - downPoint.y); float dist = (float) Math.hypot(lastSecondPoint.x - lastPoint.x, lastSecondPoint.y - lastPoint.y); float minScale = getMinScale(initialBounds, minSize); float maxScale = getMaxScale(initialBounds, maxSize); float scale = Math.max(minScale, Math.min(maxScale, dist / downDist)); // Scale the bounds by the change in distance between the points resizeBoundsOut.set(initialBounds); scaleRectAboutCenter(resizeBoundsOut, scale); // Translate by the centroid movement getCentroid(downPoint, downSecondPoint, mTmpDownCentroid); getCentroid(lastPoint, lastSecondPoint, mTmpLastCentroid); resizeBoundsOut.offset((int) (mTmpLastCentroid.x - mTmpDownCentroid.x), (int) (mTmpLastCentroid.y - mTmpDownCentroid.y)); // Calculate the angle mTmpDownVector.set(downSecondPoint.x - downPoint.x, downSecondPoint.y - downPoint.y); mTmpLastVector.set(lastSecondPoint.x - lastPoint.x, lastSecondPoint.y - lastPoint.y); float angle = (float) Math.atan2(cross(mTmpDownVector, mTmpLastVector), dot(mTmpDownVector, mTmpLastVector)); return constrainRotationAngle((float) Math.toDegrees(angle)); } // Assuming that the height is our target we calculate the width. height2 = Math.max(minVisibleHeight, Math.min(maxSize.y, height)); width2 = Math.round((float) height2 * aspect); if (width2 < minVisibleWidth) { // If the resulting width is too small we adjust to the minimal size. width2 = minVisibleWidth; height2 = Math.max(minVisibleHeight, Math.min(maxSize.y, Math.round((float) width2 / aspect))); private float getMinScale(Rect bounds, Point minSize) { return Math.max((float) minSize.x / bounds.width(), (float) minSize.y / bounds.height()); } } else { // Assuming that the width is our target we calculate the height. width1 = Math.max(minVisibleWidth, Math.min(maxSize.x, width)); height1 = Math.round((float) width1 / aspect); if (height1 < minVisibleHeight) { // If the resulting height is too small we adjust to the minimal size. height1 = minVisibleHeight; width1 = Math.max(minVisibleWidth, Math.min(maxSize.x, Math.round((float) height1 * aspect))); private float getMaxScale(Rect bounds, Point maxSize) { return Math.min((float) maxSize.x / bounds.width(), (float) maxSize.y / bounds.height()); } // Assuming that the height is our target we calculate the width. height2 = Math.max(minVisibleHeight, Math.min(maxSize.y, height)); width2 = Math.round((float) height2 * aspect); if (width2 < minVisibleWidth) { // If the resulting width is too small we adjust to the minimal size. width2 = minVisibleWidth; height2 = Math.max(minVisibleHeight, Math.min(maxSize.y, Math.round((float) width2 / aspect))); private float constrainRotationAngle(float angle) { // Remove some degrees so that user doesn't immediately start rotating until a threshold return Math.signum(angle) * Math.max(0, (Math.abs(dampedRotate(angle)) - ANGLE_THRESHOLD)); } /** * Given the current rotation angle, dampen it so that as it approaches the maximum angle, * dampen it. */ private float dampedRotate(float amount) { if (Float.compare(amount, 0) == 0) return 0; float f = amount / PINCH_RESIZE_MAX_ANGLE_ROTATION; f = f / (Math.abs(f)) * (overRotateInfluenceCurve(Math.abs(f))); // Clamp this factor, f, to -1 < f < 1 if (Math.abs(f) >= 1) { f /= Math.abs(f); } return OVERROTATE_DAMP_FACTOR * f * PINCH_RESIZE_MAX_ANGLE_ROTATION; } /** * Returns a value that corresponds to y = (f - 1)^3 + 1. */ private float overRotateInfluenceCurve(float f) { f -= 1.0f; return f * f * f + 1.0f; } private void getCentroid(PointF p1, PointF p2, PointF centroidOut) { centroidOut.set((p2.x + p1.x) / 2, (p2.y + p1.y) / 2); } // Use the bigger of the two rectangles if the major change was positive, otherwise // do the opposite. final boolean grows = width > (right - left) || height > (bottom - top); if (grows == (width1 * height1 > width2 * height2)) { width = width1; height = height1; } else { width = width2; height = height2; private float dot(PointF p1, PointF p2) { return p1.x * p2.x + p1.y * p2.y; } TMP_RECT.set(currentPipBounds.centerX() - width / 2, currentPipBounds.centerY() - height / 2, currentPipBounds.centerX() + width / 2, currentPipBounds.centerY() + height / 2); TMP_RECT.offset(dx, dy); return TMP_RECT; private float cross(PointF p1, PointF p2) { return p1.x * p2.y - p1.y * p2.x; } private void scaleRectAboutCenter(Rect r, float scale) { if (scale != 1.0f) { int cx = r.centerX(); int cy = r.centerY(); r.offset(-cx, -cy); r.scale(scale); r.offset(cx, cy); } } }
libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java +51 −130 Original line number Diff line number Diff line Loading @@ -63,10 +63,7 @@ public class PipResizeGestureHandler { private static final String TAG = "PipResizeGestureHandler"; private static final int PINCH_RESIZE_SNAP_DURATION = 250; private static final int PINCH_RESIZE_MAX_ANGLE_ROTATION = 45; private static final float PINCH_RESIZE_AUTO_MAX_RATIO = 0.9f; private static final float OVERROTATE_DAMP_FACTOR = 0.4f; private static final float ANGLE_THRESHOLD = 5f; private final Context mContext; private final PipBoundsAlgorithm mPipBoundsAlgorithm; Loading @@ -75,17 +72,20 @@ public class PipResizeGestureHandler { private final PipTaskOrganizer mPipTaskOrganizer; private final PhonePipMenuController mPhonePipMenuController; private final PipUiEventLogger mPipUiEventLogger; private final PipPinchResizingAlgorithm mPinchResizingAlgorithm; private final int mDisplayId; private final ShellExecutor mMainExecutor; private final Region mTmpRegion = new Region(); private final PointF mDownPoint = new PointF(); private final PointF mDownSecondaryPoint = new PointF(); private final PointF mDownSecondPoint = new PointF(); private final PointF mLastPoint = new PointF(); private final PointF mLastSecondPoint = new PointF(); private final Point mMaxSize = new Point(); private final Point mMinSize = new Point(); private final Rect mLastResizeBounds = new Rect(); private final Rect mUserResizeBounds = new Rect(); private final Rect mLastDownBounds = new Rect(); private final Rect mDownBounds = new Rect(); private final Rect mDragCornerSize = new Rect(); private final Rect mTmpTopLeftCorner = new Rect(); private final Rect mTmpTopRightCorner = new Rect(); Loading @@ -103,11 +103,7 @@ public class PipResizeGestureHandler { private boolean mIsEnabled; private boolean mEnablePinchResize; private boolean mIsSysUiStateValid; // For drag-resize private boolean mThresholdCrossed; // For pinch-resize private boolean mThresholdCrossed0; private boolean mThresholdCrossed1; private boolean mOngoingPinchToResize = false; private float mAngle = 0; int mFirstIndex = -1; Loading Loading @@ -135,6 +131,7 @@ public class PipResizeGestureHandler { mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable; mPhonePipMenuController = menuActivityController; mPipUiEventLogger = pipUiEventLogger; mPinchResizingAlgorithm = new PipPinchResizingAlgorithm(); } public void init() { Loading Loading @@ -294,6 +291,10 @@ public class PipResizeGestureHandler { return mEnablePinchResize; } public boolean isResizing() { return mAllowGesture; } public boolean willStartResizeGesture(MotionEvent ev) { if (isInValidSysUiState()) { switch (ev.getActionMasked()) { Loading Loading @@ -362,6 +363,7 @@ public class PipResizeGestureHandler { if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { mFirstIndex = -1; mSecondIndex = -1; mAllowGesture = false; finishResize(); } Loading @@ -371,14 +373,16 @@ public class PipResizeGestureHandler { if (action == MotionEvent.ACTION_POINTER_DOWN) { if (mFirstIndex == -1 && mSecondIndex == -1) { mAllowGesture = true; mFirstIndex = 0; mSecondIndex = 1; mDownPoint.set(ev.getRawX(mFirstIndex), ev.getRawY(mFirstIndex)); mDownSecondaryPoint.set(ev.getRawX(mSecondIndex), ev.getRawY(mSecondIndex)); mDownSecondPoint.set(ev.getRawX(mSecondIndex), ev.getRawY(mSecondIndex)); mDownBounds.set(mPipBoundsState.getBounds()); mLastDownBounds.set(mPipBoundsState.getBounds()); mLastResizeBounds.set(mLastDownBounds); mLastPoint.set(mDownPoint); mLastSecondPoint.set(mLastSecondPoint); mLastResizeBounds.set(mDownBounds); } } Loading @@ -391,131 +395,34 @@ public class PipResizeGestureHandler { float y0 = ev.getRawY(mFirstIndex); float x1 = ev.getRawX(mSecondIndex); float y1 = ev.getRawY(mSecondIndex); mLastPoint.set(x0, y0); mLastSecondPoint.set(x1, y1); double hypot0 = Math.hypot(x0 - mDownPoint.x, y0 - mDownPoint.y); double hypot1 = Math.hypot(x1 - mDownSecondaryPoint.x, y1 - mDownSecondaryPoint.y); // Capture inputs if (hypot0 > mTouchSlop && !mThresholdCrossed0) { mInputMonitor.pilferPointers(); mThresholdCrossed0 = true; // Reset the down to begin resizing from this point mDownPoint.set(x0, y0); } if (hypot1 > mTouchSlop && !mThresholdCrossed1) { if (!mThresholdCrossed && (distanceBetween(mDownSecondPoint, mLastSecondPoint) > mTouchSlop || distanceBetween(mDownPoint, mLastPoint) > mTouchSlop)) { mInputMonitor.pilferPointers(); mThresholdCrossed1 = true; mThresholdCrossed = true; // Reset the down to begin resizing from this point mDownSecondaryPoint.set(x1, y1); mDownPoint.set(mLastPoint); mDownSecondPoint.set(mLastSecondPoint); } if (mThresholdCrossed0 || mThresholdCrossed1) { if (mThresholdCrossed) { if (mPhonePipMenuController.isMenuVisible()) { mPhonePipMenuController.hideMenu(); } x0 = mThresholdCrossed0 ? x0 : mDownPoint.x; y0 = mThresholdCrossed0 ? y0 : mDownPoint.y; x1 = mThresholdCrossed1 ? x1 : mDownSecondaryPoint.x; y1 = mThresholdCrossed1 ? y1 : mDownSecondaryPoint.y; final Rect originalPipBounds = mPipBoundsState.getBounds(); int focusX = (int) originalPipBounds.centerX(); int focusY = (int) originalPipBounds.centerY(); float down0X = mDownPoint.x; float down0Y = mDownPoint.y; float down1X = mDownSecondaryPoint.x; float down1Y = mDownSecondaryPoint.y; float angle = 0; if (down0X > focusX && down0Y < focusY && down1X < focusX && down1Y > focusY) { // Top right + Bottom left pinch to zoom. angle = calculateRotationAngle(mLastResizeBounds.centerX(), mLastResizeBounds.centerY(), x0, y0, x1, y1, true); } else if (down1X > focusX && down1Y < focusY && down0X < focusX && down0Y > focusY) { // Top right + Bottom left pinch to zoom. angle = calculateRotationAngle(mLastResizeBounds.centerX(), mLastResizeBounds.centerY(), x1, y1, x0, y0, true); } else if (down0X < focusX && down0Y < focusY && down1X > focusX && down1Y > focusY) { // Top left + bottom right pinch to zoom. angle = calculateRotationAngle(mLastResizeBounds.centerX(), mLastResizeBounds.centerY(), x0, y0, x1, y1, false); } else if (down1X < focusX && down1Y < focusY && down0X > focusX && down0Y > focusY) { // Top left + bottom right pinch to zoom. angle = calculateRotationAngle(mLastResizeBounds.centerX(), mLastResizeBounds.centerY(), x1, y1, x0, y0, false); } mAngle = angle; mLastResizeBounds.set(PipPinchResizingAlgorithm.pinchResize(x0, y0, x1, y1, mDownPoint.x, mDownPoint.y, mDownSecondaryPoint.x, mDownSecondaryPoint.y, originalPipBounds, mMinSize.x, mMinSize.y, mMaxSize)); mPipTaskOrganizer.scheduleUserResizePip(mLastDownBounds, mLastResizeBounds, (float) -mAngle, null); mPipBoundsState.setHasUserResizedPip(true); } } } private float calculateRotationAngle(int pivotX, int pivotY, float topX, float topY, float bottomX, float bottomY, boolean positive) { // The base angle is the angle formed by taking the angle between the center horizontal // and one of the corners. double baseAngle = Math.toDegrees(Math.atan2(Math.abs(mLastResizeBounds.top - pivotY), Math.abs(mLastResizeBounds.right - pivotX))); double angle0 = mThresholdCrossed0 ? Math.toDegrees(Math.atan2(pivotY - topY, topX - pivotX)) : baseAngle; double angle1 = mThresholdCrossed0 ? Math.toDegrees(Math.atan2(pivotY - bottomY, bottomX - pivotX)) : baseAngle; if (positive) { angle1 = angle1 < 0 ? 180 + angle1 : angle1 - 180; } else { angle0 = angle0 < 0 ? -angle0 - 180 : 180 - angle0; angle1 = -angle1; } // Calculate the percentage difference of [0, 90] compare to the base angle. double diff0 = (Math.max(-90, Math.min(angle0, 90)) - baseAngle) / 90; double diff1 = (Math.max(-90, Math.min(angle1, 90)) - baseAngle) / 90; final float angle = (float) (diff0 + diff1) / 2 * PINCH_RESIZE_MAX_ANGLE_ROTATION * (positive ? 1 : -1); // Remove some degrees so that user doesn't immediately start rotating until a threshold return angle / Math.abs(angle) * Math.max(0, (Math.abs(dampedRotate(angle)) - ANGLE_THRESHOLD)); } /** * Given the current rotation angle, dampen it so that as it approaches the maximum angle, * dampen it. */ private float dampedRotate(float amount) { if (Float.compare(amount, 0) == 0) return 0; mAngle = mPinchResizingAlgorithm.calculateBoundsAndAngle(mDownPoint, mDownSecondPoint, mLastPoint, mLastSecondPoint, mMinSize, mMaxSize, mDownBounds, mLastResizeBounds); float f = amount / PINCH_RESIZE_MAX_ANGLE_ROTATION; f = f / (Math.abs(f)) * (overRotateInfluenceCurve(Math.abs(f))); // Clamp this factor, f, to -1 < f < 1 if (Math.abs(f) >= 1) { f /= Math.abs(f); mPipTaskOrganizer.scheduleUserResizePip(mDownBounds, mLastResizeBounds, mAngle, null); mPipBoundsState.setHasUserResizedPip(true); } return OVERROTATE_DAMP_FACTOR * f * PINCH_RESIZE_MAX_ANGLE_ROTATION; } /** * Returns a value that corresponds to y = (f - 1)^3 + 1. */ private float overRotateInfluenceCurve(float f) { f -= 1.0f; return f * f * f + 1.0f; } private void onDragCornerResize(MotionEvent ev) { Loading @@ -529,7 +436,7 @@ public class PipResizeGestureHandler { if (mAllowGesture) { setCtrlType((int) x, (int) y); mDownPoint.set(x, y); mLastDownBounds.set(mPipBoundsState.getBounds()); mDownBounds.set(mPipBoundsState.getBounds()); } if (!currentPipBounds.contains((int) ev.getX(), (int) ev.getY()) && mPhonePipMenuController.isMenuVisible()) { Loading Loading @@ -560,11 +467,11 @@ public class PipResizeGestureHandler { mLastResizeBounds.set(TaskResizingAlgorithm.resizeDrag(x, y, mDownPoint.x, mDownPoint.y, currentPipBounds, mCtrlType, mMinSize.x, mMinSize.y, mMaxSize, true, mLastDownBounds.width() > mLastDownBounds.height())); mDownBounds.width() > mDownBounds.height())); mPipBoundsAlgorithm.transformBoundsToAspectRatio(mLastResizeBounds, mPipBoundsState.getAspectRatio(), false /* useCurrentMinEdgeSize */, true /* useCurrentSize */); mPipTaskOrganizer.scheduleUserResizePip(mLastDownBounds, mLastResizeBounds, mPipTaskOrganizer.scheduleUserResizePip(mDownBounds, mLastResizeBounds, null); mPipBoundsState.setHasUserResizedPip(true); } Loading Loading @@ -593,12 +500,12 @@ public class PipResizeGestureHandler { // If user resize is pretty close to max size, just auto resize to max. if (mLastResizeBounds.width() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.x || mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) { mLastResizeBounds.set(0, 0, mMaxSize.x, mMaxSize.y); resizeRectAboutCenter(mLastResizeBounds, mMaxSize.x, mMaxSize.y); } final float snapFraction = mPipBoundsAlgorithm.getSnapFraction(mLastResizeBounds); mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction); mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds, PINCH_RESIZE_SNAP_DURATION, -mAngle, callback); PINCH_RESIZE_SNAP_DURATION, mAngle, callback); } else { mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds, PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE, callback); Loading Loading @@ -642,6 +549,20 @@ public class PipResizeGestureHandler { mOhmOffset = offset; } private float distanceBetween(PointF p1, PointF p2) { return (float) Math.hypot(p2.x - p1.x, p2.y - p1.y); } private void resizeRectAboutCenter(Rect rect, int w, int h) { int cx = rect.centerX(); int cy = rect.centerY(); int l = cx - w / 2; int r = l + w; int t = cy - h / 2; int b = t + h; rect.set(l, t, r, b); } public void dump(PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; pw.println(prefix + TAG); Loading
libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +1 −1 Original line number Diff line number Diff line Loading @@ -664,7 +664,7 @@ public class PipTouchHandler { } else if (menuState == MENU_STATE_NONE && mMenuState == MENU_STATE_FULL) { // Try and restore the PiP to the closest edge, using the saved snap fraction // if possible if (resize) { if (resize && !mPipResizeGestureHandler.isResizing()) { if (mDeferResizeToNormalBoundsUntilRotation == -1) { // This is a very special case: when the menu is expanded and visible, // navigating to another activity can trigger auto-enter PiP, and if the Loading