diff options
| author | 2019-12-20 10:56:45 -0800 | |
|---|---|---|
| committer | 2020-02-10 15:20:38 -0800 | |
| commit | 04c83f6d87cfa900e24cd6efbbf2d93da8697ee3 (patch) | |
| tree | 0445f7ef37ec4a06e574f7cfc9b57b5e141b098b | |
| parent | 6c0f623a01ef69474f86819d5438116f375e4d14 (diff) | |
Implement stretch-free resizing for PIP (input side).
This is a feature of two components:
1) Input code that takes user input around the PIP window border and
starts resizing
2) The actual resizing of the PIP task/stack that currently lives in WM,
but will live in SystemUI once migration is done to using Task Organizer
The CL only takes care of #1, the input side living in SystemUI.
Bug: 111426176
Test: Manually drag the corner of PIP window
Change-Id: If4b5e6ea5c482f4fa6eb571b441cf9e6966fbf4b
6 files changed, 260 insertions, 0 deletions
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index 3322834c9ed1..086b9d83fed5 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -377,6 +377,11 @@ public final class SystemUiDeviceConfigFlags { public static final String NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN = "nav_bar_handle_show_over_lockscreen"; + /** + * (boolean) Whether to enable user-drag resizing for PIP. + */ + public static final String PIP_USER_RESIZE = "pip_user_resize"; + private SystemUiDeviceConfigFlags() { } } diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 0db7d67efd8d..f0e4e53dfc53 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -977,6 +977,9 @@ Equal to pip_action_size - pip_action_padding. --> <dimen name="pip_expand_container_edge_margin">30dp</dimen> + <!-- The touchable/draggable edge size for PIP resize. --> + <dimen name="pip_resize_edge_size">30dp</dimen> + <dimen name="default_gear_space">18dp</dimen> <dimen name="cell_overlay_padding">18dp</dimen> diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java index 6f03f18ef64b..41b31306a931 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java @@ -345,6 +345,14 @@ public class PipBoundsHandler { } /** + * Sets the current bound with the currently store aspect ratio. + * @param stackBounds + */ + public void transformBoundsToAspectRatio(Rect stackBounds) { + transformBoundsToAspectRatio(stackBounds, mAspectRatio, true); + } + + /** * Set the current bounds (or the default bounds if there are no current bounds) with the * specified aspect ratio. */ diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java new file mode 100644 index 000000000000..9fb623471bf7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2020 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 static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_USER_RESIZE; +import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM; +import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT; +import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE; +import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT; +import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Point; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.Region; +import android.hardware.input.InputManager; +import android.os.Looper; +import android.provider.DeviceConfig; +import android.util.DisplayMetrics; +import android.view.InputChannel; +import android.view.InputEvent; +import android.view.InputEventReceiver; +import android.view.InputMonitor; +import android.view.MotionEvent; + +import com.android.internal.policy.TaskResizingAlgorithm; +import com.android.systemui.R; +import com.android.systemui.pip.PipBoundsHandler; + +import java.util.concurrent.Executor; + +/** + * Helper on top of PipTouchHandler that handles inputs OUTSIDE of the PIP window, which is used to + * trigger dynamic resize. + */ +public class PipResizeGestureHandler { + + private static final String TAG = "PipResizeGestureHandler"; + + private final DisplayMetrics mDisplayMetrics = new DisplayMetrics(); + private final PipBoundsHandler mPipBoundsHandler; + private final PipTouchHandler mPipTouchHandler; + private final PipMotionHelper mMotionHelper; + private final int mDisplayId; + private final Executor mMainExecutor; + private final Region mTmpRegion = new Region(); + + private final PointF mDownPoint = new PointF(); + private final Point mMaxSize = new Point(); + private final Point mMinSize = new Point(); + private final Rect mTmpBounds = new Rect(); + private final int mDelta; + + private boolean mAllowGesture = false; + private boolean mIsAttached; + private boolean mIsEnabled; + private boolean mEnablePipResize; + + private InputMonitor mInputMonitor; + private InputEventReceiver mInputEventReceiver; + + private int mCtrlType; + + public PipResizeGestureHandler(Context context, PipBoundsHandler pipBoundsHandler, + PipTouchHandler pipTouchHandler, PipMotionHelper motionHelper) { + final Resources res = context.getResources(); + context.getDisplay().getMetrics(mDisplayMetrics); + mDisplayId = context.getDisplayId(); + mMainExecutor = context.getMainExecutor(); + mPipBoundsHandler = pipBoundsHandler; + mPipTouchHandler = pipTouchHandler; + mMotionHelper = motionHelper; + + context.getDisplay().getRealSize(mMaxSize); + mDelta = res.getDimensionPixelSize(R.dimen.pip_resize_edge_size); + + mEnablePipResize = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_SYSTEMUI, + PIP_USER_RESIZE, + /* defaultValue = */ false); + DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mMainExecutor, + new DeviceConfig.OnPropertiesChangedListener() { + @Override + public void onPropertiesChanged(DeviceConfig.Properties properties) { + if (properties.getKeyset().contains(PIP_USER_RESIZE)) { + mEnablePipResize = properties.getBoolean( + PIP_USER_RESIZE, /* defaultValue = */ false); + } + } + }); + } + + private void disposeInputChannel() { + if (mInputEventReceiver != null) { + mInputEventReceiver.dispose(); + mInputEventReceiver = null; + } + if (mInputMonitor != null) { + mInputMonitor.dispose(); + mInputMonitor = null; + } + } + + void onActivityPinned() { + mIsAttached = true; + updateIsEnabled(); + } + + void onActivityUnpinned() { + mIsAttached = false; + updateIsEnabled(); + } + + private void updateIsEnabled() { + boolean isEnabled = mIsAttached && mEnablePipResize; + if (isEnabled == mIsEnabled) { + return; + } + mIsEnabled = isEnabled; + disposeInputChannel(); + + if (mIsEnabled) { + // Register input event receiver + mInputMonitor = InputManager.getInstance().monitorGestureInput( + "pip-resize", mDisplayId); + mInputEventReceiver = new SysUiInputEventReceiver( + mInputMonitor.getInputChannel(), Looper.getMainLooper()); + } + } + + private void onInputEvent(InputEvent ev) { + if (ev instanceof MotionEvent) { + onMotionEvent((MotionEvent) ev); + } + } + + private boolean isWithinTouchRegion(int x, int y) { + final Rect currentPipBounds = mMotionHelper.getBounds(); + if (currentPipBounds == null) { + return false; + } + + mTmpBounds.set(currentPipBounds); + mTmpBounds.inset(-mDelta, -mDelta); + + mTmpRegion.set(mTmpBounds); + mTmpRegion.op(currentPipBounds, Region.Op.DIFFERENCE); + + if (mTmpRegion.contains(x, y)) { + if (x < currentPipBounds.left) { + mCtrlType |= CTRL_LEFT; + } + if (x > currentPipBounds.right) { + mCtrlType |= CTRL_RIGHT; + } + if (y < currentPipBounds.top) { + mCtrlType |= CTRL_TOP; + } + if (y > currentPipBounds.bottom) { + mCtrlType |= CTRL_BOTTOM; + } + return true; + } + return false; + } + + private void onMotionEvent(MotionEvent ev) { + int action = ev.getActionMasked(); + if (action == MotionEvent.ACTION_DOWN) { + mAllowGesture = isWithinTouchRegion((int) ev.getX(), (int) ev.getY()); + if (mAllowGesture) { + mDownPoint.set(ev.getX(), ev.getY()); + } + + } else if (mAllowGesture) { + final Rect currentPipBounds = mMotionHelper.getBounds(); + Rect newSize = TaskResizingAlgorithm.resizeDrag(ev.getX(), ev.getY(), mDownPoint.x, + mDownPoint.y, currentPipBounds, mCtrlType, mMinSize.x, mMinSize.y, mMaxSize, + true, true); + mPipBoundsHandler.transformBoundsToAspectRatio(newSize); + switch (action) { + case MotionEvent.ACTION_POINTER_DOWN: + // We do not support multi touch for resizing via drag + mAllowGesture = false; + break; + case MotionEvent.ACTION_MOVE: + // Capture inputs + mInputMonitor.pilferPointers(); + //TODO: Actually do resize here. + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + //TODO: Finish resize operation here. + mMotionHelper.synchronizePinnedStackBounds(); + mCtrlType = CTRL_NONE; + mAllowGesture = false; + break; + } + } + } + + void updateMaxSize(int maxX, int maxY) { + mMaxSize.set(maxX, maxY); + } + + void updateMiniSize(int minX, int minY) { + mMinSize.set(minX, minY); + } + + class SysUiInputEventReceiver extends InputEventReceiver { + SysUiInputEventReceiver(InputChannel channel, Looper looper) { + super(channel, looper); + } + + public void onInputEvent(InputEvent event) { + PipResizeGestureHandler.this.onInputEvent(event); + finishInputEvent(event, true); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java index 65cc666d5164..924edb6fe312 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -73,6 +73,7 @@ public class PipTouchHandler { private final ViewConfiguration mViewConfig; private final PipMenuListener mMenuListener = new PipMenuListener(); private final PipBoundsHandler mPipBoundsHandler; + private final PipResizeGestureHandler mPipResizeGestureHandler; private IPinnedStackController mPinnedStackController; private final PipMenuActivityController mMenuController; @@ -188,6 +189,8 @@ public class PipTouchHandler { mGesture = new DefaultPipTouchGesture(); mMotionHelper = new PipMotionHelper(mContext, mActivityManager, mActivityTaskManager, mMenuController, mSnapAlgorithm, mFlingAnimationUtils); + mPipResizeGestureHandler = + new PipResizeGestureHandler(context, pipBoundsHandler, this, mMotionHelper); mTouchState = new PipTouchState(mViewConfig, mHandler, () -> mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(), mMovementBounds, true /* allowMenuTimeout */, willResizeMenu())); @@ -227,6 +230,7 @@ public class PipTouchHandler { public void onActivityPinned() { cleanUp(); mShowPipMenuOnAnimationEnd = true; + mPipResizeGestureHandler.onActivityPinned(); } public void onActivityUnpinned(ComponentName topPipActivity) { @@ -234,11 +238,14 @@ public class PipTouchHandler { // Clean up state after the last PiP activity is removed cleanUp(); } + mPipResizeGestureHandler.onActivityUnpinned(); } public void onPinnedStackAnimationEnded() { // Always synchronize the motion helper bounds once PiP animations finish mMotionHelper.synchronizePinnedStackBounds(); + mPipResizeGestureHandler.updateMiniSize(mMotionHelper.getBounds().width(), + mMotionHelper.getBounds().height()); if (mShowPipMenuOnAnimationEnd) { mMenuController.showMenu(MENU_STATE_CLOSE, mMotionHelper.getBounds(), @@ -279,6 +286,7 @@ public class PipTouchHandler { Size expandedSize = mSnapAlgorithm.getSizeForAspectRatio(aspectRatio, mExpandedShortestEdgeSize, displaySize.x, displaySize.y); mExpandedBounds.set(0, 0, expandedSize.getWidth(), expandedSize.getHeight()); + mPipResizeGestureHandler.updateMaxSize(expandedSize.getWidth(), expandedSize.getHeight()); Rect expandedMovementBounds = new Rect(); mSnapAlgorithm.getMovementBounds(mExpandedBounds, insetBounds, expandedMovementBounds, bottomOffset); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java index 44a320419309..745843deeddb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java @@ -375,6 +375,7 @@ public class EdgeBackGestureHandler implements DisplayListener, mDownPoint.set(ev.getX(), ev.getY()); mThresholdCrossed = false; } + } else if (mAllowGesture) { if (!mThresholdCrossed) { if (action == MotionEvent.ACTION_POINTER_DOWN) { |