Merge "Fix widget drop animation in 4x5 grid" into tm-dev
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 6646e1b..9f35507 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -1413,6 +1413,7 @@
.setStartBounds(startRect)
.setDestinationBounds(destinationBounds)
.setCornerRadius(mRecentsView.getPipCornerRadius())
+ .setShadowRadius(mRecentsView.getPipShadowRadius())
.setAttachedView(mRecentsView);
// We would assume home and app window always in the same rotation While homeRotation
// is not ROTATION_0 (which implies the rotation is turned on in launcher settings).
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index ee35adc..70fde1d 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -101,6 +101,7 @@
* @param fromRotation From rotation if different from final rotation, ROTATION_0 otherwise
* @param destinationBoundsTransformed Destination bounds in window space
* @param cornerRadius Corner radius in pixel value for PiP window
+ * @param shadowRadius Shadow radius in pixel value for PiP window
* @param view Attached view for logging purpose
*/
private SwipePipToHomeAnimator(@NonNull Context context,
@@ -115,6 +116,7 @@
@RecentsOrientedState.SurfaceRotation int fromRotation,
@NonNull Rect destinationBoundsTransformed,
int cornerRadius,
+ int shadowRadius,
@NonNull View view) {
super(startBounds, new RectF(destinationBoundsTransformed), context, null);
mTaskId = taskId;
@@ -126,7 +128,7 @@
mDestinationBounds.set(destinationBounds);
mFromRotation = fromRotation;
mDestinationBoundsTransformed.set(destinationBoundsTransformed);
- mSurfaceTransactionHelper = new PipSurfaceTransactionHelper(cornerRadius);
+ mSurfaceTransactionHelper = new PipSurfaceTransactionHelper(cornerRadius, shadowRadius);
if (sourceRectHint != null && (sourceRectHint.width() < destinationBounds.width()
|| sourceRectHint.height() < destinationBounds.height())) {
@@ -324,6 +326,7 @@
private RectF mStartBounds;
private Rect mDestinationBounds;
private int mCornerRadius;
+ private int mShadowRadius;
private View mAttachedView;
private @RecentsOrientedState.SurfaceRotation int mFromRotation = Surface.ROTATION_0;
private final Rect mDestinationBoundsTransformed = new Rect();
@@ -378,6 +381,11 @@
return this;
}
+ public Builder setShadowRadius(int shadowRadius) {
+ mShadowRadius = shadowRadius;
+ return this;
+ }
+
public Builder setAttachedView(View attachedView) {
mAttachedView = attachedView;
return this;
@@ -422,7 +430,7 @@
mSourceRectHint, mAppBounds,
mHomeToWindowPositionMap, mStartBounds, mDestinationBounds,
mFromRotation, mDestinationBoundsTransformed,
- mCornerRadius, mAttachedView);
+ mCornerRadius, mShadowRadius, mAttachedView);
}
}
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index b9615ab..955fffc 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -237,7 +237,7 @@
}
getPagedOrientationHandler().measureGroupedTaskViewThumbnailBounds(mSnapshotView,
mSnapshotView2, widthSize, heightSize, mSplitBoundsConfig,
- mActivity.getDeviceProfile());
+ mActivity.getDeviceProfile(), getLayoutDirection() == LAYOUT_DIRECTION_RTL);
updateIconPlacement();
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 22491bc..c0d8d1d 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -539,6 +539,7 @@
private final PinnedStackAnimationListener mIPipAnimationListener =
new PinnedStackAnimationListener();
private int mPipCornerRadius;
+ private int mPipShadowRadius;
// Used to keep track of the last requested task list id, so that we do not request to load the
// tasks again if we have already requested it and the task list has not changed
@@ -5004,6 +5005,14 @@
return mPipCornerRadius;
}
+ /**
+ * @return Shadow radius in pixel value for PiP window, which is updated via
+ * {@link #mIPipAnimationListener}
+ */
+ public int getPipShadowRadius() {
+ return mPipShadowRadius;
+ }
+
@Override
public boolean scrollLeft() {
if (!showAsGrid()) {
@@ -5099,9 +5108,10 @@
}
@Override
- public void onPipCornerRadiusChanged(int cornerRadius) {
+ public void onPipResourceDimensionsChanged(int cornerRadius, int shadowRadius) {
if (mRecentsView != null) {
mRecentsView.mPipCornerRadius = cornerRadius;
+ mRecentsView.mPipShadowRadius = shadowRadius;
}
}
diff --git a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
index 6b3f699..9e5d958 100644
--- a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
+++ b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
@@ -17,8 +17,6 @@
package com.android.quickstep;
-import static android.view.Display.DEFAULT_DISPLAY;
-
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static com.android.launcher3.util.DisplayController.NavigationMode.NO_BUTTON;
@@ -27,17 +25,16 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
-import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
-import android.hardware.display.DisplayManager;
+import android.graphics.Rect;
+import android.util.ArrayMap;
import android.util.DisplayMetrics;
+import android.util.Size;
import android.view.Display;
import android.view.MotionEvent;
import android.view.Surface;
@@ -47,6 +44,10 @@
import com.android.launcher3.ResourceUtils;
import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.RotationUtils;
+import com.android.launcher3.util.WindowBounds;
+import com.android.launcher3.util.window.CachedDisplayInfo;
+import com.android.launcher3.util.window.WindowManagerProxy;
import org.junit.Before;
import org.junit.Test;
@@ -56,18 +57,9 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
public class OrientationTouchTransformerTest {
- static class ScreenSize {
- int mHeight;
- int mWidth;
- ScreenSize(int height, int width) {
- mHeight = height;
- mWidth = width;
- }
- }
-
- private static final ScreenSize NORMAL_SCREEN_SIZE = new ScreenSize(2280, 1080);
- private static final ScreenSize LARGE_SCREEN_SIZE = new ScreenSize(3280, 1080);
+ private static final Size NORMAL_SCREEN_SIZE = new Size(1080, 2280);
+ private static final Size LARGE_SCREEN_SIZE = new Size(1080, 3280);
private static final float DENSITY_DISPLAY_METRICS = 3.0f;
private OrientationTouchTransformer mTouchTransformer;
@@ -75,7 +67,6 @@
Resources mResources;
private DisplayController.Info mInfo;
-
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
@@ -296,33 +287,24 @@
assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion2.getX(), inRegion2.getY()));
}
- private DisplayController.Info createDisplayInfo(ScreenSize screenSize, int rotation) {
- Context context = getApplicationContext();
- Display display = spy(context.getSystemService(DisplayManager.class)
- .getDisplay(DEFAULT_DISPLAY));
-
- Point p = new Point(screenSize.mWidth, screenSize.mHeight);
- if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
- p.set(screenSize.mHeight, screenSize.mWidth);
- }
-
- doReturn(rotation).when(display).getRotation();
- doAnswer(i -> {
- ((Point) i.getArgument(0)).set(p.x, p.y);
- return null;
- }).when(display).getRealSize(any(Point.class));
- doAnswer(i -> {
- ((Point) i.getArgument(0)).set(p.x, p.y);
- ((Point) i.getArgument(1)).set(p.x, p.y);
- return null;
- }).when(display).getCurrentSizeRange(any(Point.class), any(Point.class));
- return new DisplayController.Info(context, display);
+ private DisplayController.Info createDisplayInfo(Size screenSize, int rotation) {
+ Point displaySize = new Point(screenSize.getWidth(), screenSize.getHeight());
+ RotationUtils.rotateSize(displaySize, rotation);
+ CachedDisplayInfo cdi = new CachedDisplayInfo(displaySize, rotation);
+ WindowBounds wm = new WindowBounds(
+ new Rect(0, 0, displaySize.x, displaySize.y),
+ new Rect());
+ WindowManagerProxy wmProxy = mock(WindowManagerProxy.class);
+ doReturn(cdi).when(wmProxy).getDisplayInfo(any(), any());
+ doReturn(wm).when(wmProxy).getRealBounds(any(), any(), any());
+ return new DisplayController.Info(
+ getApplicationContext(), mock(Display.class), wmProxy, new ArrayMap<>());
}
- private float generateTouchRegionHeight(ScreenSize screenSize, int rotation) {
- float height = screenSize.mHeight;
+ private float generateTouchRegionHeight(Size screenSize, int rotation) {
+ float height = screenSize.getHeight();
if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
- height = screenSize.mWidth;
+ height = screenSize.getWidth();
}
return height - ResourceUtils.DEFAULT_NAVBAR_VALUE * DENSITY_DISPLAY_METRICS;
}
diff --git a/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
index c6cdafc..7d414f4 100644
--- a/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
+++ b/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
@@ -164,7 +164,7 @@
}
WindowManagerProxy wmProxy = mock(WindowManagerProxy.class);
- doReturn(cdi).when(wmProxy).getDisplayInfo(any());
+ doReturn(cdi).when(wmProxy).getDisplayInfo(any(), any());
doReturn(wm).when(wmProxy).getRealBounds(any(), any(), any());
ArrayMap<String, Pair<CachedDisplayInfo, WindowBounds[]>> perDisplayBoundsCache =
diff --git a/res/color-v31/overview_scrim.xml b/res/color-v31/overview_scrim.xml
index 8079995..212518f 100644
--- a/res/color-v31/overview_scrim.xml
+++ b/res/color-v31/overview_scrim.xml
@@ -14,5 +14,5 @@
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral2_500" android:lStar="87" />
+ <item android:color="@android:color/system_neutral2_200" />
</selector>
diff --git a/res/layout/widgets_full_sheet.xml b/res/layout/widgets_full_sheet.xml
index 505ecb1..e867405 100644
--- a/res/layout/widgets_full_sheet.xml
+++ b/res/layout/widgets_full_sheet.xml
@@ -43,7 +43,6 @@
android:layout_height="match_parent"
android:gravity="center"
android:visibility="gone"
- android:fontFamily="sans-serif-medium"
android:textSize="20sp"
android:layout_below="@id/search_and_recommendations_container"
tools:text="No widgets available" />
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index c96a228..dd3e08b 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -258,14 +258,6 @@
<attr name="allAppsCellHeightTwoPanelLandscape" format="float" />
<!-- defaults to allAppsCellWidth, if not specified -->
<attr name="allAppsCellWidthTwoPanelLandscape" format="float" />
- <!-- defaults to borderSpace, if not specified -->
- <attr name="allAppsBorderSpace" format="float" />
- <!-- defaults to allAppsBorderSpace, if not specified -->
- <attr name="allAppsBorderSpaceLandscape" format="float" />
- <!-- defaults to allAppsBorderSpace, if not specified -->
- <attr name="allAppsBorderSpaceTwoPanelPortrait" format="float" />
- <!-- defaults to allAppsBorderSpace, if not specified -->
- <attr name="allAppsBorderSpaceTwoPanelLandscape" format="float" />
<!-- The following values are only enabled if grid is supported. -->
<!-- defaults to iconImageSize, if not specified -->
<attr name="allAppsIconSize" format="float" />
@@ -280,6 +272,40 @@
<!-- defaults to allAppsIconTextSize, if not specified -->
<attr name="allAppsIconTextSizeTwoPanelLandscape" format="float" />
+ <!-- defaults to borderSpace, if not specified -->
+ <!-- space to be used horizontally and vertically -->
+ <attr name="allAppsBorderSpace" format="float" />
+ <!-- space to the right of the cell, defaults to allAppsBorderSpace if not specified -->
+ <attr name="allAppsBorderSpaceHorizontal" format="float" />
+ <!-- space below the cell, defaults to allAppsBorderSpace if not specified -->
+ <attr name="allAppsBorderSpaceVertical" format="float" />
+ <!-- space to be used horizontally and vertically,
+ defaults to allAppsBorderSpace if not specified -->
+ <attr name="allAppsBorderSpaceLandscape" format="float" />
+ <!-- space to the right of the cell, defaults to allAppsBorderSpaceLandscape
+ if not specified -->
+ <attr name="allAppsBorderSpaceLandscapeHorizontal" format="float" />
+ <!-- space below the cell, defaults to allAppsBorderSpaceLandscape if not specified -->
+ <attr name="allAppsBorderSpaceLandscapeVertical" format="float" />
+ <!-- space to be used horizontally and vertically in two panels,
+ defaults to allAppsBorderSpace if not specified -->
+ <attr name="allAppsBorderSpaceTwoPanelPortrait" format="float" />
+ <!-- space to the right of the cell in two panels, defaults to
+ allAppsBorderSpaceTwoPanelPortrait if not specified -->
+ <attr name="allAppsBorderSpaceTwoPanelPortraitHorizontal" format="float" />
+ <!-- space below the cell in two panels, defaults to allAppsBorderSpaceTwoPanelPortrait
+ if not specified -->
+ <attr name="allAppsBorderSpaceTwoPanelPortraitVertical" format="float" />
+ <!-- space to be used horizontally and vertically in two panels,
+ defaults to allAppsBorderSpace if not specified -->
+ <attr name="allAppsBorderSpaceTwoPanelLandscape" format="float" />
+ <!-- space to the right of the cell in two panels, defaults to
+ allAppsBorderSpaceTwoPanelLandscape if not specified -->
+ <attr name="allAppsBorderSpaceTwoPanelLandscapeHorizontal" format="float" />
+ <!-- space below the cell in two panels, defaults to allAppsBorderSpaceTwoPanelLandscape
+ if not specified -->
+ <attr name="allAppsBorderSpaceTwoPanelLandscapeVertical" format="float" />
+
<!-- defaults to borderSpaceDps, if not specified -->
<attr name="hotseatBorderSpace" format="float" />
<!-- defaults to hotseatBorderSpace, if not specified -->
@@ -316,6 +342,18 @@
<attr name="horizontalMarginTwoPanelLandscape" format="float"/>
<!-- defaults to horizontalMargin if not specified -->
<attr name="horizontalMarginTwoPanelPortrait" format="float"/>
+
+ <!-- By default all are false -->
+ <attr name="inlineQsb" format="integer" >
+ <!-- Enable on landscape only -->
+ <flag name="portrait" value="1" />
+ <!-- Enable on portrait only -->
+ <flag name="landscape" value="2" />
+ <!-- Enable on two panel portrait only -->
+ <flag name="twoPanelPortrait" value="4" />
+ <!-- Enable on two panel landscape only -->
+ <flag name="twoPanelLandscape" value="8" />
+ </attr>
</declare-styleable>
<declare-styleable name="CellLayout">
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 0615053..2ee4c45 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -109,6 +109,7 @@
<dimen name="all_apps_header_pill_corner_radius">12dp</dimen>
<dimen name="all_apps_header_tab_height">48dp</dimen>
<dimen name="all_apps_tabs_indicator_height">2dp</dimen>
+ <dimen name="all_apps_header_top_margin">33dp</dimen>
<dimen name="all_apps_header_top_padding">36dp</dimen>
<dimen name="all_apps_header_bottom_padding">6dp</dimen>
<dimen name="all_apps_work_profile_tab_footer_top_padding">16dp</dimen>
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 0c33bce..500244c 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -16,6 +16,10 @@
package com.android.launcher3;
+import static com.android.launcher3.InvariantDeviceProfile.INDEX_DEFAULT;
+import static com.android.launcher3.InvariantDeviceProfile.INDEX_LANDSCAPE;
+import static com.android.launcher3.InvariantDeviceProfile.INDEX_TWO_PANEL_LANDSCAPE;
+import static com.android.launcher3.InvariantDeviceProfile.INDEX_TWO_PANEL_PORTRAIT;
import static com.android.launcher3.ResourceUtils.pxFromDp;
import static com.android.launcher3.Utilities.dpiFromPx;
import static com.android.launcher3.Utilities.pxFromSp;
@@ -58,7 +62,6 @@
// Device properties
public final boolean isTablet;
- public final boolean isLargeTablet;
public final boolean isPhone;
public final boolean transposeLayoutWithOrientation;
public final boolean isTwoPanels;
@@ -253,7 +256,6 @@
// Determine device posture.
mInfo = info;
isTablet = info.isTablet(windowBounds);
- isLargeTablet = info.isLargeTablet(windowBounds);
isPhone = !isTablet;
isTwoPanels = isTablet && useTwoPanels;
isTaskbarPresent = isTablet && ApiWrapper.TASKBAR_DRAWN_IN_PROCESS;
@@ -278,15 +280,15 @@
if (isTwoPanels) {
if (isLandscape) {
- mTypeIndex = InvariantDeviceProfile.INDEX_TWO_PANEL_LANDSCAPE;
+ mTypeIndex = INDEX_TWO_PANEL_LANDSCAPE;
} else {
- mTypeIndex = InvariantDeviceProfile.INDEX_TWO_PANEL_PORTRAIT;
+ mTypeIndex = INDEX_TWO_PANEL_PORTRAIT;
}
} else {
if (isLandscape) {
- mTypeIndex = InvariantDeviceProfile.INDEX_LANDSCAPE;
+ mTypeIndex = INDEX_LANDSCAPE;
} else {
- mTypeIndex = InvariantDeviceProfile.INDEX_DEFAULT;
+ mTypeIndex = INDEX_DEFAULT;
}
}
@@ -319,7 +321,7 @@
pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].x, mMetrics),
pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].y, mMetrics));
cellLayoutBorderSpaceOriginalPx = new Point(cellLayoutBorderSpacePx);
- folderCellLayoutBorderSpaceOriginalPx = pxFromDp(inv.folderBorderSpace, mMetrics, 1f);
+ folderCellLayoutBorderSpaceOriginalPx = pxFromDp(inv.folderBorderSpace, mMetrics);
folderCellLayoutBorderSpacePx = new Point(folderCellLayoutBorderSpaceOriginalPx,
folderCellLayoutBorderSpaceOriginalPx);
@@ -348,9 +350,12 @@
workspaceCellPaddingXPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_padding_x);
hotseatQsbHeight = res.getDimensionPixelSize(R.dimen.qsb_widget_height);
- // Whether QSB might be inline in appropriate orientation (landscape).
- boolean canQsbInline = isLargeTablet && hotseatQsbHeight > 0;
- isQsbInline = canQsbInline && isLandscape;
+ // Whether QSB might be inline in appropriate orientation (e.g. landscape).
+ boolean canQsbInline = (isTwoPanels ? inv.inlineQsb[INDEX_TWO_PANEL_PORTRAIT]
+ || inv.inlineQsb[INDEX_TWO_PANEL_LANDSCAPE]
+ : inv.inlineQsb[INDEX_DEFAULT] || inv.inlineQsb[INDEX_LANDSCAPE])
+ && hotseatQsbHeight > 0;
+ isQsbInline = inv.inlineQsb[mTypeIndex] && canQsbInline;
// We shrink hotseat sizes regardless of orientation, if nav buttons are inline and QSB
// might be inline in either orientations, to keep hotseat size consistent across rotation.
@@ -388,7 +393,7 @@
res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size);
hotseatBorderSpace = pxFromDp(inv.hotseatBorderSpaces[mTypeIndex], mMetrics);
updateHotseatIconSize(
- pxFromDp(inv.iconSize[InvariantDeviceProfile.INDEX_DEFAULT], mMetrics));
+ pxFromDp(inv.iconSize[INDEX_DEFAULT], mMetrics));
qsbBottomMarginOriginalPx = isScalableGrid
? res.getDimensionPixelSize(R.dimen.scalable_grid_qsb_bottom_margin)
@@ -525,21 +530,21 @@
}
private Point getCellLayoutBorderSpace(InvariantDeviceProfile idp) {
+ return getCellLayoutBorderSpace(idp, 1f);
+
+ }
+
+ private Point getCellLayoutBorderSpace(InvariantDeviceProfile idp, float scale) {
if (!isScalableGrid) {
return new Point(0, 0);
}
- int horizontalSpacePx = pxFromDp(idp.borderSpaces[mTypeIndex].x, mMetrics);
- int verticalSpacePx = pxFromDp(idp.borderSpaces[mTypeIndex].y, mMetrics);
+ int horizontalSpacePx = pxFromDp(idp.borderSpaces[mTypeIndex].x, mMetrics, scale);
+ int verticalSpacePx = pxFromDp(idp.borderSpaces[mTypeIndex].y, mMetrics, scale);
return new Point(horizontalSpacePx, verticalSpacePx);
}
- private Point getCellLayoutBorderSpaceScaled(InvariantDeviceProfile idp, float scale) {
- Point original = getCellLayoutBorderSpace(idp);
- return new Point((int) (original.x * scale), (int) (original.y * scale));
- }
-
public Info getDisplayInfo() {
return mInfo;
}
@@ -708,7 +713,7 @@
iconTextSizePx = (int) (pxFromSp(invIconTextSizeSp, mMetrics) * iconScale);
iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * iconScale);
- cellLayoutBorderSpacePx = getCellLayoutBorderSpaceScaled(inv, scale);
+ cellLayoutBorderSpacePx = getCellLayoutBorderSpace(inv, scale);
if (isScalableGrid) {
cellWidthPx = pxFromDp(inv.minCellSize[mTypeIndex].x, mMetrics, scale);
@@ -756,15 +761,22 @@
* Updates the iconSize for allApps* variants.
*/
public void updateAllAppsIconSize(float scale, Resources res) {
- //TODO(b/218638090): remove the tablet condition once we have phone specs
- if (isScalableGrid && isTablet) {
+ allAppsBorderSpacePx = new Point(
+ pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].x, mMetrics, scale),
+ pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].y, mMetrics, scale));
+ if (isScalableGrid) {
allAppsIconSizePx =
pxFromDp(inv.allAppsIconSize[mTypeIndex], mMetrics);
allAppsIconTextSizePx =
pxFromSp(inv.allAppsIconTextSize[mTypeIndex], mMetrics);
allAppsIconDrawablePaddingPx = iconDrawablePaddingOriginalPx;
+ // AllApps cells don't have real space between cells,
+ // so we add the border space to the cell height
+ allAppsCellHeightPx = pxFromDp(inv.allAppsCellSize[mTypeIndex].y, mMetrics, scale)
+ + allAppsBorderSpacePx.y;
+ // but width is just the cell,
+ // the border is added in #updateAllAppsContainerWidth
allAppsCellWidthPx = pxFromDp(inv.allAppsCellSize[mTypeIndex].x, mMetrics, scale);
- allAppsCellHeightPx = pxFromDp(inv.allAppsCellSize[mTypeIndex].y, mMetrics, scale);
} else {
float invIconSizeDp = inv.iconSize[mTypeIndex];
float invIconTextSizeSp = inv.iconTextSize[mTypeIndex];
@@ -812,11 +824,11 @@
private void updateFolderCellSize(float scale, Resources res) {
float invIconSizeDp = isVerticalBarLayout()
- ? inv.iconSize[InvariantDeviceProfile.INDEX_LANDSCAPE]
- : inv.iconSize[InvariantDeviceProfile.INDEX_DEFAULT];
+ ? inv.iconSize[INDEX_LANDSCAPE]
+ : inv.iconSize[INDEX_DEFAULT];
folderChildIconSizePx = Math.max(1, pxFromDp(invIconSizeDp, mMetrics, scale));
folderChildTextSizePx =
- pxFromSp(inv.iconTextSize[InvariantDeviceProfile.INDEX_DEFAULT], mMetrics, scale);
+ pxFromSp(inv.iconTextSize[INDEX_DEFAULT], mMetrics, scale);
folderLabelTextSizePx = (int) (folderChildTextSizePx * folderLabelTextScale);
int textHeight = Utilities.calculateTextHeight(folderChildTextSizePx);
@@ -1167,7 +1179,6 @@
writer.println(prefix + "\t1 dp = " + mMetrics.density + " px");
writer.println(prefix + "\tisTablet:" + isTablet);
- writer.println(prefix + "\tisLargeTablet:" + isLargeTablet);
writer.println(prefix + "\tisPhone:" + isPhone);
writer.println(prefix + "\ttransposeLayoutWithOrientation:"
+ transposeLayoutWithOrientation);
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 219ed9e..36c1797 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -167,6 +167,7 @@
public String dbFile;
public int defaultLayoutId;
int demoModeLayoutId;
+ boolean[] inlineQsb = new boolean[COUNT_SIZES];
/**
* An immutable list of supported profiles.
@@ -250,6 +251,8 @@
COUNT_SIZES);
System.arraycopy(defaultDisplayOption.borderSpaces, 0, result.borderSpaces, 0,
COUNT_SIZES);
+ System.arraycopy(defaultDisplayOption.inlineQsb, 0, result.inlineQsb, 0,
+ COUNT_SIZES);
initGrid(context, myInfo, result, deviceType);
}
@@ -371,6 +374,8 @@
devicePaddings = new DevicePaddings(context, devicePaddingId);
}
+ inlineQsb = displayOption.inlineQsb;
+
// If the partner customization apk contains any grid overrides, apply them
// Supported overrides: numRows, numColumns, iconSize
applyPartnerDeviceProfileOverrides(context, metrics);
@@ -783,12 +788,18 @@
@VisibleForTesting
static final class DisplayOption {
+ private static final int INLINE_QSB_FOR_PORTRAIT = 1 << 0;
+ private static final int INLINE_QSB_FOR_LANDSCAPE = 1 << 1;
+ private static final int INLINE_QSB_FOR_TWO_PANEL_PORTRAIT = 1 << 2;
+ private static final int INLINE_QSB_FOR_TWO_PANEL_LANDSCAPE = 1 << 3;
+ private static final int DONT_INLINE_QSB = 0;
public final GridOption grid;
private final float minWidthDps;
private final float minHeightDps;
private final boolean canBeDefault;
+ private final boolean[] inlineQsb = new boolean[COUNT_SIZES];
private final PointF[] minCellSize = new PointF[COUNT_SIZES];
@@ -815,6 +826,19 @@
canBeDefault = a.getBoolean(R.styleable.ProfileDisplayOption_canBeDefault, false);
+ int inlineForRotation = a.getInt(R.styleable.ProfileDisplayOption_inlineQsb,
+ DONT_INLINE_QSB);
+ inlineQsb[INDEX_DEFAULT] =
+ (inlineForRotation & INLINE_QSB_FOR_PORTRAIT) == INLINE_QSB_FOR_PORTRAIT;
+ inlineQsb[INDEX_LANDSCAPE] =
+ (inlineForRotation & INLINE_QSB_FOR_LANDSCAPE) == INLINE_QSB_FOR_LANDSCAPE;
+ inlineQsb[INDEX_TWO_PANEL_PORTRAIT] =
+ (inlineForRotation & INLINE_QSB_FOR_TWO_PANEL_PORTRAIT)
+ == INLINE_QSB_FOR_TWO_PANEL_PORTRAIT;
+ inlineQsb[INDEX_TWO_PANEL_LANDSCAPE] =
+ (inlineForRotation & INLINE_QSB_FOR_TWO_PANEL_LANDSCAPE)
+ == INLINE_QSB_FOR_TWO_PANEL_LANDSCAPE;
+
float x;
float y;
@@ -900,19 +924,44 @@
allAppsCellSize[INDEX_DEFAULT].y);
allAppsCellSize[INDEX_TWO_PANEL_LANDSCAPE] = new PointF(x, y);
- x = y = a.getFloat(R.styleable.ProfileDisplayOption_allAppsBorderSpace,
- borderSpace);
- allAppsBorderSpaces[INDEX_DEFAULT] = new PointF(x, y);
- x = y = a.getFloat(R.styleable.ProfileDisplayOption_allAppsBorderSpaceLandscape,
- allAppsBorderSpaces[INDEX_DEFAULT].x);
- allAppsBorderSpaces[INDEX_LANDSCAPE] = new PointF(x, y);
- x = y = a.getFloat(
+ float allAppsBorderSpace = a.getFloat(
+ R.styleable.ProfileDisplayOption_allAppsBorderSpace, borderSpace);
+ float allAppsBorderSpaceLandscape = a.getFloat(
+ R.styleable.ProfileDisplayOption_allAppsBorderSpaceLandscape,
+ allAppsBorderSpace);
+ float allAppsBorderSpaceTwoPanelPortrait = a.getFloat(
R.styleable.ProfileDisplayOption_allAppsBorderSpaceTwoPanelPortrait,
- allAppsBorderSpaces[INDEX_DEFAULT].x);
- allAppsBorderSpaces[INDEX_TWO_PANEL_PORTRAIT] = new PointF(x, y);
- x = y = a.getFloat(
+ allAppsBorderSpace);
+ float allAppsBorderSpaceTwoPanelLandscape = a.getFloat(
R.styleable.ProfileDisplayOption_allAppsBorderSpaceTwoPanelLandscape,
- allAppsBorderSpaces[INDEX_DEFAULT].x);
+ allAppsBorderSpace);
+
+ x = a.getFloat(R.styleable.ProfileDisplayOption_allAppsBorderSpaceHorizontal,
+ allAppsBorderSpace);
+ y = a.getFloat(R.styleable.ProfileDisplayOption_allAppsBorderSpaceVertical,
+ allAppsBorderSpace);
+ allAppsBorderSpaces[INDEX_DEFAULT] = new PointF(x, y);
+
+ x = a.getFloat(R.styleable.ProfileDisplayOption_allAppsBorderSpaceLandscapeHorizontal,
+ allAppsBorderSpaceLandscape);
+ y = a.getFloat(R.styleable.ProfileDisplayOption_allAppsBorderSpaceLandscapeVertical,
+ allAppsBorderSpaceLandscape);
+ allAppsBorderSpaces[INDEX_LANDSCAPE] = new PointF(x, y);
+
+ x = a.getFloat(
+ R.styleable.ProfileDisplayOption_allAppsBorderSpaceTwoPanelPortraitHorizontal,
+ allAppsBorderSpaceTwoPanelPortrait);
+ y = a.getFloat(
+ R.styleable.ProfileDisplayOption_allAppsBorderSpaceTwoPanelPortraitVertical,
+ allAppsBorderSpaceTwoPanelPortrait);
+ allAppsBorderSpaces[INDEX_TWO_PANEL_PORTRAIT] = new PointF(x, y);
+
+ x = a.getFloat(
+ R.styleable.ProfileDisplayOption_allAppsBorderSpaceTwoPanelLandscapeHorizontal,
+ allAppsBorderSpaceTwoPanelLandscape);
+ y = a.getFloat(
+ R.styleable.ProfileDisplayOption_allAppsBorderSpaceTwoPanelLandscapeVertical,
+ allAppsBorderSpaceTwoPanelLandscape);
allAppsBorderSpaces[INDEX_TWO_PANEL_LANDSCAPE] = new PointF(x, y);
iconSizes[INDEX_DEFAULT] =
@@ -1004,6 +1053,7 @@
allAppsIconSizes[i] = 0;
allAppsIconTextSizes[i] = 0;
allAppsBorderSpaces[i] = new PointF();
+ inlineQsb[i] = false;
}
}
@@ -1046,6 +1096,7 @@
allAppsIconTextSizes[i] += p.allAppsIconTextSizes[i];
allAppsBorderSpaces[i].x += p.allAppsBorderSpaces[i].x;
allAppsBorderSpaces[i].y += p.allAppsBorderSpaces[i].y;
+ inlineQsb[i] |= p.inlineQsb[i];
}
folderBorderSpace += p.folderBorderSpace;
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 11e0a1f..e279f59 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -175,22 +175,27 @@
@Override
protected View replaceRVContainer(boolean showTabs) {
View rvContainer = super.replaceRVContainer(showTabs);
+
+ removeCustomRules(rvContainer);
if (FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get()) {
- alignParentTop(rvContainer);
+ alignParentTop(rvContainer, showTabs);
layoutAboveSearchContainer(rvContainer);
} else {
- layoutBelowSearchContainer(rvContainer);
+ layoutBelowSearchContainer(rvContainer, showTabs);
}
+
return rvContainer;
}
@Override
void setupHeader() {
super.setupHeader();
+
+ removeCustomRules(mHeader);
if (FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get()) {
- alignParentTop(mHeader);
+ alignParentTop(mHeader, false /* includeTabsMargin */);
} else {
- layoutBelowSearchContainer(mHeader);
+ layoutBelowSearchContainer(mHeader, false /* includeTabsMargin */);
}
}
@@ -226,31 +231,55 @@
return super.getHeaderBottom() + mSearchContainer.getBottom();
}
- private void layoutBelowSearchContainer(View v) {
+ private void layoutBelowSearchContainer(View v, boolean includeTabsMargin) {
if (!(v.getLayoutParams() instanceof RelativeLayout.LayoutParams)) {
return;
}
+
RelativeLayout.LayoutParams layoutParams = (LayoutParams) v.getLayoutParams();
- layoutParams.removeRule(RelativeLayout.ALIGN_PARENT_TOP);
- layoutParams.removeRule(RelativeLayout.ABOVE);
- layoutParams.addRule(RelativeLayout.BELOW, R.id.search_container_all_apps);
+ layoutParams.addRule(RelativeLayout.ALIGN_TOP, R.id.search_container_all_apps);
+
+ int topMargin = getContext().getResources().getDimensionPixelSize(
+ R.dimen.all_apps_header_top_margin);
+ if (includeTabsMargin) {
+ topMargin = topMargin + getContext().getResources().getDimensionPixelSize(
+ R.dimen.all_apps_header_pill_height);
+ }
+ layoutParams.topMargin = topMargin;
}
private void layoutAboveSearchContainer(View v) {
if (!(v.getLayoutParams() instanceof RelativeLayout.LayoutParams)) {
return;
}
+
RelativeLayout.LayoutParams layoutParams = (LayoutParams) v.getLayoutParams();
layoutParams.addRule(RelativeLayout.ABOVE, R.id.search_container_all_apps);
}
- private void alignParentTop(View v) {
+ private void alignParentTop(View v, boolean includeTabsMargin) {
if (!(v.getLayoutParams() instanceof RelativeLayout.LayoutParams)) {
return;
}
+
RelativeLayout.LayoutParams layoutParams = (LayoutParams) v.getLayoutParams();
- layoutParams.removeRule(RelativeLayout.BELOW);
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+ layoutParams.topMargin =
+ includeTabsMargin
+ ? getContext().getResources().getDimensionPixelSize(
+ R.dimen.all_apps_header_pill_height)
+ : 0;
+ }
+
+ private void removeCustomRules(View v) {
+ if (!(v.getLayoutParams() instanceof RelativeLayout.LayoutParams)) {
+ return;
+ }
+
+ RelativeLayout.LayoutParams layoutParams = (LayoutParams) v.getLayoutParams();
+ layoutParams.removeRule(RelativeLayout.ABOVE);
+ layoutParams.removeRule(RelativeLayout.ALIGN_TOP);
+ layoutParams.removeRule(RelativeLayout.ALIGN_PARENT_TOP);
}
@Override
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index 91fb44e..ca91296 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -15,22 +15,17 @@
*/
package com.android.launcher3.model;
-import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
-
import android.content.Intent;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageInstaller.SessionInfo;
import android.os.UserHandle;
import android.util.Log;
-import android.util.LongSparseArray;
import android.util.Pair;
-import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel.CallbackTask;
import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.data.AppInfo;
@@ -41,9 +36,7 @@
import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.pm.PackageInstallInfo;
import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.PackageManagerHelper;
import java.util.ArrayList;
@@ -58,11 +51,23 @@
private final List<Pair<ItemInfo, Object>> mItemList;
+ private final WorkspaceItemSpaceFinder mItemSpaceFinder;
+
/**
* @param itemList items to add on the workspace
*/
public AddWorkspaceItemsTask(List<Pair<ItemInfo, Object>> itemList) {
+ this(itemList, new WorkspaceItemSpaceFinder());
+ }
+
+ /**
+ * @param itemList items to add on the workspace
+ * @param itemSpaceFinder inject WorkspaceItemSpaceFinder dependency for testing
+ */
+ public AddWorkspaceItemsTask(List<Pair<ItemInfo, Object>> itemList,
+ WorkspaceItemSpaceFinder itemSpaceFinder) {
mItemList = itemList;
+ mItemSpaceFinder = itemSpaceFinder;
}
@Override
@@ -74,7 +79,7 @@
final ArrayList<ItemInfo> addedItemsFinal = new ArrayList<>();
final IntArray addedWorkspaceScreensFinal = new IntArray();
- synchronized(dataModel) {
+ synchronized (dataModel) {
IntArray workspaceScreens = dataModel.collectWorkspaceScreens();
List<ItemInfo> filteredItems = new ArrayList<>();
@@ -117,7 +122,7 @@
for (ItemInfo item : filteredItems) {
// Find appropriate space for the item.
- int[] coords = findSpaceForItem(app, dataModel, workspaceScreens,
+ int[] coords = mItemSpaceFinder.findSpaceForItem(app, dataModel, workspaceScreens,
addedWorkspaceScreensFinal, item.spanX, item.spanY);
int screenId = coords[0];
@@ -288,82 +293,4 @@
}
return false;
}
-
- /**
- * Find a position on the screen for the given size or adds a new screen.
- * @return screenId and the coordinates for the item in an int array of size 3.
- */
- protected int[] findSpaceForItem( LauncherAppState app, BgDataModel dataModel,
- IntArray workspaceScreens, IntArray addedWorkspaceScreensFinal, int spanX, int spanY) {
- LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>();
-
- // Use sBgItemsIdMap as all the items are already loaded.
- synchronized (dataModel) {
- for (ItemInfo info : dataModel.itemsIdMap) {
- if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
- ArrayList<ItemInfo> items = screenItems.get(info.screenId);
- if (items == null) {
- items = new ArrayList<>();
- screenItems.put(info.screenId, items);
- }
- items.add(info);
- }
- }
- }
-
- // Find appropriate space for the item.
- int screenId = 0;
- int[] coordinates = new int[2];
- boolean found = false;
-
- int screenCount = workspaceScreens.size();
- // First check the preferred screen.
- IntSet screensToExclude = new IntSet();
- if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
- screensToExclude.add(FIRST_SCREEN_ID);
- }
-
- for (int screen = 0; screen < screenCount; screen++) {
- screenId = workspaceScreens.get(screen);
- if (!screensToExclude.contains(screenId) && findNextAvailableIconSpaceInScreen(
- app, screenItems.get(screenId), coordinates, spanX, spanY)) {
- // We found a space for it
- found = true;
- break;
- }
- }
-
- if (!found) {
- // Still no position found. Add a new screen to the end.
- screenId = LauncherSettings.Settings.call(app.getContext().getContentResolver(),
- LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
- .getInt(LauncherSettings.Settings.EXTRA_VALUE);
-
- // Save the screen id for binding in the workspace
- workspaceScreens.add(screenId);
- addedWorkspaceScreensFinal.add(screenId);
-
- // If we still can't find an empty space, then God help us all!!!
- if (!findNextAvailableIconSpaceInScreen(
- app, screenItems.get(screenId), coordinates, spanX, spanY)) {
- throw new RuntimeException("Can't find space to add the item");
- }
- }
- return new int[] {screenId, coordinates[0], coordinates[1]};
- }
-
- private boolean findNextAvailableIconSpaceInScreen(
- LauncherAppState app, ArrayList<ItemInfo> occupiedPos,
- int[] xy, int spanX, int spanY) {
- InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
-
- GridOccupancy occupied = new GridOccupancy(profile.numColumns, profile.numRows);
- if (occupiedPos != null) {
- for (ItemInfo r : occupiedPos) {
- occupied.markCells(r, true);
- }
- }
- return occupied.findVacantCell(xy, spanX, spanY);
- }
-
}
diff --git a/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java b/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java
new file mode 100644
index 0000000..93fc6a5
--- /dev/null
+++ b/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java
@@ -0,0 +1,115 @@
+/*
+ * 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.launcher3.model;
+
+import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
+
+import android.util.LongSparseArray;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.util.GridOccupancy;
+import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.IntSet;
+
+import java.util.ArrayList;
+
+/**
+ * Utility class to help find space for new workspace items
+ */
+public class WorkspaceItemSpaceFinder {
+
+ /**
+ * Find a position on the screen for the given size or adds a new screen.
+ *
+ * @return screenId and the coordinates for the item in an int array of size 3.
+ */
+ public int[] findSpaceForItem(LauncherAppState app, BgDataModel dataModel,
+ IntArray workspaceScreens, IntArray addedWorkspaceScreensFinal, int spanX, int spanY) {
+ LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>();
+
+ // Use sBgItemsIdMap as all the items are already loaded.
+ synchronized (dataModel) {
+ for (ItemInfo info : dataModel.itemsIdMap) {
+ if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+ ArrayList<ItemInfo> items = screenItems.get(info.screenId);
+ if (items == null) {
+ items = new ArrayList<>();
+ screenItems.put(info.screenId, items);
+ }
+ items.add(info);
+ }
+ }
+ }
+
+ // Find appropriate space for the item.
+ int screenId = 0;
+ int[] coordinates = new int[2];
+ boolean found = false;
+
+ int screenCount = workspaceScreens.size();
+ // First check the preferred screen.
+ IntSet screensToExclude = new IntSet();
+ if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
+ screensToExclude.add(FIRST_SCREEN_ID);
+ }
+
+ for (int screen = 0; screen < screenCount; screen++) {
+ screenId = workspaceScreens.get(screen);
+ if (!screensToExclude.contains(screenId) && findNextAvailableIconSpaceInScreen(
+ app, screenItems.get(screenId), coordinates, spanX, spanY)) {
+ // We found a space for it
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ // Still no position found. Add a new screen to the end.
+ screenId = LauncherSettings.Settings.call(app.getContext().getContentResolver(),
+ LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
+ .getInt(LauncherSettings.Settings.EXTRA_VALUE);
+
+ // Save the screen id for binding in the workspace
+ workspaceScreens.add(screenId);
+ addedWorkspaceScreensFinal.add(screenId);
+
+ // If we still can't find an empty space, then God help us all!!!
+ if (!findNextAvailableIconSpaceInScreen(
+ app, screenItems.get(screenId), coordinates, spanX, spanY)) {
+ throw new RuntimeException("Can't find space to add the item");
+ }
+ }
+ return new int[]{screenId, coordinates[0], coordinates[1]};
+ }
+
+ private boolean findNextAvailableIconSpaceInScreen(
+ LauncherAppState app, ArrayList<ItemInfo> occupiedPos,
+ int[] xy, int spanX, int spanY) {
+ InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
+
+ GridOccupancy occupied = new GridOccupancy(profile.numColumns, profile.numRows);
+ if (occupiedPos != null) {
+ for (ItemInfo r : occupiedPos) {
+ occupied.markCells(r, true);
+ }
+ }
+ return occupied.findVacantCell(xy, spanX, spanY);
+ }
+}
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index 88a4d66..cba5be5 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -454,8 +454,8 @@
@Override
public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
- int parentWidth, int parentHeight,
- StagedSplitBounds splitBoundsConfig, DeviceProfile dp) {
+ int parentWidth, int parentHeight, StagedSplitBounds splitBoundsConfig,
+ DeviceProfile dp, boolean isRtl) {
int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
int dividerBar = splitBoundsConfig.appsStackedVertically
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index 510a728..911f2b0 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -158,7 +158,7 @@
void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
int parentWidth, int parentHeight,
- StagedSplitBounds splitBoundsConfig, DeviceProfile dp);
+ StagedSplitBounds splitBoundsConfig, DeviceProfile dp, boolean isRtl);
// Overview TaskMenuView methods
void setTaskIconParams(FrameLayout.LayoutParams iconParams,
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index d88656c..2ca236e 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -571,8 +571,8 @@
@Override
public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
- int parentWidth, int parentHeight,
- StagedSplitBounds splitBoundsConfig, DeviceProfile dp) {
+ int parentWidth, int parentHeight, StagedSplitBounds splitBoundsConfig,
+ DeviceProfile dp, boolean isRtl) {
int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
int dividerBar = splitBoundsConfig.appsStackedVertically
@@ -591,7 +591,13 @@
secondarySnapshotHeight = totalThumbnailHeight;
secondarySnapshotWidth = parentWidth - primarySnapshotWidth - dividerBar;
int translationX = primarySnapshotWidth + dividerBar;
- secondarySnapshot.setTranslationX(translationX);
+ if (isRtl) {
+ primarySnapshot.setTranslationX(-translationX);
+ secondarySnapshot.setTranslationX(0);
+ } else {
+ secondarySnapshot.setTranslationX(translationX);
+ primarySnapshot.setTranslationX(0);
+ }
secondarySnapshot.setTranslationY(spaceAboveSnapshot);
} else {
primarySnapshotWidth = parentWidth;
@@ -602,6 +608,7 @@
int translationY = primarySnapshotHeight + spaceAboveSnapshot + dividerBar;
secondarySnapshot.setTranslationY(translationY);
secondarySnapshot.setTranslationX(0);
+ primarySnapshot.setTranslationX(0);
}
primarySnapshot.measure(
View.MeasureSpec.makeMeasureSpec(primarySnapshotWidth, View.MeasureSpec.EXACTLY),
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 8b4ff85..8005181 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -26,7 +26,6 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NAVIGATION_MODE_GESTURE_BUTTON;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
-import static com.android.launcher3.util.window.WindowManagerProxy.MIN_LARGE_TABLET_WIDTH;
import static com.android.launcher3.util.window.WindowManagerProxy.MIN_TABLET_WIDTH;
import android.annotation.SuppressLint;
@@ -306,7 +305,7 @@
public Info(Context context, Display display,
WindowManagerProxy wmProxy,
ArrayMap<String, Pair<CachedDisplayInfo, WindowBounds[]>> perDisplayBoundsCache) {
- CachedDisplayInfo displayInfo = wmProxy.getDisplayInfo(display);
+ CachedDisplayInfo displayInfo = wmProxy.getDisplayInfo(context, display);
rotation = displayInfo.rotation;
currentSize = displayInfo.size;
displayId = displayInfo.id;
@@ -349,13 +348,6 @@
}
/**
- * Returns {@code true} if the bounds represent a large tablet.
- */
- public boolean isLargeTablet(WindowBounds bounds) {
- return smallestSizeDp(bounds) >= MIN_LARGE_TABLET_WIDTH;
- }
-
- /**
* Returns smallest size in dp for given bounds.
*/
public float smallestSizeDp(WindowBounds bounds) {
@@ -372,7 +364,7 @@
pw.println(" id=" + info.displayId);
pw.println(" rotation=" + info.rotation);
pw.println(" fontScale=" + info.fontScale);
- pw.println(" densityDpi=" + info.displayId);
+ pw.println(" densityDpi=" + info.densityDpi);
pw.println(" navigationMode=" + info.navigationMode.name());
pw.println(" currentSize=" + info.currentSize);
pw.println(" supportedBounds=" + info.supportedBounds);
diff --git a/src/com/android/launcher3/util/SplitConfigurationOptions.java b/src/com/android/launcher3/util/SplitConfigurationOptions.java
index b40493a..6a336cc 100644
--- a/src/com/android/launcher3/util/SplitConfigurationOptions.java
+++ b/src/com/android/launcher3/util/SplitConfigurationOptions.java
@@ -154,10 +154,12 @@
}
}
- leftTaskPercent = this.leftTopBounds.width() / (float) rightBottomBounds.right;
- topTaskPercent = this.leftTopBounds.height() / (float) rightBottomBounds.bottom;
- dividerWidthPercent = visualDividerBounds.width() / (float) rightBottomBounds.right;
- dividerHeightPercent = visualDividerBounds.height() / (float) rightBottomBounds.bottom;
+ float totalWidth = rightBottomBounds.right - leftTopBounds.left;
+ float totalHeight = rightBottomBounds.bottom - leftTopBounds.top;
+ leftTaskPercent = leftTopBounds.width() / totalWidth;
+ topTaskPercent = leftTopBounds.height() / totalHeight;
+ dividerWidthPercent = visualDividerBounds.width() / totalWidth;
+ dividerHeightPercent = visualDividerBounds.height() / totalHeight;
}
}
diff --git a/src/com/android/launcher3/util/window/WindowManagerProxy.java b/src/com/android/launcher3/util/window/WindowManagerProxy.java
index ba3d981..2fd0a7e 100644
--- a/src/com/android/launcher3/util/window/WindowManagerProxy.java
+++ b/src/com/android/launcher3/util/window/WindowManagerProxy.java
@@ -15,6 +15,9 @@
*/
package com.android.launcher3.util.window;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+
import static com.android.launcher3.ResourceUtils.INVALID_RESOURCE_HANDLE;
import static com.android.launcher3.ResourceUtils.NAVBAR_HEIGHT;
import static com.android.launcher3.ResourceUtils.NAVBAR_HEIGHT_LANDSCAPE;
@@ -47,7 +50,6 @@
import com.android.launcher3.R;
import com.android.launcher3.ResourceUtils;
import com.android.launcher3.Utilities;
-import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.ResourceBasedOverride;
import com.android.launcher3.util.WindowBounds;
@@ -58,7 +60,6 @@
public class WindowManagerProxy implements ResourceBasedOverride {
public static final int MIN_TABLET_WIDTH = 600;
- public static final int MIN_LARGE_TABLET_WIDTH = 720;
public static final MainThreadInitializedObject<WindowManagerProxy> INSTANCE =
forOverride(WindowManagerProxy.class, R.string.window_manager_proxy_class);
@@ -91,7 +92,10 @@
ArrayMap<String, Pair<CachedDisplayInfo, WindowBounds[]>> result = new ArrayMap<>();
for (Display display : displays) {
if (isInternalDisplay(display)) {
- CachedDisplayInfo info = getDisplayInfo(display).normalize();
+ Context displayContext = Utilities.ATLEAST_S
+ ? context.createWindowContext(display, TYPE_APPLICATION, null)
+ : context.createDisplayContext(display);
+ CachedDisplayInfo info = getDisplayInfo(displayContext, display).normalize();
WindowBounds[] bounds = estimateWindowBounds(context, info);
result.put(info.id, Pair.create(info, bounds));
}
@@ -122,7 +126,7 @@
}
WindowMetrics wm = windowContext.getSystemService(WindowManager.class)
- .getCurrentWindowMetrics();
+ .getMaximumWindowMetrics();
Rect insets = new Rect();
normalizeWindowInsets(windowContext, wm.getWindowInsets(), insets);
@@ -268,21 +272,23 @@
* Returns a CachedDisplayInfo initialized for the current display
*/
@TargetApi(Build.VERSION_CODES.S)
- public CachedDisplayInfo getDisplayInfo(Display display) {
- int rotation = display.getRotation();
-
- Point size = new Point();
- display.getRealSize(size);
-
+ public CachedDisplayInfo getDisplayInfo(Context displayContext, Display display) {
+ int rotation = getRotation(displayContext);
Rect cutoutRect = new Rect();
+ Point size = new Point();
if (Utilities.ATLEAST_S) {
- DisplayCutout cutout = display.getCutout();
+ WindowMetrics wm = displayContext.getSystemService(WindowManager.class)
+ .getMaximumWindowMetrics();
+ DisplayCutout cutout = wm.getWindowInsets().getDisplayCutout();
if (cutout != null) {
cutoutRect.set(cutout.getSafeInsetLeft(), cutout.getSafeInsetTop(),
cutout.getSafeInsetRight(), cutout.getSafeInsetBottom());
}
- }
+ size.set(wm.getBounds().right, wm.getBounds().bottom);
+ } else {
+ display.getRealSize(size);
+ }
return new CachedDisplayInfo(getDisplayId(display), size, rotation, cutoutRect);
}
@@ -305,7 +311,9 @@
// Ignore
}
}
- return d == null ? DisplayController.INSTANCE.get(context).getInfo().rotation
- : d.getRotation();
+ if (d == null) {
+ d = context.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
+ }
+ return d.getRotation();
}
}
diff --git a/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt b/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt
new file mode 100644
index 0000000..e598df9
--- /dev/null
+++ b/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt
@@ -0,0 +1,133 @@
+/*
+ * 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.launcher3
+
+import android.content.Context
+import android.graphics.PointF
+import androidx.test.core.app.ApplicationProvider
+import com.android.launcher3.util.DisplayController.Info
+import com.android.launcher3.util.WindowBounds
+import org.junit.Before
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when` as whenever
+
+abstract class DeviceProfileBaseTest {
+
+ protected var context: Context? = null
+ protected var inv: InvariantDeviceProfile? = null
+ protected var info: Info = mock(Info::class.java)
+ protected var windowBounds: WindowBounds? = null
+ protected var isMultiWindowMode: Boolean = false
+ protected var transposeLayoutWithOrientation: Boolean = false
+ protected var useTwoPanels: Boolean = false
+ protected var isGestureMode: Boolean = true
+
+ @Before
+ fun setUp() {
+ context = ApplicationProvider.getApplicationContext()
+ // make sure to reset values
+ useTwoPanels = false
+ isGestureMode = true
+ }
+
+ protected fun newDP(): DeviceProfile = DeviceProfile(
+ context,
+ inv,
+ info,
+ windowBounds,
+ isMultiWindowMode,
+ transposeLayoutWithOrientation,
+ useTwoPanels,
+ isGestureMode
+ )
+
+ protected fun initializeVarsForPhone(isLandscape: Boolean = false) {
+ val (x, y) = if (isLandscape)
+ Pair(3120, 1440)
+ else
+ Pair(1440, 3120)
+
+ windowBounds = WindowBounds(x, y, x, y - 100, 0)
+
+ whenever(info.isTablet(any())).thenReturn(false)
+
+ inv = newScalableInvariantDeviceProfile()
+ }
+
+ protected fun initializeVarsForTablet(isLandscape: Boolean = false) {
+ val (x, y) = if (isLandscape)
+ Pair(2560, 1600)
+ else
+ Pair(1600, 2560)
+
+ windowBounds = WindowBounds(x, y, x, y - 100, 0)
+
+ whenever(info.isTablet(any())).thenReturn(true)
+
+ inv = newScalableInvariantDeviceProfile()
+ }
+
+ /**
+ * A very generic grid, just to make qsb tests work. For real calculations, make sure to use
+ * values that better represent a real grid.
+ */
+ protected fun newScalableInvariantDeviceProfile(): InvariantDeviceProfile =
+ InvariantDeviceProfile().apply {
+ isScalable = true
+ numColumns = 5
+ numRows = 5
+ numShownHotseatIcons = 5
+ numDatabaseHotseatIcons = 6
+ numShrunkenHotseatIcons = 4
+ horizontalMargin = FloatArray(4) { 22f }
+ borderSpaces = listOf(
+ PointF(16f, 16f),
+ PointF(16f, 16f),
+ PointF(16f, 16f),
+ PointF(16f, 16f)
+ ).toTypedArray()
+ allAppsBorderSpaces = listOf(
+ PointF(16f, 16f),
+ PointF(16f, 16f),
+ PointF(16f, 16f),
+ PointF(16f, 16f)
+ ).toTypedArray()
+ hotseatBorderSpaces = FloatArray(4) { 16f }
+ iconSize = FloatArray(4) { 56f }
+ allAppsIconSize = FloatArray(4) { 56f }
+ iconTextSize = FloatArray(4) { 14f }
+ allAppsIconTextSize = FloatArray(4) { 14f }
+ minCellSize = listOf(
+ PointF(64f, 83f),
+ PointF(64f, 83f),
+ PointF(64f, 83f),
+ PointF(64f, 83f)
+ ).toTypedArray()
+ allAppsCellSize = listOf(
+ PointF(64f, 83f),
+ PointF(64f, 83f),
+ PointF(64f, 83f),
+ PointF(64f, 83f)
+ ).toTypedArray()
+ inlineQsb = booleanArrayOf(
+ false,
+ false,
+ false,
+ false
+ )
+ }
+}
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/DeviceProfileTest.kt b/tests/src/com/android/launcher3/DeviceProfileTest.kt
deleted file mode 100644
index d1e91ed..0000000
--- a/tests/src/com/android/launcher3/DeviceProfileTest.kt
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * 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.launcher3
-
-import android.content.Context
-import android.graphics.PointF
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.launcher3.util.DisplayController.Info
-import com.android.launcher3.util.WindowBounds
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mockito.*
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class DeviceProfileTest {
-
- private var context: Context? = null
- private var inv: InvariantDeviceProfile? = null
- private var info: Info = mock(Info::class.java)
- private var windowBounds: WindowBounds? = null
- private var isMultiWindowMode: Boolean = false
- private var transposeLayoutWithOrientation: Boolean = false
- private var useTwoPanels: Boolean = false
- private var isGestureMode: Boolean = true
-
- @Before
- fun setUp() {
- context = ApplicationProvider.getApplicationContext()
- // make sure to reset values
- useTwoPanels = false
- }
-
- @Test
- fun qsbWidth_is_match_parent_for_phones() {
- initializeVarsForPhone()
-
- val dp = DeviceProfile(
- context,
- inv,
- info,
- windowBounds,
- isMultiWindowMode,
- transposeLayoutWithOrientation,
- useTwoPanels,
- isGestureMode
- )
-
- assertThat(dp.isQsbInline).isFalse()
- assertThat(dp.qsbWidth).isEqualTo(0)
- }
-
- @Test
- fun qsbWidth_is_match_parent_for_tablet_portrait() {
- initializeVarsForLargeTablet()
-
- val dp = DeviceProfile(
- context,
- inv,
- info,
- windowBounds,
- isMultiWindowMode,
- transposeLayoutWithOrientation,
- useTwoPanels,
- isGestureMode
- )
-
- assertThat(dp.isQsbInline).isFalse()
- assertThat(dp.qsbWidth).isEqualTo(0)
- }
-
- @Test
- fun qsbWidth_has_size_for_large_tablet_landscape() {
- initializeVarsForLargeTablet(true)
-
- val dp = DeviceProfile(
- context,
- inv,
- info,
- windowBounds,
- isMultiWindowMode,
- transposeLayoutWithOrientation,
- useTwoPanels,
- isGestureMode
- )
-
- if (dp.hotseatQsbHeight > 0) {
- assertThat(dp.isQsbInline).isTrue()
- assertThat(dp.qsbWidth).isGreaterThan(0)
- } else {
- assertThat(dp.isQsbInline).isFalse()
- assertThat(dp.qsbWidth).isEqualTo(0)
- }
- }
-
- /**
- * This test is to make sure that two panels don't inline the QSB as tablets do
- */
- @Test
- fun qsbWidth_is_match_parent_for_small_two_panel_landscape() {
- initializeVarsForSmallTablet(true)
- useTwoPanels = true
-
- val dp = DeviceProfile(
- context,
- inv,
- info,
- windowBounds,
- isMultiWindowMode,
- transposeLayoutWithOrientation,
- useTwoPanels,
- isGestureMode
- )
-
- assertThat(dp.isQsbInline).isFalse()
- assertThat(dp.qsbWidth).isEqualTo(0)
- }
-
- private fun initializeVarsForPhone(isLandscape: Boolean = false) {
- val (x, y) = if (isLandscape)
- Pair(3120, 1440)
- else
- Pair(1440, 3120)
-
- windowBounds = WindowBounds(x, y, x, y - 100, 0)
-
- `when`(info.isTablet(any())).thenReturn(false)
- `when`(info.isLargeTablet(any())).thenReturn(false)
-
- scalableInvariantDeviceProfile()
- }
-
- private fun initializeVarsForSmallTablet(isLandscape: Boolean = false) {
- val (x, y) = if (isLandscape)
- Pair(2560, 1600)
- else
- Pair(1600, 2560)
-
- windowBounds = WindowBounds(x, y, x, y - 100, 0)
-
- `when`(info.isTablet(any())).thenReturn(true)
- `when`(info.isLargeTablet(any())).thenReturn(false)
-
- scalableInvariantDeviceProfile()
- }
-
- private fun initializeVarsForLargeTablet(isLandscape: Boolean = false) {
- val (x, y) = if (isLandscape)
- Pair(2560, 1600)
- else
- Pair(1600, 2560)
-
- windowBounds = WindowBounds(x, y, x, y - 100, 0)
-
- `when`(info.isTablet(any())).thenReturn(true)
- `when`(info.isLargeTablet(any())).thenReturn(true)
-
- scalableInvariantDeviceProfile()
- }
-
- /**
- * A very generic grid, just to make qsb tests work. For real calculations, make sure to use
- * values that better represent a real grid.
- */
- private fun scalableInvariantDeviceProfile() {
- inv = InvariantDeviceProfile().apply {
- isScalable = true
- numColumns = 5
- numRows = 5
- horizontalMargin = FloatArray(4) { 22f }
- borderSpaces = listOf(
- PointF(16f, 16f),
- PointF(16f, 16f),
- PointF(16f, 16f),
- PointF(16f, 16f)
- ).toTypedArray()
- allAppsBorderSpaces = listOf(
- PointF(16f, 16f),
- PointF(16f, 16f),
- PointF(16f, 16f),
- PointF(16f, 16f)
- ).toTypedArray()
- hotseatBorderSpaces = FloatArray(4) { 16f }
- iconSize = FloatArray(4) { 56f }
- allAppsIconSize = FloatArray(4) { 56f }
- iconTextSize = FloatArray(4) { 14f }
- allAppsIconTextSize = FloatArray(4) { 14f }
- minCellSize = listOf(
- PointF(64f, 83f),
- PointF(64f, 83f),
- PointF(64f, 83f),
- PointF(64f, 83f)
- ).toTypedArray()
- allAppsCellSize = listOf(
- PointF(64f, 83f),
- PointF(64f, 83f),
- PointF(64f, 83f),
- PointF(64f, 83f)
- ).toTypedArray()
- }
- }
-}
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/HotseatSizeTest.kt b/tests/src/com/android/launcher3/HotseatSizeTest.kt
new file mode 100644
index 0000000..ca697d7
--- /dev/null
+++ b/tests/src/com/android/launcher3/HotseatSizeTest.kt
@@ -0,0 +1,186 @@
+/*
+ * 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.launcher3
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.InvariantDeviceProfile.TYPE_MULTI_DISPLAY
+import com.android.launcher3.InvariantDeviceProfile.TYPE_PHONE
+import com.android.launcher3.InvariantDeviceProfile.TYPE_TABLET
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito.`when` as whenever
+
+/**
+ * Test for [DeviceProfile]
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class HotseatSizeTest : DeviceProfileBaseTest() {
+
+ @Test
+ fun hotseat_size_is_normal_for_handhelds() {
+ initializeVarsForPhone()
+ inv = newScalableInvariantDeviceProfile().apply {
+ deviceType = TYPE_PHONE
+ }
+
+ val dp = newDP()
+
+ assertThat(dp.isQsbInline).isFalse()
+ assertThat(dp.numShownHotseatIcons).isEqualTo(5)
+ }
+
+ @Test
+ fun hotseat_size_is_max_for_foldables() {
+ initializeVarsForTablet(isLandscape = true)
+ inv = newScalableInvariantDeviceProfile().apply {
+ deviceType = TYPE_MULTI_DISPLAY
+ }
+ useTwoPanels = true
+
+ val dp = newDP()
+
+ assertThat(dp.isQsbInline).isFalse()
+ assertThat(dp.numShownHotseatIcons).isEqualTo(6)
+ }
+
+ @Test
+ fun hotseat_size_is_shrunk_if_needed() {
+ initializeVarsForTablet(isLandscape = true)
+ inv = newScalableInvariantDeviceProfile().apply {
+ deviceType = TYPE_MULTI_DISPLAY
+ inlineQsb = booleanArrayOf(
+ false,
+ false,
+ false,
+ true // two panels landscape
+ )
+ }
+ useTwoPanels = true
+
+ isGestureMode = false
+ val dp = newDP()
+
+ if (dp.hotseatQsbHeight > 0) {
+ assertThat(dp.isQsbInline).isTrue()
+ assertThat(dp.numShownHotseatIcons).isEqualTo(4)
+ } else { // Launcher3 doesn't have QSB height
+ assertThat(dp.isQsbInline).isFalse()
+ assertThat(dp.numShownHotseatIcons).isEqualTo(6)
+ }
+ }
+
+ /**
+ * For consistency, the hotseat should shrink if any orientation on the device type has an
+ * inline qsb
+ */
+ @Test
+ fun hotseat_size_is_shrunk_even_in_portrait() {
+ initializeVarsForTablet()
+ inv = newScalableInvariantDeviceProfile().apply {
+ deviceType = TYPE_MULTI_DISPLAY
+ inlineQsb = booleanArrayOf(
+ false,
+ false,
+ false,
+ true // two panels landscape
+ )
+ }
+ useTwoPanels = true
+
+ isGestureMode = false
+ val dp = newDP()
+
+ if (dp.hotseatQsbHeight > 0) {
+ assertThat(dp.isQsbInline).isFalse()
+ assertThat(dp.numShownHotseatIcons).isEqualTo(4)
+ } else { // Launcher3 doesn't have QSB height
+ assertThat(dp.isQsbInline).isFalse()
+ assertThat(dp.numShownHotseatIcons).isEqualTo(6)
+ }
+ }
+
+ @Test
+ fun hotseat_size_is_default_when_folded() {
+ initializeVarsForPhone()
+ inv = newScalableInvariantDeviceProfile().apply {
+ deviceType = TYPE_MULTI_DISPLAY
+ }
+ useTwoPanels = true
+
+ val dp = newDP()
+
+ assertThat(dp.numShownHotseatIcons).isEqualTo(5)
+ }
+
+ @Test
+ fun hotseat_size_is_shrunk_if_needed_on_tablet() {
+ initializeVarsForTablet(isLandscape = true)
+ inv = newScalableInvariantDeviceProfile().apply {
+ deviceType = TYPE_TABLET
+ inlineQsb = booleanArrayOf(
+ false,
+ true, // landscape
+ false,
+ false
+ )
+ }
+
+ isGestureMode = false
+ val dp = newDP()
+
+ if (dp.hotseatQsbHeight > 0) {
+ assertThat(dp.isQsbInline).isTrue()
+ assertThat(dp.numShownHotseatIcons).isEqualTo(4)
+ } else { // Launcher3 doesn't have QSB height
+ assertThat(dp.isQsbInline).isFalse()
+ assertThat(dp.numShownHotseatIcons).isEqualTo(5)
+ }
+ }
+
+ /**
+ * For consistency, the hotseat should shrink if any orientation on the device type has an
+ * inline qsb
+ */
+ @Test
+ fun hotseat_size_is_shrunk_even_in_portrait_on_tablet() {
+ initializeVarsForTablet()
+ inv = newScalableInvariantDeviceProfile().apply {
+ deviceType = TYPE_TABLET
+ inlineQsb = booleanArrayOf(
+ false,
+ true, // landscape
+ false,
+ false
+ )
+ }
+
+ isGestureMode = false
+ val dp = newDP()
+
+ if (dp.hotseatQsbHeight > 0) {
+ assertThat(dp.isQsbInline).isFalse()
+ assertThat(dp.numShownHotseatIcons).isEqualTo(4)
+ } else { // Launcher3 doesn't have QSB height
+ assertThat(dp.isQsbInline).isFalse()
+ assertThat(dp.numShownHotseatIcons).isEqualTo(5)
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/InlineQsbTest.kt b/tests/src/com/android/launcher3/InlineQsbTest.kt
new file mode 100644
index 0000000..e00dca8
--- /dev/null
+++ b/tests/src/com/android/launcher3/InlineQsbTest.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.launcher3
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Test for [DeviceProfile]
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class InlineQsbTest : DeviceProfileBaseTest() {
+
+ @Test
+ fun qsbWidth_is_match_parent_for_phones() {
+ initializeVarsForPhone()
+
+ val dp = newDP()
+
+ assertThat(dp.isQsbInline).isFalse()
+ assertThat(dp.qsbWidth).isEqualTo(0)
+ }
+
+ @Test
+ fun qsbWidth_is_match_parent_for_tablet_portrait() {
+ initializeVarsForTablet()
+ inv = newScalableInvariantDeviceProfile().apply {
+ inlineQsb = booleanArrayOf(
+ false,
+ true, // landscape
+ false,
+ false
+ )
+ }
+
+ val dp = DeviceProfile(
+ context,
+ inv,
+ info,
+ windowBounds,
+ isMultiWindowMode,
+ transposeLayoutWithOrientation,
+ useTwoPanels,
+ isGestureMode
+ )
+
+ assertThat(dp.isQsbInline).isFalse()
+ assertThat(dp.qsbWidth).isEqualTo(0)
+ }
+
+ @Test
+ fun qsbWidth_has_size_for_tablet_landscape() {
+ initializeVarsForTablet(isLandscape = true)
+ inv = newScalableInvariantDeviceProfile().apply {
+ inlineQsb = booleanArrayOf(
+ false,
+ true, // landscape
+ false,
+ false
+ )
+ }
+
+ val dp = newDP()
+
+ if (dp.hotseatQsbHeight > 0) {
+ assertThat(dp.isQsbInline).isTrue()
+ assertThat(dp.qsbWidth).isGreaterThan(0)
+ } else { // Launcher3 doesn't have QSB height
+ assertThat(dp.isQsbInline).isFalse()
+ assertThat(dp.qsbWidth).isEqualTo(0)
+ }
+ }
+
+ /**
+ * This test is to make sure that a tablet doesn't inline the QSB if the layout doesn't support
+ */
+ @Test
+ fun qsbWidth_is_match_parent_for_tablet_landscape_without_inline() {
+ initializeVarsForTablet(isLandscape = true)
+ useTwoPanels = true
+
+ val dp = newDP()
+
+ assertThat(dp.isQsbInline).isFalse()
+ assertThat(dp.qsbWidth).isEqualTo(0)
+ }
+
+}
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/model/AbstractWorkspaceModelTest.kt b/tests/src/com/android/launcher3/model/AbstractWorkspaceModelTest.kt
new file mode 100644
index 0000000..d26381d
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/AbstractWorkspaceModelTest.kt
@@ -0,0 +1,162 @@
+/*
+ * 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.launcher3.model
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.graphics.Rect
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherSettings
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.util.ContentWriter
+import com.android.launcher3.util.GridOccupancy
+import com.android.launcher3.util.IntArray
+import com.android.launcher3.util.IntSparseArrayMap
+import com.android.launcher3.util.LauncherModelHelper
+import java.util.UUID
+
+/**
+ * Base class for workspace related tests.
+ */
+abstract class AbstractWorkspaceModelTest {
+ companion object {
+ val emptyScreenSpaces = listOf(Rect(0, 0, 5, 5))
+ val fullScreenSpaces = emptyList<Rect>()
+ val nonEmptyScreenSpaces = listOf(Rect(1, 2, 3, 4))
+ }
+
+ protected lateinit var mTargetContext: Context
+ protected lateinit var mIdp: InvariantDeviceProfile
+ protected lateinit var mAppState: LauncherAppState
+ protected lateinit var mModelHelper: LauncherModelHelper
+ protected lateinit var mExistingScreens: IntArray
+ protected lateinit var mNewScreens: IntArray
+ protected lateinit var mScreenOccupancy: IntSparseArrayMap<GridOccupancy>
+
+ open fun setup() {
+ mModelHelper = LauncherModelHelper()
+ mTargetContext = mModelHelper.sandboxContext
+ mIdp = InvariantDeviceProfile.INSTANCE[mTargetContext]
+ mIdp.numRows = 5
+ mIdp.numColumns = mIdp.numRows
+ mAppState = LauncherAppState.getInstance(mTargetContext)
+ mExistingScreens = IntArray()
+ mScreenOccupancy = IntSparseArrayMap()
+ mNewScreens = IntArray()
+ }
+
+ open fun tearDown() {
+ mModelHelper.destroy()
+ }
+
+
+ /**
+ * Sets up workspaces with the given screen IDs with some items and a 2x2 space.
+ */
+ fun setupWorkspaces(screenIdsWithItems: List<Int>) {
+ var nextItemId = 1
+ screenIdsWithItems.forEach { screenId ->
+ nextItemId = setupWorkspace(nextItemId, screenId, nonEmptyScreenSpaces)
+ }
+ }
+
+ /**
+ * Sets up the given workspaces with the given spaces, and fills the remaining space with items.
+ */
+ fun setupWorkspacesWithSpaces(
+ screen0: List<Rect>? = null,
+ screen1: List<Rect>? = null,
+ screen2: List<Rect>? = null,
+ screen3: List<Rect>? = null,
+ ) = listOf(screen0, screen1, screen2, screen3)
+ .let(this::setupWithSpaces)
+
+ private fun setupWithSpaces(workspaceSpaces: List<List<Rect>?>) {
+ var nextItemId = 1
+ workspaceSpaces.forEachIndexed { screenId, spaces ->
+ if (spaces != null) {
+ nextItemId = setupWorkspace(nextItemId, screenId, spaces)
+ }
+ }
+ }
+
+ private fun setupWorkspace(startId: Int, screenId: Int, spaces: List<Rect>): Int {
+ return mModelHelper.executeSimpleTask { dataModel ->
+ writeWorkspaceWithSpaces(dataModel, startId, screenId, spaces)
+ }
+ }
+
+ private fun writeWorkspaceWithSpaces(
+ bgDataModel: BgDataModel,
+ itemStartId: Int,
+ screenId: Int,
+ spaces: List<Rect>,
+ ): Int {
+ var itemId = itemStartId
+ val occupancy = GridOccupancy(mIdp.numColumns, mIdp.numRows)
+ occupancy.markCells(0, 0, mIdp.numColumns, mIdp.numRows, true)
+ spaces.forEach { spaceRect ->
+ occupancy.markCells(spaceRect, false)
+ }
+ mExistingScreens.add(screenId)
+ mScreenOccupancy.append(screenId, occupancy)
+ for (x in 0 until mIdp.numColumns) {
+ for (y in 0 until mIdp.numRows) {
+ if (!occupancy.cells[x][y]) {
+ continue
+ }
+ val info = getExistingItem()
+ info.id = itemId++
+ info.screenId = screenId
+ info.cellX = x
+ info.cellY = y
+ info.container = LauncherSettings.Favorites.CONTAINER_DESKTOP
+ bgDataModel.addItem(mTargetContext, info, false)
+ val writer = ContentWriter(mTargetContext)
+ info.writeToValues(writer)
+ writer.put(LauncherSettings.Favorites._ID, info.id)
+ mTargetContext.contentResolver.insert(
+ LauncherSettings.Favorites.CONTENT_URI,
+ writer.getValues(mTargetContext)
+ )
+ }
+ }
+ return itemId
+ }
+
+ fun getExistingItem() = WorkspaceItemInfo()
+ .apply { intent = Intent().setComponent(ComponentName("a", "b")) }
+
+ fun getNewItem(): WorkspaceItemInfo {
+ val itemPackage = UUID.randomUUID().toString()
+ return WorkspaceItemInfo()
+ .apply { intent = Intent().setComponent(ComponentName(itemPackage, itemPackage)) }
+ }
+}
+
+data class NewItemSpace(
+ val screenId: Int,
+ val cellX: Int,
+ val cellY: Int
+) {
+ fun toIntArray() = intArrayOf(screenId, cellX, cellY)
+
+ companion object {
+ fun fromIntArray(array: kotlin.IntArray) = NewItemSpace(array[0], array[1], array[2])
+ }
+}
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
deleted file mode 100644
index 8a4590a..0000000
--- a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
+++ /dev/null
@@ -1,201 +0,0 @@
-package com.android.launcher3.model;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Rect;
-import android.util.Pair;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.model.BgDataModel.Callbacks;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.util.ContentWriter;
-import com.android.launcher3.util.Executors;
-import com.android.launcher3.util.GridOccupancy;
-import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.IntSparseArrayMap;
-import com.android.launcher3.util.LauncherModelHelper;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Tests for {@link AddWorkspaceItemsTask}
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class AddWorkspaceItemsTaskTest {
-
- private final ComponentName mComponent1 = new ComponentName("a", "b");
- private final ComponentName mComponent2 = new ComponentName("b", "b");
-
- private Context mTargetContext;
- private InvariantDeviceProfile mIdp;
- private LauncherAppState mAppState;
- private LauncherModelHelper mModelHelper;
-
- private IntArray mExistingScreens;
- private IntArray mNewScreens;
- private IntSparseArrayMap<GridOccupancy> mScreenOccupancy;
-
- @Before
- public void setup() {
- mModelHelper = new LauncherModelHelper();
- mTargetContext = mModelHelper.sandboxContext;
- mIdp = InvariantDeviceProfile.INSTANCE.get(mTargetContext);
- mIdp.numColumns = mIdp.numRows = 5;
- mAppState = LauncherAppState.getInstance(mTargetContext);
-
- mExistingScreens = new IntArray();
- mScreenOccupancy = new IntSparseArrayMap<>();
- mNewScreens = new IntArray();
- }
-
- @After
- public void tearDown() {
- mModelHelper.destroy();
- }
-
- private AddWorkspaceItemsTask newTask(ItemInfo... items) {
- List<Pair<ItemInfo, Object>> list = new ArrayList<>();
- for (ItemInfo item : items) {
- list.add(Pair.create(item, null));
- }
- return new AddWorkspaceItemsTask(list);
- }
-
- @Test
- public void testFindSpaceForItem_prefers_second() throws Exception {
- // First screen has only one hole of size 1
- int nextId = setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
-
- // Second screen has 2 holes of sizes 3x2 and 2x3
- setupWorkspaceWithHoles(nextId, 2, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
-
- int[] spaceFound = newTask().findSpaceForItem(
- mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 1, 1);
- assertEquals(1, spaceFound[0]);
- assertTrue(mScreenOccupancy.get(spaceFound[0])
- .isRegionVacant(spaceFound[1], spaceFound[2], 1, 1));
-
- // Find a larger space
- spaceFound = newTask().findSpaceForItem(
- mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 2, 3);
- assertEquals(2, spaceFound[0]);
- assertTrue(mScreenOccupancy.get(spaceFound[0])
- .isRegionVacant(spaceFound[1], spaceFound[2], 2, 3));
- }
-
- @Test
- public void testFindSpaceForItem_adds_new_screen() throws Exception {
- // First screen has 2 holes of sizes 3x2 and 2x3
- setupWorkspaceWithHoles(1, 1, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
-
- IntArray oldScreens = mExistingScreens.clone();
- int[] spaceFound = newTask().findSpaceForItem(
- mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 3, 3);
- assertFalse(oldScreens.contains(spaceFound[0]));
- assertTrue(mNewScreens.contains(spaceFound[0]));
- }
-
- @Test
- public void testAddItem_existing_item_ignored() throws Exception {
- WorkspaceItemInfo info = new WorkspaceItemInfo();
- info.intent = new Intent().setComponent(mComponent1);
-
- // Setup a screen with a hole
- setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
-
- // Nothing was added
- assertTrue(mModelHelper.executeTaskForTest(newTask(info)).isEmpty());
- }
-
- @Test
- public void testAddItem_some_items_added() throws Exception {
- Callbacks callbacks = mock(Callbacks.class);
- Executors.MAIN_EXECUTOR.submit(() -> mModelHelper.getModel().addCallbacks(callbacks)).get();
-
- WorkspaceItemInfo info = new WorkspaceItemInfo();
- info.intent = new Intent().setComponent(mComponent1);
-
- WorkspaceItemInfo info2 = new WorkspaceItemInfo();
- info2.intent = new Intent().setComponent(mComponent2);
-
- // Setup a screen with a hole
- setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
-
- mModelHelper.executeTaskForTest(newTask(info, info2)).get(0).run();
- ArgumentCaptor<ArrayList> notAnimated = ArgumentCaptor.forClass(ArrayList.class);
- ArgumentCaptor<ArrayList> animated = ArgumentCaptor.forClass(ArrayList.class);
-
- // only info2 should be added because info was already added to the workspace
- // in setupWorkspaceWithHoles()
- verify(callbacks).bindAppsAdded(any(IntArray.class), notAnimated.capture(),
- animated.capture());
- assertTrue(notAnimated.getValue().isEmpty());
-
- assertEquals(1, animated.getValue().size());
- assertTrue(animated.getValue().contains(info2));
- }
-
- private int setupWorkspaceWithHoles(int startId, int screenId, Rect... holes) throws Exception {
- return mModelHelper.executeSimpleTask(
- model -> writeWorkspaceWithHoles(model, startId, screenId, holes));
- }
-
- private int writeWorkspaceWithHoles(
- BgDataModel bgDataModel, int startId, int screenId, Rect... holes) {
- GridOccupancy occupancy = new GridOccupancy(mIdp.numColumns, mIdp.numRows);
- occupancy.markCells(0, 0, mIdp.numColumns, mIdp.numRows, true);
- for (Rect r : holes) {
- occupancy.markCells(r, false);
- }
-
- mExistingScreens.add(screenId);
- mScreenOccupancy.append(screenId, occupancy);
-
- for (int x = 0; x < mIdp.numColumns; x++) {
- for (int y = 0; y < mIdp.numRows; y++) {
- if (!occupancy.cells[x][y]) {
- continue;
- }
-
- WorkspaceItemInfo info = new WorkspaceItemInfo();
- info.intent = new Intent().setComponent(mComponent1);
- info.id = startId++;
- info.screenId = screenId;
- info.cellX = x;
- info.cellY = y;
- info.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
- bgDataModel.addItem(mTargetContext, info, false);
-
- ContentWriter writer = new ContentWriter(mTargetContext);
- info.writeToValues(writer);
- writer.put(Favorites._ID, info.id);
- mTargetContext.getContentResolver().insert(Favorites.CONTENT_URI,
- writer.getValues(mTargetContext));
- }
- }
- return startId;
- }
-}
diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
new file mode 100644
index 0000000..65d938b
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
@@ -0,0 +1,245 @@
+/*
+ * 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.launcher3.model
+
+import android.util.Pair
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.util.Executors
+import com.android.launcher3.util.IntArray
+import com.android.launcher3.util.same
+import com.android.launcher3.util.eq
+import com.android.launcher3.util.any
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.times
+import org.mockito.Mockito.`when` as whenever
+
+/**
+ * Tests for [AddWorkspaceItemsTask]
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() {
+
+ @Captor
+ private lateinit var mAnimatedItemArgumentCaptor: ArgumentCaptor<ArrayList<ItemInfo>>
+
+ @Captor
+ private lateinit var mNotAnimatedItemArgumentCaptor: ArgumentCaptor<ArrayList<ItemInfo>>
+
+ @Mock
+ private lateinit var mDataModelCallbacks: BgDataModel.Callbacks
+
+ @Mock
+ private lateinit var mWorkspaceItemSpaceFinder: WorkspaceItemSpaceFinder
+
+
+ @Before
+ override fun setup() {
+ super.setup()
+ MockitoAnnotations.initMocks(this)
+ Executors.MAIN_EXECUTOR.submit { mModelHelper.model.addCallbacks(mDataModelCallbacks) }
+ .get()
+ }
+
+ @After
+ override fun tearDown() {
+ super.tearDown()
+ }
+
+ @Test
+ fun givenNewItemAndNonEmptyPages_whenExecuteTask_thenAddNewItem() {
+ val itemToAdd = getNewItem()
+ val nonEmptyScreenIds = listOf(0, 1, 2)
+ givenNewItemSpaces(NewItemSpace(1, 2, 2))
+
+ val addedItems = testAddItems(nonEmptyScreenIds, itemToAdd)
+
+ assertThat(addedItems.size).isEqualTo(1)
+ assertThat(addedItems.first().itemInfo.screenId).isEqualTo(1)
+ assertThat(addedItems.first().isAnimated).isTrue()
+ verifyItemSpaceFinderCall(nonEmptyScreenIds, numberOfExpectedCall = 1)
+ }
+
+ @Test
+ fun givenNewAndExistingItems_whenExecuteTask_thenOnlyAddNewItem() {
+ val itemsToAdd = arrayOf(
+ getNewItem(),
+ getExistingItem()
+ )
+ givenNewItemSpaces(NewItemSpace(1, 0, 0))
+ val nonEmptyScreenIds = listOf(0)
+
+ val addedItems = testAddItems(nonEmptyScreenIds, *itemsToAdd)
+
+ assertThat(addedItems.size).isEqualTo(1)
+ assertThat(addedItems.first().itemInfo.screenId).isEqualTo(1)
+ assertThat(addedItems.first().isAnimated).isTrue()
+ verifyItemSpaceFinderCall(nonEmptyScreenIds, numberOfExpectedCall = 1)
+ }
+
+ @Test
+ fun givenOnlyExistingItem_whenExecuteTask_thenDoNotAddItem() {
+ val itemToAdd = getExistingItem()
+ givenNewItemSpaces(NewItemSpace(1, 0, 0))
+ val nonEmptyScreenIds = listOf(0)
+
+ val addedItems = testAddItems(nonEmptyScreenIds, itemToAdd)
+
+ assertThat(addedItems.size).isEqualTo(0)
+ verifyZeroInteractions(mWorkspaceItemSpaceFinder, mDataModelCallbacks)
+ }
+
+ @Test
+ fun givenNonSequentialScreenIds_whenExecuteTask_thenReturnNewScreenId() {
+ val itemToAdd = getNewItem()
+ givenNewItemSpaces(NewItemSpace(2, 1, 3))
+ val nonEmptyScreenIds = listOf(0, 2, 3)
+
+ val addedItems = testAddItems(nonEmptyScreenIds, itemToAdd)
+
+ assertThat(addedItems.size).isEqualTo(1)
+ assertThat(addedItems.first().itemInfo.screenId).isEqualTo(2)
+ assertThat(addedItems.first().isAnimated).isTrue()
+ verifyItemSpaceFinderCall(nonEmptyScreenIds, numberOfExpectedCall = 1)
+ }
+
+ @Test
+ fun givenMultipleItems_whenExecuteTask_thenAddThem() {
+ val itemsToAdd = arrayOf(
+ getNewItem(),
+ getExistingItem(),
+ getNewItem(),
+ getNewItem(),
+ getExistingItem(),
+ )
+ givenNewItemSpaces(
+ NewItemSpace(1, 3, 3),
+ NewItemSpace(2, 0, 0),
+ NewItemSpace(2, 0, 1),
+ )
+ val nonEmptyScreenIds = listOf(0, 1)
+
+ val addedItems = testAddItems(nonEmptyScreenIds, *itemsToAdd)
+
+ // Only the new items should be added
+ assertThat(addedItems.size).isEqualTo(3)
+
+ // Items that are added to the first screen should not be animated
+ val itemsAddedToFirstScreen = addedItems.filter { it.itemInfo.screenId == 1 }
+ assertThat(itemsAddedToFirstScreen.size).isEqualTo(1)
+ assertThat(itemsAddedToFirstScreen.first().isAnimated).isFalse()
+
+ // Items that are added to the second screen should be animated
+ val itemsAddedToSecondScreen = addedItems.filter { it.itemInfo.screenId == 2 }
+ assertThat(itemsAddedToSecondScreen.size).isEqualTo(2)
+ itemsAddedToSecondScreen.forEach {
+ assertThat(it.isAnimated).isTrue()
+ }
+ verifyItemSpaceFinderCall(nonEmptyScreenIds, numberOfExpectedCall = 3)
+ }
+
+ /**
+ * Sets up the item space data that will be returned from WorkspaceItemSpaceFinder.
+ */
+ private fun givenNewItemSpaces(vararg newItemSpaces: NewItemSpace) {
+ val spaceStack = newItemSpaces.toMutableList()
+ whenever(
+ mWorkspaceItemSpaceFinder.findSpaceForItem(
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ any()
+ )
+ )
+ .then { spaceStack.removeFirst().toIntArray() }
+ }
+
+ /**
+ * Verifies if WorkspaceItemSpaceFinder was called with proper arguments and how many times was
+ * it called.
+ */
+ private fun verifyItemSpaceFinderCall(
+ nonEmptyScreenIds: List<Int>,
+ numberOfExpectedCall: Int
+ ) {
+ verify(mWorkspaceItemSpaceFinder, times(numberOfExpectedCall))
+ .findSpaceForItem(
+ same(mAppState), same(mModelHelper.bgDataModel),
+ eq(IntArray.wrap(*nonEmptyScreenIds.toIntArray())), eq(IntArray()), eq(1), eq(1)
+ )
+ }
+
+ /**
+ * Sets up the workspaces with items, executes the task, collects the added items from the
+ * model callback then returns it.
+ */
+ private fun testAddItems(
+ nonEmptyScreenIds: List<Int>,
+ vararg itemsToAdd: WorkspaceItemInfo
+ ): List<AddedItem> {
+ setupWorkspaces(nonEmptyScreenIds)
+ val task = newTask(*itemsToAdd)
+ var updateCount = 0
+ mModelHelper.executeTaskForTest(task)
+ .forEach {
+ updateCount++
+ it.run()
+ }
+
+ val addedItems = mutableListOf<AddedItem>()
+ if (updateCount > 0) {
+ verify(mDataModelCallbacks).bindAppsAdded(
+ any(),
+ mNotAnimatedItemArgumentCaptor.capture(), mAnimatedItemArgumentCaptor.capture()
+ )
+ addedItems.addAll(mAnimatedItemArgumentCaptor.value.map { AddedItem(it, true) })
+ addedItems.addAll(mNotAnimatedItemArgumentCaptor.value.map { AddedItem(it, false) })
+
+ }
+
+ return addedItems
+ }
+
+ /**
+ * Creates the task with the given items and replaces the WorkspaceItemSpaceFinder dependency
+ * with a mock.
+ */
+ private fun newTask(vararg items: ItemInfo): AddWorkspaceItemsTask =
+ items.map { Pair.create(it, Any()) }
+ .toMutableList()
+ .let { AddWorkspaceItemsTask(it, mWorkspaceItemSpaceFinder) }
+}
+
+private data class AddedItem(
+ val itemInfo: ItemInfo,
+ val isAnimated: Boolean
+)
diff --git a/tests/src/com/android/launcher3/model/WorkspaceItemSpaceFinderTest.kt b/tests/src/com/android/launcher3/model/WorkspaceItemSpaceFinderTest.kt
new file mode 100644
index 0000000..bfb1ac6
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/WorkspaceItemSpaceFinderTest.kt
@@ -0,0 +1,171 @@
+/*
+ * 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.launcher3.model
+
+import android.graphics.Rect
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for [WorkspaceItemSpaceFinder]
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WorkspaceItemSpaceFinderTest : AbstractWorkspaceModelTest() {
+
+ private val mItemSpaceFinder = WorkspaceItemSpaceFinder()
+
+ @Before
+ override fun setup() {
+ super.setup()
+ }
+
+ @After
+ override fun tearDown() {
+ super.tearDown()
+ }
+
+ private fun findSpace(spanX: Int, spanY: Int): NewItemSpace =
+ mItemSpaceFinder.findSpaceForItem(
+ mAppState, mModelHelper.bgDataModel,
+ mExistingScreens, mNewScreens, spanX, spanY
+ )
+ .let { NewItemSpace.fromIntArray(it) }
+
+ private fun assertRegionVacant(newItemSpace: NewItemSpace, spanX: Int, spanY: Int) {
+ assertThat(
+ mScreenOccupancy[newItemSpace.screenId]
+ .isRegionVacant(newItemSpace.cellX, newItemSpace.cellY, spanX, spanY)
+ ).isTrue()
+ }
+
+ @Test
+ fun justEnoughSpaceOnFirstScreen_whenFindSpaceForItem_thenReturnFirstScreenId() {
+ setupWorkspacesWithSpaces(
+ // 3x2 space on screen 0, but it should be skipped
+ screen0 = listOf(Rect(2, 0, 5, 2)),
+ screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 space
+ // 2 spaces of sizes 3x2 and 2x3
+ screen2 = listOf(Rect(2, 0, 5, 2), Rect(0, 2, 2, 5)),
+ )
+
+ val spaceFound = findSpace(1, 1)
+
+ assertThat(spaceFound.screenId).isEqualTo(1)
+ assertRegionVacant(spaceFound, 1, 1)
+ }
+
+ @Test
+ fun notEnoughSpaceOnFirstScreen_whenFindSpaceForItem_thenReturnSecondScreenId() {
+ setupWorkspacesWithSpaces(
+ // 3x2 space on screen 0, but it should be skipped
+ screen0 = listOf(Rect(2, 0, 5, 2)),
+ screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 space
+ // 2 spaces of sizes 3x2 and 2x3
+ screen2 = listOf(Rect(2, 0, 5, 2), Rect(0, 2, 2, 5)),
+ )
+
+ // Find a larger space
+ val spaceFound = findSpace(2, 3)
+
+ assertThat(spaceFound.screenId).isEqualTo(2)
+ assertRegionVacant(spaceFound, 2, 3)
+ }
+
+ @Test
+ fun notEnoughSpaceOnExistingScreens_returnNewScreenId() {
+ setupWorkspacesWithSpaces(
+ // 3x2 space on screen 0, but it should be skipped
+ screen0 = listOf(Rect(2, 0, 5, 2)),
+ // 2 spaces of sizes 3x2 and 2x3
+ screen1 = listOf(Rect(2, 0, 5, 2), Rect(0, 2, 2, 5)),
+ // 2 spaces of sizes 1x2 and 2x2
+ screen2 = listOf(Rect(1, 0, 2, 2), Rect(3, 2, 5, 4)),
+ )
+
+ val oldScreens = mExistingScreens.clone()
+ val spaceFound = findSpace(3, 3)
+
+ assertThat(oldScreens.contains(spaceFound.screenId)).isFalse()
+ assertThat(mNewScreens.contains(spaceFound.screenId)).isTrue()
+ }
+
+ @Test
+ fun firstScreenIsEmptyButSecondIsNotEmpty_returnSecondScreenId() {
+ setupWorkspacesWithSpaces(
+ // 3x2 space on screen 0, but it should be skipped
+ screen0 = listOf(Rect(2, 0, 5, 2)),
+ // empty screens are skipped
+ screen2 = listOf(Rect(2, 0, 5, 2)), // 3x2 space
+ )
+
+ val spaceFound = findSpace(2, 1)
+
+ assertThat(spaceFound.screenId).isEqualTo(2)
+ assertRegionVacant(spaceFound, 2, 1)
+ }
+
+ @Test
+ fun twoEmptyMiddleScreens_returnThirdScreen() {
+ setupWorkspacesWithSpaces(
+ // 3x2 space on screen 0, but it should be skipped
+ screen0 = listOf(Rect(2, 0, 5, 2)),
+ // empty screens are skipped
+ screen3 = listOf(Rect(1, 1, 4, 4)), // 3x3 space
+ )
+
+ val spaceFound = findSpace(2, 3)
+
+ assertThat(spaceFound.screenId).isEqualTo(3)
+ assertRegionVacant(spaceFound, 2, 3)
+ }
+
+ @Test
+ fun allExistingPagesAreFull_returnNewScreenId() {
+ setupWorkspacesWithSpaces(
+ // 3x2 space on screen 0, but it should be skipped
+ screen0 = listOf(Rect(2, 0, 5, 2)),
+ screen1 = fullScreenSpaces,
+ screen2 = fullScreenSpaces,
+ )
+
+ val spaceFound = findSpace(2, 3)
+
+ assertThat(spaceFound.screenId).isEqualTo(3)
+ assertThat(mNewScreens.contains(spaceFound.screenId)).isTrue()
+ }
+
+ @Test
+ fun firstTwoPagesAreFull_and_ThirdPageIsEmpty_returnThirdPage() {
+ setupWorkspacesWithSpaces(
+ // 3x2 space on screen 0, but it should be skipped
+ screen0 = listOf(Rect(2, 0, 5, 2)),
+ screen1 = fullScreenSpaces, // full screens are skipped
+ screen2 = fullScreenSpaces, // full screens are skipped
+ screen3 = emptyScreenSpaces
+ )
+
+ val spaceFound = findSpace(3, 1)
+
+ assertThat(spaceFound.screenId).isEqualTo(3)
+ assertRegionVacant(spaceFound, 3, 1)
+ }
+}
diff --git a/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java
index d0daefc..f646b50 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java
@@ -255,24 +255,24 @@
Workspace workspace = mLauncher.getWorkspace();
workspace.dragIcon(workspace.getWorkspaceAppIcon("Play Store"), 2);
- workspace.dragIcon(workspace.getHotseatAppIcon("Camera"), 1);
+ workspace.dragIcon(workspace.getHotseatAppIcon("Chrome"), 1);
executeOnLauncher(launcher -> {
assertPagesExist(launcher, 0, 1, 2, 3);
assertItemsOnPage(launcher, 0, "Maps");
assertPageEmpty(launcher, 1);
assertItemsOnPage(launcher, 2, "Play Store");
- assertItemsOnPage(launcher, 3, "Camera");
+ assertItemsOnPage(launcher, 3, "Chrome");
});
- workspace.dragIcon(workspace.getWorkspaceAppIcon("Camera"), -1);
+ workspace.dragIcon(workspace.getWorkspaceAppIcon("Chrome"), -1);
workspace.flingForward();
workspace.dragIcon(workspace.getWorkspaceAppIcon("Play Store"), -2);
executeOnLauncher(launcher -> {
assertPagesExist(launcher, 0, 1);
assertItemsOnPage(launcher, 0, "Play Store", "Maps");
- assertItemsOnPage(launcher, 1, "Camera");
+ assertItemsOnPage(launcher, 1, "Chrome");
});
}
diff --git a/tests/src/com/android/launcher3/util/KotlinMockitoHelpers.kt b/tests/src/com/android/launcher3/util/KotlinMockitoHelpers.kt
new file mode 100644
index 0000000..57db13a
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/KotlinMockitoHelpers.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.launcher3.util
+
+/**
+ * Kotlin versions of popular mockito methods that can return null in situations when Kotlin expects
+ * a non-null value. Kotlin will throw an IllegalStateException when this takes place ("x must not
+ * be null"). To fix this, we can use methods that modify the return type to be nullable. This
+ * causes Kotlin to skip the null checks.
+ */
+
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito
+
+/**
+ * Returns Mockito.eq() as nullable type to avoid java.lang.IllegalStateException when
+ * null is returned.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+fun <T> eq(obj: T): T = Mockito.eq<T>(obj)
+
+/**
+ * Returns Mockito.same() as nullable type to avoid java.lang.IllegalStateException when
+ * null is returned.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+fun <T> same(obj: T): T = Mockito.same<T>(obj)
+
+/**
+ * Returns Mockito.any() as nullable type to avoid java.lang.IllegalStateException when
+ * null is returned.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
+inline fun <reified T> any(): T = any(T::class.java)
+
+/**
+ * Kotlin type-inferred version of Mockito.nullable()
+ */
+inline fun <reified T> nullable(): T? = Mockito.nullable(T::class.java)
+
+/**
+ * Returns ArgumentCaptor.capture() as nullable type to avoid java.lang.IllegalStateException
+ * when null is returned.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+
+/**
+ * Helper function for creating an argumentCaptor in kotlin.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+inline fun <reified T : Any> argumentCaptor(): ArgumentCaptor<T> =
+ ArgumentCaptor.forClass(T::class.java)
+
+/**
+ * Helper function for creating new mocks, without the need to pass in a [Class] instance.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+inline fun <reified T : Any> mock(): T = Mockito.mock(T::class.java)
+
+/**
+ * A kotlin implemented wrapper of [ArgumentCaptor] which prevents the following exception when
+ * kotlin tests are mocking kotlin objects and the methods take non-null parameters:
+ *
+ * java.lang.NullPointerException: capture() must not be null
+ */
+class KotlinArgumentCaptor<T> constructor(clazz: Class<T>) {
+ private val wrapped: ArgumentCaptor<T> = ArgumentCaptor.forClass(clazz)
+ fun capture(): T = wrapped.capture()
+ val value: T
+ get() = wrapped.value
+}
+
+/**
+ * Helper function for creating an argumentCaptor in kotlin.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+inline fun <reified T : Any> kotlinArgumentCaptor(): KotlinArgumentCaptor<T> =
+ KotlinArgumentCaptor(T::class.java)
+
+/**
+ * Helper function for creating and using a single-use ArgumentCaptor in kotlin.
+ *
+ * val captor = argumentCaptor<Foo>()
+ * verify(...).someMethod(captor.capture())
+ * val captured = captor.value
+ *
+ * becomes:
+ *
+ * val captured = withArgCaptor<Foo> { verify(...).someMethod(capture()) }
+ *
+ * NOTE: this uses the KotlinArgumentCaptor to avoid the NullPointerException.
+ */
+inline fun <reified T : Any> withArgCaptor(block: KotlinArgumentCaptor<T>.() -> Unit): T =
+ kotlinArgumentCaptor<T>().apply { block() }.value
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 8e0eb7b..f270cd5 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -877,10 +877,9 @@
}
/**
+ * @return the Workspace object.
* @deprecated use goHome().
* Presses nav bar home button.
- *
- * @return the Workspace object.
*/
@Deprecated
public Workspace pressHome() {
@@ -1653,6 +1652,13 @@
Point getRealDisplaySize() {
final Point size = new Point();
getContext().getSystemService(WindowManager.class).getDefaultDisplay().getRealSize(size);
+ final Rect winMetricsHeight = getContext().getSystemService(WindowManager.class)
+ .getMaximumWindowMetrics()
+ .getBounds();
+ final Point winMetricsSize = new Point(winMetricsHeight.width(), winMetricsHeight.height());
+ if (!winMetricsSize.equals(size)) {
+ fail("Display size mismatch: " + size + " vs " + winMetricsSize);
+ }
return size;
}