Loading core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +5 −0 Original line number Diff line number Diff line Loading @@ -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() { } } packages/SystemUI/res/values/dimens.xml +3 −0 Original line number Diff line number Diff line Loading @@ -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> Loading packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java +8 −0 Original line number Diff line number Diff line Loading @@ -344,6 +344,14 @@ public class PipBoundsHandler { && Float.compare(aspectRatio, mMaxAspectRatio) <= 0; } /** * 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. Loading packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java 0 → 100644 +235 −0 Original line number Diff line number Diff line /* * 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); } } } packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +8 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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())); Loading Loading @@ -227,6 +230,7 @@ public class PipTouchHandler { public void onActivityPinned() { cleanUp(); mShowPipMenuOnAnimationEnd = true; mPipResizeGestureHandler.onActivityPinned(); } public void onActivityUnpinned(ComponentName topPipActivity) { Loading @@ -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(), Loading Loading @@ -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); Loading Loading
core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +5 −0 Original line number Diff line number Diff line Loading @@ -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() { } }
packages/SystemUI/res/values/dimens.xml +3 −0 Original line number Diff line number Diff line Loading @@ -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> Loading
packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java +8 −0 Original line number Diff line number Diff line Loading @@ -344,6 +344,14 @@ public class PipBoundsHandler { && Float.compare(aspectRatio, mMaxAspectRatio) <= 0; } /** * 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. Loading
packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java 0 → 100644 +235 −0 Original line number Diff line number Diff line /* * 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); } } }
packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +8 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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())); Loading Loading @@ -227,6 +230,7 @@ public class PipTouchHandler { public void onActivityPinned() { cleanUp(); mShowPipMenuOnAnimationEnd = true; mPipResizeGestureHandler.onActivityPinned(); } public void onActivityUnpinned(ComponentName topPipActivity) { Loading @@ -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(), Loading Loading @@ -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); Loading