Merge "Allow pip screen size to toggle on double tap" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
491e471def
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -929,9 +929,18 @@ public class PipTouchHandler {
|
|||||||
if (mMenuController.isMenuVisible()) {
|
if (mMenuController.isMenuVisible()) {
|
||||||
mMenuController.hideMenu(ANIM_TYPE_NONE, false /* resize */);
|
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());
|
mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
|
||||||
animateToMaximizedState(null);
|
animateToMaximizedState(null);
|
||||||
|
} else if (nextSize == PipDoubleTapHelper.SIZE_SPEC_DEFAULT) {
|
||||||
|
mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
|
||||||
|
animateToNormalSize(null);
|
||||||
} else {
|
} else {
|
||||||
animateToUnexpandedState(getUserResizeBounds());
|
animateToUnexpandedState(getUserResizeBounds());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user