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
This commit is contained in:
@@ -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() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user