From fa7053789f6f874ea1f950826d2471d910114f6e Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Tue, 8 Nov 2016 15:45:10 -0800 Subject: [PATCH] Adding experiment for minimized pinned stack. - Also refactoring the PIP touch handling to be independent gestures Test: Enable the setting in SystemUI tuner, then drag the PIP slightly offscreen. This is only experimental behaviour, and android.server.cts.ActivityManagerPinnedStackTests will be updated accordingly if we keep this behavior. Change-Id: I5834971fcbbb127526339e764e7d76b5d22d4707 --- core/java/android/view/IPinnedStackController.aidl | 5 + .../android/internal/policy/PipSnapAlgorithm.java | 12 +- packages/SystemUI/res/values/strings.xml | 16 +- packages/SystemUI/res/xml/tuner_prefs.xml | 6 + .../systemui/pip/phone/PipTouchGesture.java | 42 ++ .../systemui/pip/phone/PipTouchHandler.java | 548 +++++++++++++++------ .../android/systemui/pip/phone/PipTouchState.java | 176 +++++++ .../android/server/wm/PinnedStackController.java | 9 + 8 files changed, 648 insertions(+), 166 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchGesture.java create mode 100644 packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java diff --git a/core/java/android/view/IPinnedStackController.aidl b/core/java/android/view/IPinnedStackController.aidl index a81eef831f4e..d59be02c7371 100644 --- a/core/java/android/view/IPinnedStackController.aidl +++ b/core/java/android/view/IPinnedStackController.aidl @@ -32,6 +32,11 @@ interface IPinnedStackController { oneway void setInInteractiveMode(boolean inInteractiveMode); /** + * Notifies the controller that the PIP is currently minimized. + */ + oneway void setIsMinimized(boolean isMinimized); + + /** * Notifies the controller that the desired snap mode is to the closest edge. */ oneway void setSnapToEdge(boolean snapToEdge); diff --git a/core/java/com/android/internal/policy/PipSnapAlgorithm.java b/core/java/com/android/internal/policy/PipSnapAlgorithm.java index cbacf269a0f0..1e2a53ba97e4 100644 --- a/core/java/com/android/internal/policy/PipSnapAlgorithm.java +++ b/core/java/com/android/internal/policy/PipSnapAlgorithm.java @@ -208,15 +208,19 @@ public class PipSnapAlgorithm { final int fromTop = Math.abs(stackBounds.top - movementBounds.top); final int fromRight = Math.abs(movementBounds.right - stackBounds.left); final int fromBottom = Math.abs(movementBounds.bottom - stackBounds.top); + final int boundedLeft = Math.max(movementBounds.left, Math.min(movementBounds.right, + stackBounds.left)); + final int boundedTop = Math.max(movementBounds.top, Math.min(movementBounds.bottom, + stackBounds.top)); boundsOut.set(stackBounds); if (fromLeft <= fromTop && fromLeft <= fromRight && fromLeft <= fromBottom) { - boundsOut.offsetTo(movementBounds.left, stackBounds.top); + boundsOut.offsetTo(movementBounds.left, boundedTop); } else if (fromTop <= fromLeft && fromTop <= fromRight && fromTop <= fromBottom) { - boundsOut.offsetTo(stackBounds.left, movementBounds.top); + boundsOut.offsetTo(boundedLeft, movementBounds.top); } else if (fromRight < fromLeft && fromRight < fromTop && fromRight < fromBottom) { - boundsOut.offsetTo(movementBounds.right, stackBounds.top); + boundsOut.offsetTo(movementBounds.right, boundedTop); } else { - boundsOut.offsetTo(stackBounds.left, movementBounds.bottom); + boundsOut.offsetTo(boundedLeft, movementBounds.bottom); } } diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 37a7e38eb4f9..b1d81ca6d5ac 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1698,20 +1698,28 @@ not appear on production builds ever. --> Drag to the dismiss target at the bottom of the screen to close the PIP - Tap to interact - Tap once to interact with the activity - Snap to closest edge - Snap to the closest edge + + Allow PIP to minimize + + + Allow PIP to minimize slightly offscreen + diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml index f09d6e9fe052..74d5d6ccf92d 100644 --- a/packages/SystemUI/res/xml/tuner_prefs.xml +++ b/packages/SystemUI/res/xml/tuner_prefs.xml @@ -149,6 +149,12 @@ android:summary="@string/pip_snap_mode_edge_summary" sysui:defValue="false" /> + + mViewConfig.getScaledTouchSlop()) { - mIsDragging = true; - mIsTappingThrough = false; - mMenuController.hideMenu(); - if (mEnableSwipeToDismiss) { - // TODO: this check can have some buffer so that we only start swiping - // after a significant move out of bounds - mIsSwipingToDismiss = !(mBoundedPinnedStackBounds.left <= left && - left <= mBoundedPinnedStackBounds.right) && - Math.abs(mDownTouch.x - x) > Math.abs(y - mLastTouch.y); - } - if (mEnableDragToDismiss) { - mDismissViewController.showDismissTarget(); - } + for (PipTouchGesture gesture : mGestures) { + if (gesture.onMove(mTouchState)) { + break; } } - - if (mIsSwipingToDismiss) { - // Ignore the vertical movement - mTmpBounds.set(mPinnedStackBounds); - mTmpBounds.offsetTo((int) left, mPinnedStackBounds.top); - if (!mTmpBounds.equals(mPinnedStackBounds)) { - mPinnedStackBounds.set(mTmpBounds); - mMotionHelper.resizeToBounds(mPinnedStackBounds); - } - } else if (mIsDragging) { - // Move the pinned stack - if (!DEBUG_ALLOW_OUT_OF_BOUNDS_STACK) { - left = Math.max(mBoundedPinnedStackBounds.left, Math.min( - mBoundedPinnedStackBounds.right, left)); - top = Math.max(mBoundedPinnedStackBounds.top, Math.min( - mBoundedPinnedStackBounds.bottom, top)); - } - mTmpBounds.set(mPinnedStackBounds); - mTmpBounds.offsetTo((int) left, (int) top); - if (!mTmpBounds.equals(mPinnedStackBounds)) { - mPinnedStackBounds.set(mTmpBounds); - mMotionHelper.resizeToBounds(mPinnedStackBounds); - } - } - mLastTouch.set(ev.getX(), ev.getY()); - break; - } - case MotionEvent.ACTION_POINTER_UP: { - // Update the velocity tracker - mVelocityTracker.addMovement(ev); - - int pointerIndex = ev.getActionIndex(); - int pointerId = ev.getPointerId(pointerIndex); - if (pointerId == mActivePointerId) { - // Select a new active pointer id and reset the movement state - final int newPointerIndex = (pointerIndex == 0) ? 1 : 0; - mActivePointerId = ev.getPointerId(newPointerIndex); - mLastTouch.set(ev.getX(newPointerIndex), ev.getY(newPointerIndex)); - } break; } case MotionEvent.ACTION_UP: { - // Update the velocity tracker - mVelocityTracker.addMovement(ev); - mVelocityTracker.computeCurrentVelocity(1000, - ViewConfiguration.get(mContext).getScaledMaximumFlingVelocity()); - float velocityX = mVelocityTracker.getXVelocity(); - float velocityY = mVelocityTracker.getYVelocity(); - float velocity = PointF.length(velocityX, velocityY); - // Update the movement bounds again if the state has changed since the user started // dragging (ie. when the IME shows) updateBoundedPinnedStackBounds(false /* updatePinnedStackBounds */); - if (mIsSwipingToDismiss) { - if (Math.abs(velocityX) > mFlingAnimationUtils.getMinVelocityPxPerSecond()) { - flingToDismiss(velocityX); - } else { - animateToClosestSnapTarget(); - } - } else if (mIsDragging) { - if (velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond()) { - flingToSnapTarget(velocity, velocityX, velocityY); - } else { - int activePointerIndex = ev.findPointerIndex(mActivePointerId); - int x = (int) ev.getX(activePointerIndex); - int y = (int) ev.getY(activePointerIndex); - Rect dismissBounds = mEnableDragToDismiss - ? mDismissViewController.getDismissBounds() - : null; - if (dismissBounds != null && dismissBounds.contains(x, y)) { - animateDismissPinnedStack(dismissBounds); - } else { - animateToClosestSnapTarget(); - } + for (PipTouchGesture gesture : mGestures) { + if (gesture.onUp(mTouchState)) { + break; } - } else { - if (mEnableTapThrough) { - if (!mIsTappingThrough) { - mMenuController.showMenu(); - mIsTappingThrough = true; - } - } else { - expandPinnedStackToFullscreen(); - } - } - if (mEnableDragToDismiss) { - mDismissViewController.destroyDismissTarget(); } // Fall through to clean up } case MotionEvent.ACTION_CANCEL: { - mIsDragging = false; - mIsSwipingToDismiss = false; try { mPinnedStackController.setInInteractiveMode(false); } catch (RemoteException e) { Log.e(TAG, "Could not set dragging state", e); } - recycleVelocityTracker(); break; } } return !mIsTappingThrough; } - private void initOrResetVelocityTracker() { - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } else { - mVelocityTracker.clear(); - } - } - - private void recycleVelocityTracker() { - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; - } + /** + * @return whether the current touch state is a horizontal drag offscreen. + */ + private boolean isDraggingOffscreen(PipTouchState touchState) { + PointF lastDelta = touchState.getLastTouchDelta(); + PointF downDelta = touchState.getDownTouchDelta(); + float left = mPinnedStackBounds.left + lastDelta.x; + return !(mBoundedPinnedStackBounds.left <= left && left <= mBoundedPinnedStackBounds.right) + && Math.abs(downDelta.x) > Math.abs(downDelta.y); } /** @@ -449,6 +363,74 @@ public class PipTouchHandler implements TunerService.Tunable { } /** + * Sets the minimized state and notifies the controller. + */ + private void setMinimizedState(boolean isMinimized) { + mIsMinimized = isMinimized; + try { + mPinnedStackController.setIsMinimized(isMinimized); + } catch (RemoteException e) { + Log.e(TAG, "Could not set minimized state", e); + } + } + + /** + * @return whether the given {@param pinnedStackBounds} indicates the PIP should be minimized. + */ + private boolean shouldMinimizedPinnedStack() { + Point displaySize = new Point(); + mContext.getDisplay().getRealSize(displaySize); + if (mPinnedStackBounds.left < 0) { + float offscreenFraction = (float) -mPinnedStackBounds.left / mPinnedStackBounds.width(); + return offscreenFraction >= MINIMIZE_OFFSCREEN_FRACTION; + } else if (mPinnedStackBounds.right > displaySize.x) { + float offscreenFraction = (float) (mPinnedStackBounds.right - displaySize.x) / + mPinnedStackBounds.width(); + return offscreenFraction >= MINIMIZE_OFFSCREEN_FRACTION; + } else { + return false; + } + } + + /** + * Flings the minimized PIP to the closest minimized snap target. + */ + private void flingToMinimizedSnapTarget(float velocityY) { + Rect movementBounds = new Rect(mPinnedStackBounds.left, mBoundedPinnedStackBounds.top, + mPinnedStackBounds.left, mBoundedPinnedStackBounds.bottom); + Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mPinnedStackBounds, + 0 /* velocityX */, velocityY); + if (!mPinnedStackBounds.equals(toBounds)) { + mPinnedStackBoundsAnimator = mMotionHelper.createAnimationToBounds(mPinnedStackBounds, + toBounds, 0, FAST_OUT_SLOW_IN, mUpdatePinnedStackBoundsListener); + mFlingAnimationUtils.apply(mPinnedStackBoundsAnimator, 0, + distanceBetweenRectOffsets(mPinnedStackBounds, toBounds), + velocityY); + mPinnedStackBoundsAnimator.start(); + } + } + + /** + * Animates the PIP to the minimized state, slightly offscreen. + */ + private void animateToClosestMinimizedTarget() { + Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(mBoundedPinnedStackBounds, + mPinnedStackBounds); + Point displaySize = new Point(); + mContext.getDisplay().getRealSize(displaySize); + int visibleWidth = (int) (MINIMIZED_VISIBLE_FRACTION * mPinnedStackBounds.width()); + if (mPinnedStackBounds.left < 0) { + toBounds.offsetTo(-toBounds.width() + visibleWidth, toBounds.top); + } else if (mPinnedStackBounds.right > displaySize.x) { + toBounds.offsetTo(displaySize.x - visibleWidth, toBounds.top); + } + mPinnedStackBoundsAnimator = mMotionHelper.createAnimationToBounds(mPinnedStackBounds, + toBounds, MINIMIZE_STACK_MAX_DURATION, LINEAR_OUT_SLOW_IN, + mUpdatePinnedStackBoundsListener); + mPinnedStackBoundsAnimator.start(); + } + + /** * Flings the PIP to the closest snap target. */ private void flingToSnapTarget(float velocity, float velocityX, float velocityY) { @@ -478,12 +460,26 @@ public class PipTouchHandler implements TunerService.Tunable { } /** + * @return whether the velocity is coincident with the current pinned stack bounds to be + * considered a fling to dismiss. + */ + private boolean isFlingToDismiss(float velocityX) { + Point displaySize = new Point(); + mContext.getDisplay().getRealSize(displaySize); + return (mPinnedStackBounds.right > displaySize.x && velocityX > 0) || + (mPinnedStackBounds.left < 0 && velocityX < 0); + } + + /** * Flings the PIP to dismiss it offscreen. */ private void flingToDismiss(float velocityX) { + Point displaySize = new Point(); + mContext.getDisplay().getRealSize(displaySize); float offsetX = velocityX > 0 - ? mBoundedPinnedStackBounds.right + 2 * mPinnedStackBounds.width() - : mBoundedPinnedStackBounds.left - 2 * mPinnedStackBounds.width(); + ? displaySize.x + mPinnedStackBounds.width() + : -mPinnedStackBounds.width(); + Rect toBounds = new Rect(mPinnedStackBounds); toBounds.offsetTo((int) offsetX, toBounds.top); if (!mPinnedStackBounds.equals(toBounds)) { @@ -495,13 +491,7 @@ public class PipTouchHandler implements TunerService.Tunable { mPinnedStackBoundsAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - BackgroundThread.getHandler().post(() -> { - try { - mActivityManager.removeStack(PINNED_STACK_ID); - } catch (RemoteException e) { - Log.e(TAG, "Failed to remove PIP", e); - } - }); + BackgroundThread.getHandler().post(PipTouchHandler.this::dismissPinnedStack); } }); mPinnedStackBoundsAnimator.start(); @@ -521,13 +511,7 @@ public class PipTouchHandler implements TunerService.Tunable { mPinnedStackBoundsAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - BackgroundThread.getHandler().post(() -> { - try { - mActivityManager.removeStack(PINNED_STACK_ID); - } catch (RemoteException e) { - Log.e(TAG, "Failed to remove PIP", e); - } - }); + BackgroundThread.getHandler().post(PipTouchHandler.this::dismissPinnedStack); } }); mPinnedStackBoundsAnimator.start(); @@ -549,6 +533,27 @@ public class PipTouchHandler implements TunerService.Tunable { } /** + * Tries to the move the pinned stack to the given {@param bounds}. + */ + private void movePinnedStack(Rect bounds) { + if (!bounds.equals(mPinnedStackBounds)) { + mPinnedStackBounds.set(bounds); + mMotionHelper.resizeToBounds(mPinnedStackBounds); + } + } + + /** + * Dismisses the pinned stack. + */ + private void dismissPinnedStack() { + try { + mActivityManager.removeStack(PINNED_STACK_ID); + } catch (RemoteException e) { + Log.e(TAG, "Failed to remove PIP", e); + } + } + + /** * Updates the movement bounds of the pinned stack. */ private void updateBoundedPinnedStackBounds(boolean updatePinnedStackBounds) { @@ -572,4 +577,231 @@ public class PipTouchHandler implements TunerService.Tunable { private float distanceBetweenRectOffsets(Rect r1, Rect r2) { return PointF.length(r1.left - r2.left, r1.top - r2.top); } + + /** + * Gesture controlling dragging over a target to dismiss the PIP. + */ + private PipTouchGesture mDragToDismissGesture = new PipTouchGesture() { + @Override + public void onDown(PipTouchState touchState) { + if (mEnableDragToDismiss) { + // TODO: Consider setting a timer such at after X time, we show the dismiss + // target if the user hasn't already dragged some distance + mDismissViewController.createDismissTarget(); + } + } + + @Override + boolean onMove(PipTouchState touchState) { + if (mEnableDragToDismiss && touchState.startedDragging()) { + mDismissViewController.showDismissTarget(); + } + return false; + } + + @Override + public boolean onUp(PipTouchState touchState) { + if (mEnableDragToDismiss) { + try { + if (touchState.isDragging()) { + Rect dismissBounds = mDismissViewController.getDismissBounds(); + PointF lastTouch = touchState.getLastTouchPosition(); + if (dismissBounds.contains((int) lastTouch.x, (int) lastTouch.y)) { + animateDismissPinnedStack(dismissBounds); + return true; + } + } + } finally { + mDismissViewController.destroyDismissTarget(); + } + } + return false; + } + }; + + /**** Gestures ****/ + + /** + * Gesture controlling swiping offscreen to dismiss the PIP. + */ + private PipTouchGesture mSwipeToDismissGesture = new PipTouchGesture() { + @Override + boolean onMove(PipTouchState touchState) { + if (mEnableSwipeToDismiss) { + boolean isDraggingOffscreen = isDraggingOffscreen(touchState); + + if (touchState.startedDragging() && isDraggingOffscreen) { + // Reset the minimized state once we drag horizontally + setMinimizedState(false); + } + + if (isDraggingOffscreen) { + // Move the pinned stack, but ignore the vertical movement + float left = mPinnedStackBounds.left + touchState.getLastTouchDelta().x; + mTmpBounds.set(mPinnedStackBounds); + mTmpBounds.offsetTo((int) left, mPinnedStackBounds.top); + if (!mTmpBounds.equals(mPinnedStackBounds)) { + mPinnedStackBounds.set(mTmpBounds); + mMotionHelper.resizeToBounds(mPinnedStackBounds); + } + return true; + } + } + return false; + } + + @Override + public boolean onUp(PipTouchState touchState) { + if (mEnableSwipeToDismiss && touchState.isDragging()) { + PointF vel = touchState.getVelocity(); + PointF downDelta = touchState.getDownTouchDelta(); + float minFlingVel = mFlingAnimationUtils.getMinVelocityPxPerSecond(); + float flingVelScale = mEnableMinimizing ? 3f : 2f; + if (Math.abs(vel.x) > (flingVelScale * minFlingVel)) { + // Determine if this gesture is actually a fling to dismiss + if (isFlingToDismiss(vel.x) && Math.abs(downDelta.x) >= + (DISMISS_FLING_DISTANCE_FRACTION * mPinnedStackBounds.width())) { + flingToDismiss(vel.x); + } else { + flingToSnapTarget(vel.length(), vel.x, vel.y); + } + return true; + } + } + return false; + } + }; + + /** + * Gesture controlling dragging the PIP slightly offscreen to minimize it. + */ + private PipTouchGesture mMinimizeGesture = new PipTouchGesture() { + @Override + boolean onMove(PipTouchState touchState) { + if (mEnableMinimizing) { + boolean isDraggingOffscreen = isDraggingOffscreen(touchState); + if (touchState.startedDragging() && isDraggingOffscreen) { + // Reset the minimized state once we drag horizontally + setMinimizedState(false); + } + + if (isDraggingOffscreen) { + // Move the pinned stack, but ignore the vertical movement + float left = mPinnedStackBounds.left + touchState.getLastTouchDelta().x; + mTmpBounds.set(mPinnedStackBounds); + mTmpBounds.offsetTo((int) left, mPinnedStackBounds.top); + if (!mTmpBounds.equals(mPinnedStackBounds)) { + mPinnedStackBounds.set(mTmpBounds); + mMotionHelper.resizeToBounds(mPinnedStackBounds); + } + return true; + } else if (mIsMinimized && touchState.isDragging()) { + // Move the pinned stack, but ignore the horizontal movement + PointF lastDelta = touchState.getLastTouchDelta(); + float top = mPinnedStackBounds.top + lastDelta.y; + top = Math.max(mBoundedPinnedStackBounds.top, Math.min( + mBoundedPinnedStackBounds.bottom, top)); + mTmpBounds.set(mPinnedStackBounds); + mTmpBounds.offsetTo(mPinnedStackBounds.left, (int) top); + movePinnedStack(mTmpBounds); + return true; + } + } + return false; + } + + @Override + public boolean onUp(PipTouchState touchState) { + if (mEnableMinimizing) { + if (touchState.isDragging()) { + if (isDraggingOffscreen(touchState)) { + if (shouldMinimizedPinnedStack()) { + setMinimizedState(true); + animateToClosestMinimizedTarget(); + return true; + } + } else if (mIsMinimized) { + PointF vel = touchState.getVelocity(); + if (vel.length() > mFlingAnimationUtils.getMinVelocityPxPerSecond()) { + flingToMinimizedSnapTarget(vel.y); + } else { + animateToClosestMinimizedTarget(); + } + return true; + } + } else if (mIsMinimized) { + setMinimizedState(false); + animateToClosestSnapTarget(); + return true; + } + } + return false; + } + }; + + /** + * Gesture controlling tapping on the PIP to show an overlay. + */ + private PipTouchGesture mTapThroughGesture = new PipTouchGesture() { + @Override + boolean onMove(PipTouchState touchState) { + if (mEnableTapThrough && touchState.startedDragging()) { + mIsTappingThrough = false; + mMenuController.hideMenu(); + } + return false; + } + + @Override + public boolean onUp(PipTouchState touchState) { + if (mEnableTapThrough && !touchState.isDragging() && !mIsTappingThrough) { + mMenuController.showMenu(); + mIsTappingThrough = true; + return true; + } + return false; + } + }; + + /** + * Gesture controlling normal movement of the PIP. + */ + private PipTouchGesture mDefaultMovementGesture = new PipTouchGesture() { + @Override + boolean onMove(PipTouchState touchState) { + if (touchState.isDragging()) { + // Move the pinned stack freely + PointF lastDelta = touchState.getLastTouchDelta(); + float left = mPinnedStackBounds.left + lastDelta.x; + float top = mPinnedStackBounds.top + lastDelta.y; + if (!DEBUG_ALLOW_OUT_OF_BOUNDS_STACK) { + left = Math.max(mBoundedPinnedStackBounds.left, Math.min( + mBoundedPinnedStackBounds.right, left)); + top = Math.max(mBoundedPinnedStackBounds.top, Math.min( + mBoundedPinnedStackBounds.bottom, top)); + } + mTmpBounds.set(mPinnedStackBounds); + mTmpBounds.offsetTo((int) left, (int) top); + movePinnedStack(mTmpBounds); + return true; + } + return false; + } + + @Override + public boolean onUp(PipTouchState touchState) { + if (touchState.isDragging()) { + PointF vel = mTouchState.getVelocity(); + float velocity = PointF.length(vel.x, vel.y); + if (velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond()) { + flingToSnapTarget(velocity, vel.x, vel.y); + } else { + animateToClosestSnapTarget(); + } + } else { + expandPinnedStackToFullscreen(); + } + return true; + } + }; } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java new file mode 100644 index 000000000000..80af5a6c0268 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.pip.phone; + +import android.app.IActivityManager; +import android.graphics.PointF; +import android.view.IPinnedStackController; +import android.view.IPinnedStackListener; +import android.view.IWindowManager; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.ViewConfiguration; + +/** + * This keeps track of the touch state throughout the current touch gesture. + */ +public class PipTouchState { + + private ViewConfiguration mViewConfig; + + private VelocityTracker mVelocityTracker; + private final PointF mDownTouch = new PointF(); + private final PointF mDownDelta = new PointF(); + private final PointF mLastTouch = new PointF(); + private final PointF mLastDelta = new PointF(); + private final PointF mVelocity = new PointF(); + private boolean mIsDragging = false; + private boolean mStartedDragging = false; + private int mActivePointerId; + + public PipTouchState(ViewConfiguration viewConfig) { + mViewConfig = viewConfig; + } + + /** + * Processess a given touch event and updates the state. + */ + public void onTouchEvent(MotionEvent ev) { + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: { + // Initialize the velocity tracker + initOrResetVelocityTracker(); + mActivePointerId = ev.getPointerId(0); + mLastTouch.set(ev.getX(), ev.getY()); + mDownTouch.set(mLastTouch); + mIsDragging = false; + mStartedDragging = false; + break; + } + case MotionEvent.ACTION_MOVE: { + // Update the velocity tracker + mVelocityTracker.addMovement(ev); + int pointerIndex = ev.findPointerIndex(mActivePointerId); + float x = ev.getX(pointerIndex); + float y = ev.getY(pointerIndex); + mLastDelta.set(x - mLastTouch.x, y - mLastTouch.y); + mDownDelta.set(x - mDownTouch.x, y - mDownTouch.y); + + boolean hasMovedBeyondTap = mDownDelta.length() > mViewConfig.getScaledTouchSlop(); + if (!mIsDragging) { + if (hasMovedBeyondTap) { + mIsDragging = true; + mStartedDragging = true; + } + } else { + mStartedDragging = false; + } + mLastTouch.set(x, y); + break; + } + case MotionEvent.ACTION_POINTER_UP: { + // Update the velocity tracker + mVelocityTracker.addMovement(ev); + + int pointerIndex = ev.getActionIndex(); + int pointerId = ev.getPointerId(pointerIndex); + if (pointerId == mActivePointerId) { + // Select a new active pointer id and reset the movement state + final int newPointerIndex = (pointerIndex == 0) ? 1 : 0; + mActivePointerId = ev.getPointerId(newPointerIndex); + mLastTouch.set(ev.getX(newPointerIndex), ev.getY(newPointerIndex)); + } + break; + } + case MotionEvent.ACTION_UP: { + // Update the velocity tracker + mVelocityTracker.addMovement(ev); + mVelocityTracker.computeCurrentVelocity(1000, + mViewConfig.getScaledMaximumFlingVelocity()); + mVelocity.set(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity()); + + int pointerIndex = ev.findPointerIndex(mActivePointerId); + mLastTouch.set(ev.getX(pointerIndex), ev.getY(pointerIndex)); + + // Fall through to clean up + } + case MotionEvent.ACTION_CANCEL: { + recycleVelocityTracker(); + break; + } + } + } + + /** + * @return the velocity of the active touch pointer at the point it is lifted off the screen. + */ + public PointF getVelocity() { + return mVelocity; + } + + /** + * @return the last touch position of the active pointer. + */ + public PointF getLastTouchPosition() { + return mLastTouch; + } + + /** + * @return the movement delta between the last handled touch event and the previous touch + * position. + */ + public PointF getLastTouchDelta() { + return mLastDelta; + } + + /** + * @return the movement delta between the last handled touch event and the down touch + * position. + */ + public PointF getDownTouchDelta() { + return mDownDelta; + } + + /** + * @return whether the user has started dragging. + */ + public boolean isDragging() { + return mIsDragging; + } + + /** + * @return whether the user has started dragging just in the last handled touch event. + */ + public boolean startedDragging() { + return mStartedDragging; + } + + private void initOrResetVelocityTracker() { + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } else { + mVelocityTracker.clear(); + } + } + + private void recycleVelocityTracker() { + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + } +} diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java index effb1b284cc2..c711b39aabaf 100644 --- a/services/core/java/com/android/server/wm/PinnedStackController.java +++ b/services/core/java/com/android/server/wm/PinnedStackController.java @@ -69,6 +69,7 @@ class PinnedStackController { // States that affect how the PIP can be manipulated private boolean mInInteractiveMode; + private boolean mIsMinimized; private boolean mIsImeShowing; private int mImeHeight; private ValueAnimator mBoundsAnimator = null; @@ -103,6 +104,13 @@ class PinnedStackController { } @Override + public void setIsMinimized(final boolean isMinimized) { + mHandler.post(() -> { + mIsMinimized = isMinimized; + }); + } + + @Override public void setSnapToEdge(final boolean snapToEdge) { mHandler.post(() -> { mSnapAlgorithm.setSnapToEdge(snapToEdge); @@ -335,5 +343,6 @@ class PinnedStackController { pw.println(); pw.println(prefix + " mIsImeShowing=" + mIsImeShowing); pw.println(prefix + " mInInteractiveMode=" + mInInteractiveMode); + pw.println(prefix + " mIsMinimized=" + mIsMinimized); } } -- 2.11.0