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

This commit is contained in:
Ikram Gabiyev
2022-09-13 23:17:10 +00:00
committed by Android (Google) Code Review
3 changed files with 305 additions and 1 deletions

View File

@@ -0,0 +1,123 @@
/*
* 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);
}
}

View File

@@ -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());
}

View File

@@ -0,0 +1,172 @@
/*
* 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);
}
}