Loading libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java 0 → 100644 +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); } } libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +10 −1 Original line number Diff line number Diff line Loading @@ -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()); } Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java 0 → 100644 +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); } } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java 0 → 100644 +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); } }
libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +10 −1 Original line number Diff line number Diff line Loading @@ -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()); } Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java 0 → 100644 +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); } }