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

Commit f9a13d3e authored by Ikram Gabiyev's avatar Ikram Gabiyev Committed by Automerger Merge Worker
Browse files

Merge "Allow pip screen size to toggle on double tap" into tm-qpr-dev am: 491e471d

parents 452cd820 491e471d
Loading
Loading
Loading
Loading
+123 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.wm.shell.pip.phone;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.graphics.Rect;

import com.android.wm.shell.pip.PipBoundsState;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * Static utilities to get appropriate {@link PipDoubleTapHelper.PipSizeSpec} on a double tap.
 */
public class PipDoubleTapHelper {

    /**
     * Should not be instantiated as a stateless class.
     */
    private PipDoubleTapHelper() {}

    /**
     * A constant that represents a pip screen size.
     *
     * <p>CUSTOM - user resized screen size (by pinching in/out)</p>
     * <p>DEFAULT - normal screen size used as default when entering pip mode</p>
     * <p>MAX - maximum allowed screen size</p>
     */
    @IntDef(value = {
        SIZE_SPEC_CUSTOM,
        SIZE_SPEC_DEFAULT,
        SIZE_SPEC_MAX
    })
    @Retention(RetentionPolicy.SOURCE)
    @interface PipSizeSpec {}

    static final int SIZE_SPEC_CUSTOM = 2;
    static final int SIZE_SPEC_DEFAULT = 0;
    static final int SIZE_SPEC_MAX = 1;

    /**
     * Returns MAX or DEFAULT {@link PipSizeSpec} to toggle to/from.
     *
     * <p>Each double tap toggles back and forth between {@code PipSizeSpec.CUSTOM} and
     * either {@code PipSizeSpec.MAX} or {@code PipSizeSpec.DEFAULT}. The choice between
     * the latter two sizes is determined based on the current state of the pip screen.</p>
     *
     * @param mPipBoundsState current state of the pip screen
     */
    @PipSizeSpec
    private static int getMaxOrDefaultPipSizeSpec(@NonNull PipBoundsState mPipBoundsState) {
        // determine the average pip screen width
        int averageWidth = (mPipBoundsState.getMaxSize().x
                + mPipBoundsState.getMinSize().x) / 2;

        // If pip screen width is above average, DEFAULT is the size spec we need to
        // toggle to. Otherwise, we choose MAX.
        return (mPipBoundsState.getBounds().width() > averageWidth)
                ? SIZE_SPEC_DEFAULT
                : SIZE_SPEC_MAX;
    }

    /**
     * Determines the {@link PipSizeSpec} to toggle to on double tap.
     *
     * @param mPipBoundsState current state of the pip screen
     * @param userResizeBounds latest user resized bounds (by pinching in/out)
     * @return pip screen size to switch to
     */
    @PipSizeSpec
    static int nextSizeSpec(@NonNull PipBoundsState mPipBoundsState,
            @NonNull Rect userResizeBounds) {
        // is pip screen at its maximum
        boolean isScreenMax = mPipBoundsState.getBounds().width()
                == mPipBoundsState.getMaxSize().x;

        // is pip screen at its normal default size
        boolean isScreenDefault = (mPipBoundsState.getBounds().width()
                == mPipBoundsState.getNormalBounds().width())
                && (mPipBoundsState.getBounds().height()
                == mPipBoundsState.getNormalBounds().height());

        // edge case 1
        // if user hasn't resized screen yet, i.e. CUSTOM size does not exist yet
        // or if user has resized exactly to DEFAULT, then we just want to maximize
        if (isScreenDefault
                && userResizeBounds.width() == mPipBoundsState.getNormalBounds().width()) {
            return SIZE_SPEC_MAX;
        }

        // edge case 2
        // if user has maximized, then we want to toggle to DEFAULT
        if (isScreenMax
                && userResizeBounds.width() == mPipBoundsState.getMaxSize().x) {
            return SIZE_SPEC_DEFAULT;
        }

        // otherwise in general we want to toggle back to user's CUSTOM size
        if (isScreenDefault || isScreenMax) {
            return SIZE_SPEC_CUSTOM;
        }

        // if we are currently in user resized CUSTOM size state
        // then we toggle either to MAX or DEFAULT depending on the current pip screen state
        return getMaxOrDefaultPipSizeSpec(mPipBoundsState);
    }
}
+10 −1
Original line number Diff line number Diff line
@@ -929,9 +929,18 @@ public class PipTouchHandler {
                    if (mMenuController.isMenuVisible()) {
                        mMenuController.hideMenu(ANIM_TYPE_NONE, false /* resize */);
                    }
                    if (toExpand) {

                    // the size to toggle to after a double tap
                    int nextSize = PipDoubleTapHelper
                            .nextSizeSpec(mPipBoundsState, getUserResizeBounds());

                    // actually toggle to the size chosen
                    if (nextSize == PipDoubleTapHelper.SIZE_SPEC_MAX) {
                        mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
                        animateToMaximizedState(null);
                    } else if (nextSize == PipDoubleTapHelper.SIZE_SPEC_DEFAULT) {
                        mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
                        animateToNormalSize(null);
                    } else {
                        animateToUnexpandedState(getUserResizeBounds());
                    }
+172 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.wm.shell.pip.phone;

import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.SIZE_SPEC_CUSTOM;
import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.SIZE_SPEC_DEFAULT;
import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.SIZE_SPEC_MAX;
import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.nextSizeSpec;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import android.graphics.Point;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;

import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.pip.PipBoundsState;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;

/**
 * Unit test against {@link PipDoubleTapHelper}.
 */
@RunWith(AndroidTestingRunner.class)
public class PipDoubleTapHelperTest extends ShellTestCase {
    // represents the current pip window state and has information on current
    // max, min, and normal sizes
    @Mock private PipBoundsState mBoundStateMock;
    // tied to boundsStateMock.getBounds() in setUp()
    @Mock private Rect mBoundsMock;

    // represents the most recent manually resized bounds
    // i.e. dimensions from the most recent pinch in/out
    @Mock private Rect mUserResizeBoundsMock;

    // actual dimensions of the pip screen bounds
    private static final int MAX_WIDTH = 100;
    private static final int DEFAULT_WIDTH = 40;
    private static final int MIN_WIDTH = 10;

    private static final int AVERAGE_WIDTH = (MAX_WIDTH + MIN_WIDTH) / 2;

    /**
     * Initializes mocks and assigns values for different pip screen bounds.
     */
    @Before
    public void setUp() {
        // define pip bounds
        when(mBoundStateMock.getMaxSize()).thenReturn(new Point(MAX_WIDTH, 20));
        when(mBoundStateMock.getMinSize()).thenReturn(new Point(MIN_WIDTH, 2));

        Rect rectMock = mock(Rect.class);
        when(rectMock.width()).thenReturn(DEFAULT_WIDTH);
        when(mBoundStateMock.getNormalBounds()).thenReturn(rectMock);

        when(mBoundsMock.width()).thenReturn(DEFAULT_WIDTH);
        when(mBoundStateMock.getBounds()).thenReturn(mBoundsMock);
    }

    /**
     * Tests {@link PipDoubleTapHelper#nextSizeSpec(PipBoundsState, Rect)}.
     *
     * <p>when the user resizes the screen to a larger than the average but not the maximum width,
     * then we toggle between {@code PipSizeSpec.CUSTOM} and {@code PipSizeSpec.DEFAULT}
     */
    @Test
    public void testNextScreenSize_resizedWiderThanAverage_returnDefaultThenCustom() {
        // make the user resize width in between MAX and average
        when(mUserResizeBoundsMock.width()).thenReturn((MAX_WIDTH + AVERAGE_WIDTH) / 2);
        // make current bounds same as resized bound since no double tap yet
        when(mBoundsMock.width()).thenReturn((MAX_WIDTH + AVERAGE_WIDTH) / 2);

        // then nextScreenSize() i.e. double tapping should
        // toggle to DEFAULT state
        Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock),
                SIZE_SPEC_DEFAULT);

        // once we toggle to DEFAULT our screen size gets updated
        // but not the user resize bounds
        when(mBoundsMock.width()).thenReturn(DEFAULT_WIDTH);

        // then nextScreenSize() i.e. double tapping should
        // toggle to CUSTOM state
        Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock),
                SIZE_SPEC_CUSTOM);
    }

    /**
     * Tests {@link PipDoubleTapHelper#nextSizeSpec(PipBoundsState, Rect)}.
     *
     * <p>when the user resizes the screen to a smaller than the average but not the default width,
     * then we toggle between {@code PipSizeSpec.CUSTOM} and {@code PipSizeSpec.MAX}
     */
    @Test
    public void testNextScreenSize_resizedNarrowerThanAverage_returnMaxThenCustom() {
        // make the user resize width in between MIN and average
        when(mUserResizeBoundsMock.width()).thenReturn((MIN_WIDTH + AVERAGE_WIDTH) / 2);
        // make current bounds same as resized bound since no double tap yet
        when(mBoundsMock.width()).thenReturn((MIN_WIDTH + AVERAGE_WIDTH) / 2);

        // then nextScreenSize() i.e. double tapping should
        // toggle to MAX state
        Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock),
                SIZE_SPEC_MAX);

        // once we toggle to MAX our screen size gets updated
        // but not the user resize bounds
        when(mBoundsMock.width()).thenReturn(MAX_WIDTH);

        // then nextScreenSize() i.e. double tapping should
        // toggle to CUSTOM state
        Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock),
                SIZE_SPEC_CUSTOM);
    }

    /**
     * Tests {@link PipDoubleTapHelper#nextSizeSpec(PipBoundsState, Rect)}.
     *
     * <p>when the user resizes the screen to exactly the maximum width
     * then we toggle to {@code PipSizeSpec.DEFAULT}
     */
    @Test
    public void testNextScreenSize_resizedToMax_returnDefault() {
        // the resized width is the same as MAX_WIDTH
        when(mUserResizeBoundsMock.width()).thenReturn(MAX_WIDTH);
        // the current bounds are also at MAX_WIDTH
        when(mBoundsMock.width()).thenReturn(MAX_WIDTH);

        // then nextScreenSize() i.e. double tapping should
        // toggle to DEFAULT state
        Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock),
                SIZE_SPEC_DEFAULT);
    }

    /**
     * Tests {@link PipDoubleTapHelper#nextSizeSpec(PipBoundsState, Rect)}.
     *
     * <p>when the user resizes the screen to exactly the default width
     * then we toggle to {@code PipSizeSpec.MAX}
     */
    @Test
    public void testNextScreenSize_resizedToDefault_returnMax() {
        // the resized width is the same as DEFAULT_WIDTH
        when(mUserResizeBoundsMock.width()).thenReturn(DEFAULT_WIDTH);
        // the current bounds are also at DEFAULT_WIDTH
        when(mBoundsMock.width()).thenReturn(DEFAULT_WIDTH);

        // then nextScreenSize() i.e. double tapping should
        // toggle to MAX state
        Assert.assertSame(nextSizeSpec(mBoundStateMock, mUserResizeBoundsMock),
                SIZE_SPEC_MAX);
    }
}