Merge "Allow pip screen size to toggle on double tap" into tm-qpr-dev am: 491e471def
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19906482 Change-Id: Ic049d05cd79ec7c70497b7d3e8acc51aec1eb8a4 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -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()) {
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -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