Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a0000893 authored by Ben Lin's avatar Ben Lin Committed by Android (Google) Code Review
Browse files

Merge "Implement stretch-free resizing for PIP (input side)."

parents eb1ae5b6 04c83f6d
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -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() {
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -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>

+8 −0
Original line number Diff line number Diff line
@@ -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.
+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);
        }
    }
}
+8 −0
Original line number Diff line number Diff line
@@ -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);
Loading