Loading packages/SystemUI/res/values/strings.xml +20 −0 Original line number Diff line number Diff line Loading @@ -1667,4 +1667,24 @@ not appear on production builds ever. --> <string name="plugins" translatable="false">Plugins</string> <!-- PIP section of the tuner. Non-translatable since it should not appear on production builds ever. --> <string name="picture_in_picture" translatable="false">Picture-in-Picture</string> <!-- PIP swipe to dismiss title. Non-translatable since it should not appear on production builds ever. --> <string name="pip_swipe_to_dismiss_title" translatable="false">Swipe to dismiss</string> <!-- PIP swipe to dismiss description. Non-translatable since it should not appear on production builds ever. --> <string name="pip_swipe_to_dismiss_summary" translatable="false">Swipe left or right off screen to close the PIP</string> <!-- PIP drag to dismiss title. Non-translatable since it should not appear on production builds ever. --> <string name="pip_drag_to_dismiss_title" translatable="false">Drag to dismiss</string> <!-- PIP drag to dismiss description. Non-translatable since it should not appear on production builds ever. --> <string name="pip_drag_to_dismiss_summary" translatable="false">Drag to the dismiss target at the bottom of the screen to close the PIP</string> </resources> packages/SystemUI/res/xml/tuner_prefs.xml +18 −0 Original line number Diff line number Diff line Loading @@ -121,6 +121,24 @@ </PreferenceScreen> <PreferenceScreen android:key="picture_in_picture" android:title="@string/picture_in_picture"> <com.android.systemui.tuner.TunerSwitch android:key="pip_swipe_to_dismiss" android:title="@string/pip_swipe_to_dismiss_title" android:summary="@string/pip_swipe_to_dismiss_summary" sysui:defValue="true" /> <com.android.systemui.tuner.TunerSwitch android:key="pip_drag_to_dismiss" android:title="@string/pip_drag_to_dismiss_title" android:summary="@string/pip_drag_to_dismiss_summary" sysui:defValue="true" /> </PreferenceScreen> <!-- <Preference android:key="nav_bar" Loading packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +115 −32 Original line number Diff line number Diff line Loading @@ -46,15 +46,19 @@ import android.view.animation.Interpolator; import com.android.internal.os.BackgroundThread; import com.android.internal.policy.PipSnapAlgorithm; import com.android.systemui.statusbar.FlingAnimationUtils; import com.android.systemui.tuner.TunerService; /** * Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding * the PIP. */ public class PipTouchHandler { public class PipTouchHandler implements TunerService.Tunable { private static final String TAG = "PipTouchHandler"; private static final boolean DEBUG_ALLOW_OUT_OF_BOUNDS_STACK = false; private static final String TUNER_KEY_SWIPE_TO_DISMISS = "pip_swipe_to_dismiss"; private static final String TUNER_KEY_DRAG_TO_DISMISS = "pip_drag_to_dismiss"; private static final int SNAP_STACK_DURATION = 225; private static final int DISMISS_STACK_DURATION = 375; private static final int EXPAND_STACK_DURATION = 225; Loading @@ -65,9 +69,12 @@ public class PipTouchHandler { private final InputChannel mInputChannel = new InputChannel(); private final PipInputEventReceiver mInputEventReceiver; private final PipDismissViewController mDismissViewController; private PipDismissViewController mDismissViewController; private PipSnapAlgorithm mSnapAlgorithm; private boolean mEnableSwipeToDismiss = true; private boolean mEnableDragToDismiss = true; private final Rect mPinnedStackBounds = new Rect(); private final Rect mBoundedPinnedStackBounds = new Rect(); private ValueAnimator mPinnedStackBoundsAnimator = null; Loading @@ -75,6 +82,7 @@ public class PipTouchHandler { private final PointF mDownTouch = new PointF(); private final PointF mLastTouch = new PointF(); private boolean mIsDragging; private boolean mIsSwipingToDismiss; private int mActivePointerId; private final FlingAnimationUtils mFlingAnimationUtils; Loading Loading @@ -117,8 +125,26 @@ public class PipTouchHandler { mActivityManager = activityManager; mViewConfig = ViewConfiguration.get(context); mInputEventReceiver = new PipInputEventReceiver(mInputChannel, Looper.myLooper()); if (mEnableDragToDismiss) { mDismissViewController = new PipDismissViewController(context); } mFlingAnimationUtils = new FlingAnimationUtils(context, 2f); // Register any tuner settings changes TunerService.get(context).addTunable(this, TUNER_KEY_SWIPE_TO_DISMISS, TUNER_KEY_DRAG_TO_DISMISS); } @Override public void onTuningChanged(String key, String newValue) { switch (key) { case TUNER_KEY_SWIPE_TO_DISMISS: mEnableSwipeToDismiss = (newValue != null) && Integer.parseInt(newValue) != 0; break; case TUNER_KEY_DRAG_TO_DISMISS: mEnableDragToDismiss = (newValue != null) && Integer.parseInt(newValue) != 0; break; } } public void onConfigurationChanged() { Loading @@ -140,9 +166,11 @@ public class PipTouchHandler { mLastTouch.set(ev.getX(), ev.getY()); mDownTouch.set(mLastTouch); mIsDragging = false; // TODO: Consider setting a timer such at after X time, we show the dismiss target // if the user hasn't already dragged some distance 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(); } break; } case MotionEvent.ACTION_MOVE: { Loading @@ -150,27 +178,40 @@ public class PipTouchHandler { mVelocityTracker.addMovement(ev); int activePointerIndex = ev.findPointerIndex(mActivePointerId); float x = ev.getX(activePointerIndex); float y = ev.getY(activePointerIndex); float left = mPinnedStackBounds.left + (x - mLastTouch.x); float top = mPinnedStackBounds.top + (y - mLastTouch.y); if (!mIsDragging) { // Check if the pointer has moved far enough float movement = PointF.length(mDownTouch.x - ev.getX(activePointerIndex), mDownTouch.y - ev.getY(activePointerIndex)); float movement = PointF.length(mDownTouch.x - x, mDownTouch.y - y); if (movement > mViewConfig.getScaledTouchSlop()) { mIsDragging = true; 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(); } } } if (mIsDragging) { if (mIsSwipingToDismiss) { // Ignore the vertical movement top = mPinnedStackBounds.top; movePinnedStack(left, top); } else if (mIsDragging) { // Move the pinned stack float dx = ev.getX(activePointerIndex) - mLastTouch.x; float dy = ev.getY(activePointerIndex) - mLastTouch.y; float left = Math.max(mBoundedPinnedStackBounds.left, Math.min( mBoundedPinnedStackBounds.right, mPinnedStackBounds.left + dx)); float top = Math.max(mBoundedPinnedStackBounds.top, Math.min( mBoundedPinnedStackBounds.bottom, mPinnedStackBounds.top + dy)); if (DEBUG_ALLOW_OUT_OF_BOUNDS_STACK) { left = mPinnedStackBounds.left + dx; top = mPinnedStackBounds.top + dy; 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)); } movePinnedStack(left, top); } Loading @@ -194,21 +235,29 @@ public class PipTouchHandler { case MotionEvent.ACTION_UP: { // Update the velocity tracker mVelocityTracker.addMovement(ev); if (mIsDragging) { mVelocityTracker.computeCurrentVelocity(1000, ViewConfiguration.get(mContext).getScaledMaximumFlingVelocity()); float velocityX = mVelocityTracker.getXVelocity(); float velocityY = mVelocityTracker.getYVelocity(); float velocity = PointF.length(velocityX, velocityY); 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 = mDismissViewController.getDismissBounds(); if (dismissBounds.contains(x, y)) { Rect dismissBounds = mEnableDragToDismiss ? mDismissViewController.getDismissBounds() : null; if (dismissBounds != null && dismissBounds.contains(x, y)) { animateDismissPinnedStack(dismissBounds); } else { animateToClosestSnapTarget(); Loading @@ -217,12 +266,15 @@ public class PipTouchHandler { } else { expandPinnedStackToFullscreen(); } if (mEnableDragToDismiss) { mDismissViewController.destroyDismissTarget(); } // Fall through to clean up } case MotionEvent.ACTION_CANCEL: { mIsDragging = false; mIsSwipingToDismiss = false; recycleVelocityTracker(); break; } Loading @@ -245,7 +297,7 @@ public class PipTouchHandler { } /** * Creates an animation that continues the fling to a snap target. * Flings the PIP to the closest snap target. */ private void flingToSnapTarget(float velocity, float velocityX, float velocityY) { Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(mBoundedPinnedStackBounds, Loading @@ -261,7 +313,7 @@ public class PipTouchHandler { } /** * Animates the pinned stack to the closest snap target. * Animates the PIP to the closest snap target. */ private void animateToClosestSnapTarget() { Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(mBoundedPinnedStackBounds, Loading @@ -274,7 +326,38 @@ public class PipTouchHandler { } /** * Animates the dismissal of the pinned stack into the given bounds. * Flings the PIP to dismiss it offscreen. */ private void flingToDismiss(float velocityX) { float offsetX = velocityX > 0 ? mBoundedPinnedStackBounds.right + 2 * mPinnedStackBounds.width() : mBoundedPinnedStackBounds.left - 2 * mPinnedStackBounds.width(); Rect toBounds = new Rect(mPinnedStackBounds); toBounds.offsetTo((int) offsetX, toBounds.top); if (!mPinnedStackBounds.equals(toBounds)) { mPinnedStackBoundsAnimator = createResizePinnedStackAnimation( toBounds, 0, FAST_OUT_SLOW_IN); mFlingAnimationUtils.apply(mPinnedStackBoundsAnimator, 0, distanceBetweenRectOffsets(mPinnedStackBounds, toBounds), velocityX); 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); } }); } }); mPinnedStackBoundsAnimator.start(); } } /** * Animates the dismissal of the PIP over the dismiss target bounds. */ private void animateDismissPinnedStack(Rect dismissBounds) { Rect toBounds = new Rect(dismissBounds.centerX(), Loading Loading
packages/SystemUI/res/values/strings.xml +20 −0 Original line number Diff line number Diff line Loading @@ -1667,4 +1667,24 @@ not appear on production builds ever. --> <string name="plugins" translatable="false">Plugins</string> <!-- PIP section of the tuner. Non-translatable since it should not appear on production builds ever. --> <string name="picture_in_picture" translatable="false">Picture-in-Picture</string> <!-- PIP swipe to dismiss title. Non-translatable since it should not appear on production builds ever. --> <string name="pip_swipe_to_dismiss_title" translatable="false">Swipe to dismiss</string> <!-- PIP swipe to dismiss description. Non-translatable since it should not appear on production builds ever. --> <string name="pip_swipe_to_dismiss_summary" translatable="false">Swipe left or right off screen to close the PIP</string> <!-- PIP drag to dismiss title. Non-translatable since it should not appear on production builds ever. --> <string name="pip_drag_to_dismiss_title" translatable="false">Drag to dismiss</string> <!-- PIP drag to dismiss description. Non-translatable since it should not appear on production builds ever. --> <string name="pip_drag_to_dismiss_summary" translatable="false">Drag to the dismiss target at the bottom of the screen to close the PIP</string> </resources>
packages/SystemUI/res/xml/tuner_prefs.xml +18 −0 Original line number Diff line number Diff line Loading @@ -121,6 +121,24 @@ </PreferenceScreen> <PreferenceScreen android:key="picture_in_picture" android:title="@string/picture_in_picture"> <com.android.systemui.tuner.TunerSwitch android:key="pip_swipe_to_dismiss" android:title="@string/pip_swipe_to_dismiss_title" android:summary="@string/pip_swipe_to_dismiss_summary" sysui:defValue="true" /> <com.android.systemui.tuner.TunerSwitch android:key="pip_drag_to_dismiss" android:title="@string/pip_drag_to_dismiss_title" android:summary="@string/pip_drag_to_dismiss_summary" sysui:defValue="true" /> </PreferenceScreen> <!-- <Preference android:key="nav_bar" Loading
packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +115 −32 Original line number Diff line number Diff line Loading @@ -46,15 +46,19 @@ import android.view.animation.Interpolator; import com.android.internal.os.BackgroundThread; import com.android.internal.policy.PipSnapAlgorithm; import com.android.systemui.statusbar.FlingAnimationUtils; import com.android.systemui.tuner.TunerService; /** * Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding * the PIP. */ public class PipTouchHandler { public class PipTouchHandler implements TunerService.Tunable { private static final String TAG = "PipTouchHandler"; private static final boolean DEBUG_ALLOW_OUT_OF_BOUNDS_STACK = false; private static final String TUNER_KEY_SWIPE_TO_DISMISS = "pip_swipe_to_dismiss"; private static final String TUNER_KEY_DRAG_TO_DISMISS = "pip_drag_to_dismiss"; private static final int SNAP_STACK_DURATION = 225; private static final int DISMISS_STACK_DURATION = 375; private static final int EXPAND_STACK_DURATION = 225; Loading @@ -65,9 +69,12 @@ public class PipTouchHandler { private final InputChannel mInputChannel = new InputChannel(); private final PipInputEventReceiver mInputEventReceiver; private final PipDismissViewController mDismissViewController; private PipDismissViewController mDismissViewController; private PipSnapAlgorithm mSnapAlgorithm; private boolean mEnableSwipeToDismiss = true; private boolean mEnableDragToDismiss = true; private final Rect mPinnedStackBounds = new Rect(); private final Rect mBoundedPinnedStackBounds = new Rect(); private ValueAnimator mPinnedStackBoundsAnimator = null; Loading @@ -75,6 +82,7 @@ public class PipTouchHandler { private final PointF mDownTouch = new PointF(); private final PointF mLastTouch = new PointF(); private boolean mIsDragging; private boolean mIsSwipingToDismiss; private int mActivePointerId; private final FlingAnimationUtils mFlingAnimationUtils; Loading Loading @@ -117,8 +125,26 @@ public class PipTouchHandler { mActivityManager = activityManager; mViewConfig = ViewConfiguration.get(context); mInputEventReceiver = new PipInputEventReceiver(mInputChannel, Looper.myLooper()); if (mEnableDragToDismiss) { mDismissViewController = new PipDismissViewController(context); } mFlingAnimationUtils = new FlingAnimationUtils(context, 2f); // Register any tuner settings changes TunerService.get(context).addTunable(this, TUNER_KEY_SWIPE_TO_DISMISS, TUNER_KEY_DRAG_TO_DISMISS); } @Override public void onTuningChanged(String key, String newValue) { switch (key) { case TUNER_KEY_SWIPE_TO_DISMISS: mEnableSwipeToDismiss = (newValue != null) && Integer.parseInt(newValue) != 0; break; case TUNER_KEY_DRAG_TO_DISMISS: mEnableDragToDismiss = (newValue != null) && Integer.parseInt(newValue) != 0; break; } } public void onConfigurationChanged() { Loading @@ -140,9 +166,11 @@ public class PipTouchHandler { mLastTouch.set(ev.getX(), ev.getY()); mDownTouch.set(mLastTouch); mIsDragging = false; // TODO: Consider setting a timer such at after X time, we show the dismiss target // if the user hasn't already dragged some distance 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(); } break; } case MotionEvent.ACTION_MOVE: { Loading @@ -150,27 +178,40 @@ public class PipTouchHandler { mVelocityTracker.addMovement(ev); int activePointerIndex = ev.findPointerIndex(mActivePointerId); float x = ev.getX(activePointerIndex); float y = ev.getY(activePointerIndex); float left = mPinnedStackBounds.left + (x - mLastTouch.x); float top = mPinnedStackBounds.top + (y - mLastTouch.y); if (!mIsDragging) { // Check if the pointer has moved far enough float movement = PointF.length(mDownTouch.x - ev.getX(activePointerIndex), mDownTouch.y - ev.getY(activePointerIndex)); float movement = PointF.length(mDownTouch.x - x, mDownTouch.y - y); if (movement > mViewConfig.getScaledTouchSlop()) { mIsDragging = true; 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(); } } } if (mIsDragging) { if (mIsSwipingToDismiss) { // Ignore the vertical movement top = mPinnedStackBounds.top; movePinnedStack(left, top); } else if (mIsDragging) { // Move the pinned stack float dx = ev.getX(activePointerIndex) - mLastTouch.x; float dy = ev.getY(activePointerIndex) - mLastTouch.y; float left = Math.max(mBoundedPinnedStackBounds.left, Math.min( mBoundedPinnedStackBounds.right, mPinnedStackBounds.left + dx)); float top = Math.max(mBoundedPinnedStackBounds.top, Math.min( mBoundedPinnedStackBounds.bottom, mPinnedStackBounds.top + dy)); if (DEBUG_ALLOW_OUT_OF_BOUNDS_STACK) { left = mPinnedStackBounds.left + dx; top = mPinnedStackBounds.top + dy; 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)); } movePinnedStack(left, top); } Loading @@ -194,21 +235,29 @@ public class PipTouchHandler { case MotionEvent.ACTION_UP: { // Update the velocity tracker mVelocityTracker.addMovement(ev); if (mIsDragging) { mVelocityTracker.computeCurrentVelocity(1000, ViewConfiguration.get(mContext).getScaledMaximumFlingVelocity()); float velocityX = mVelocityTracker.getXVelocity(); float velocityY = mVelocityTracker.getYVelocity(); float velocity = PointF.length(velocityX, velocityY); 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 = mDismissViewController.getDismissBounds(); if (dismissBounds.contains(x, y)) { Rect dismissBounds = mEnableDragToDismiss ? mDismissViewController.getDismissBounds() : null; if (dismissBounds != null && dismissBounds.contains(x, y)) { animateDismissPinnedStack(dismissBounds); } else { animateToClosestSnapTarget(); Loading @@ -217,12 +266,15 @@ public class PipTouchHandler { } else { expandPinnedStackToFullscreen(); } if (mEnableDragToDismiss) { mDismissViewController.destroyDismissTarget(); } // Fall through to clean up } case MotionEvent.ACTION_CANCEL: { mIsDragging = false; mIsSwipingToDismiss = false; recycleVelocityTracker(); break; } Loading @@ -245,7 +297,7 @@ public class PipTouchHandler { } /** * Creates an animation that continues the fling to a snap target. * Flings the PIP to the closest snap target. */ private void flingToSnapTarget(float velocity, float velocityX, float velocityY) { Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(mBoundedPinnedStackBounds, Loading @@ -261,7 +313,7 @@ public class PipTouchHandler { } /** * Animates the pinned stack to the closest snap target. * Animates the PIP to the closest snap target. */ private void animateToClosestSnapTarget() { Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(mBoundedPinnedStackBounds, Loading @@ -274,7 +326,38 @@ public class PipTouchHandler { } /** * Animates the dismissal of the pinned stack into the given bounds. * Flings the PIP to dismiss it offscreen. */ private void flingToDismiss(float velocityX) { float offsetX = velocityX > 0 ? mBoundedPinnedStackBounds.right + 2 * mPinnedStackBounds.width() : mBoundedPinnedStackBounds.left - 2 * mPinnedStackBounds.width(); Rect toBounds = new Rect(mPinnedStackBounds); toBounds.offsetTo((int) offsetX, toBounds.top); if (!mPinnedStackBounds.equals(toBounds)) { mPinnedStackBoundsAnimator = createResizePinnedStackAnimation( toBounds, 0, FAST_OUT_SLOW_IN); mFlingAnimationUtils.apply(mPinnedStackBoundsAnimator, 0, distanceBetweenRectOffsets(mPinnedStackBounds, toBounds), velocityX); 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); } }); } }); mPinnedStackBoundsAnimator.start(); } } /** * Animates the dismissal of the PIP over the dismiss target bounds. */ private void animateDismissPinnedStack(Rect dismissBounds) { Rect toBounds = new Rect(dismissBounds.centerX(), Loading