Merge "Make hotseat fit one line on tablets"
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 167c6b2..e215db9 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -33,6 +33,7 @@
<dimen name="dynamic_grid_hotseat_top_padding">8dp</dimen>
<dimen name="dynamic_grid_hotseat_bottom_padding">2dp</dimen>
<dimen name="dynamic_grid_hotseat_bottom_tall_padding">0dp</dimen>
+ <dimen name="inline_qsb_bottom_margin">0dp</dimen>
<!-- Qsb -->
<!-- Used for adjusting the position of QSB when placed in hotseat. This is a ratio and a higher
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index a2324ac..bb03b91 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -61,6 +61,7 @@
public final boolean isPhone;
public final boolean transposeLayoutWithOrientation;
public final boolean isTwoPanels;
+ public final boolean isQsbInline;
// Device properties in current orientation
public final boolean isLandscape;
@@ -165,6 +166,7 @@
public final float qsbBottomMarginOriginalPx;
public int qsbBottomMarginPx;
+ public int qsbWidth; // only used when isQsbInline
// All apps
public Point allAppsBorderSpacePx;
@@ -247,6 +249,7 @@
isPhone = !isTablet;
isTwoPanels = isTablet && useTwoPanels;
+
aspectRatio = ((float) Math.max(widthPx, heightPx)) / Math.min(widthPx, heightPx);
boolean isTallDevice = Float.compare(aspectRatio, TALL_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0;
mQsbCenterFactor = context.getResources().getFloat(R.dimen.qsb_center_factor);
@@ -271,7 +274,6 @@
}
}
- hotseatQsbHeight = res.getDimensionPixelSize(R.dimen.qsb_widget_height);
isTaskbarPresent = isTablet && ApiWrapper.TASKBAR_DRAWN_IN_PROCESS
&& FeatureFlags.ENABLE_TASKBAR.get();
if (isTaskbarPresent) {
@@ -334,6 +336,8 @@
workspaceCellPaddingXPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_padding_x);
+ hotseatQsbHeight = res.getDimensionPixelSize(R.dimen.qsb_widget_height);
+ isQsbInline = isTablet && isLandscape && !isTwoPanels && hotseatQsbHeight > 0;
numShownHotseatIcons =
isTwoPanels ? inv.numDatabaseHotseatIcons : inv.numShownHotseatIcons;
numShownAllAppsColumns =
@@ -341,10 +345,17 @@
hotseatBarSizeExtraSpacePx = 0;
hotseatBarTopPaddingPx =
res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_top_padding);
- hotseatBarBottomPaddingPx = (isTallDevice ? res.getDimensionPixelSize(
- R.dimen.dynamic_grid_hotseat_bottom_tall_padding)
- : res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_non_tall_padding))
- + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding);
+ if (isQsbInline) {
+ hotseatBarBottomPaddingPx = res.getDimensionPixelSize(R.dimen.inline_qsb_bottom_margin);
+ qsbWidth = calculateQsbWidth();
+ } else {
+ hotseatBarBottomPaddingPx = (isTallDevice ? res.getDimensionPixelSize(
+ R.dimen.dynamic_grid_hotseat_bottom_tall_padding)
+ : res.getDimensionPixelSize(
+ R.dimen.dynamic_grid_hotseat_bottom_non_tall_padding))
+ + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding);
+ qsbWidth = 0;
+ }
hotseatBarSidePaddingEndPx =
res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_side_padding);
// Add a bit of space between nav bar and hotseat in vertical bar layout.
@@ -473,6 +484,14 @@
new DotRenderer(allAppsIconSizePx, dotPath, DEFAULT_DOT_SIZE);
}
+ private int calculateQsbWidth() {
+ return cellWidthPx * inv.numColumns
+ + cellLayoutBorderSpacePx.x * (inv.numColumns - 1)
+ - (cellWidthPx - iconSizePx) // left and right cell space
+ - iconSizePx * numShownHotseatIcons
+ - hotseatBorderSpace * numShownHotseatIcons;
+ }
+
private int getHorizontalMarginPx(InvariantDeviceProfile idp, Resources res) {
if (isVerticalBarLayout()) {
return 0;
@@ -723,6 +742,11 @@
// Hotseat
hotseatBorderSpace = pxFromDp(inv.hotseatBorderSpaces[mTypeIndex], mMetrics, scale);
+ if (isQsbInline) {
+ qsbWidth = calculateQsbWidth();
+ } else {
+ qsbWidth = 0;
+ }
updateHotseatIconSize(iconSizePx);
if (!isVerticalLayout) {
@@ -901,15 +925,24 @@
} else if (isTaskbarPresent) {
int hotseatHeight = workspacePadding.bottom;
int taskbarOffset = getTaskbarOffsetY();
+ int additionalLeftSpace = 0;
+
+ // Center the QSB with hotseat and push icons to the right
+ if (isQsbInline) {
+ additionalLeftSpace = qsbWidth + hotseatBorderSpace;
+ }
+
int hotseatTopDiff = hotseatHeight - taskbarOffset;
int endOffset = ApiWrapper.getHotseatEndOffset(context);
int requiredWidth = iconSizePx * numShownHotseatIcons
- + hotseatBorderSpace * (numShownHotseatIcons - 1);
+ + hotseatBorderSpace * (numShownHotseatIcons - 1)
+ + additionalLeftSpace;
int hotseatSize = Math.min(requiredWidth, availableWidthPx - endOffset);
int sideSpacing = (availableWidthPx - hotseatSize) / 2;
- mHotseatPadding.set(sideSpacing, hotseatTopDiff, sideSpacing, taskbarOffset);
+ mHotseatPadding.set(sideSpacing + additionalLeftSpace, hotseatTopDiff, sideSpacing,
+ taskbarOffset);
if (endOffset > sideSpacing) {
int diff = Utilities.isRtl(context.getResources())
@@ -942,6 +975,10 @@
* Returns the number of pixels the QSB is translated from the bottom of the screen.
*/
public int getQsbOffsetY() {
+ if (isQsbInline) {
+ return hotseatBarBottomPaddingPx;
+ }
+
int freeSpace = isTaskbarPresent
? workspacePadding.bottom
: hotseatBarSizePx - hotseatCellHeightPx - hotseatQsbHeight;
@@ -959,7 +996,11 @@
* Returns the number of pixels the taskbar is translated from the bottom of the screen.
*/
public int getTaskbarOffsetY() {
- return (getQsbOffsetY() - taskbarSize) / 2;
+ if (isQsbInline) {
+ return getQsbOffsetY() + (Math.abs(hotseatQsbHeight - iconSizePx) / 2);
+ } else {
+ return (getQsbOffsetY() - taskbarSize) / 2;
+ }
}
/**
@@ -1122,6 +1163,8 @@
writer.println(prefix + pxToDpStr("hotseatBarSidePaddingEndPx",
hotseatBarSidePaddingEndPx));
writer.println(prefix + "\tnumShownHotseatIcons: " + numShownHotseatIcons);
+ writer.println(prefix + "\tisQsbInline: " + isQsbInline);
+ writer.println(prefix + pxToDpStr("qsbWidth", qsbWidth));
writer.println(prefix + "\tisTaskbarPresent:" + isTaskbarPresent);
writer.println(prefix + "\tisTaskbarPresentInApps:" + isTaskbarPresentInApps);
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index ffe3816..cb0cc11 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -173,7 +173,10 @@
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int width = getShortcutsAndWidgets().getMeasuredWidth();
+ int width = mActivity.getDeviceProfile().isQsbInline
+ ? mActivity.getDeviceProfile().qsbWidth
+ : getShortcutsAndWidgets().getMeasuredWidth();
+
mQsb.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(mQsbHeight, MeasureSpec.EXACTLY));
}
@@ -183,7 +186,13 @@
super.onLayout(changed, l, t, r, b);
int qsbWidth = mQsb.getMeasuredWidth();
- int left = (r - l - qsbWidth) / 2;
+ int left;
+ if (mActivity.getDeviceProfile().isQsbInline) {
+ int qsbSpace = mActivity.getDeviceProfile().hotseatBorderSpace;
+ left = l + getPaddingLeft() - qsbWidth - qsbSpace;
+ } else {
+ left = (r - l - qsbWidth) / 2;
+ }
int right = left + qsbWidth;
int bottom = b - t - mActivity.getDeviceProfile().getQsbOffsetY();
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index fec1d68..5583eae 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -19,6 +19,7 @@
import static android.view.MotionEvent.ACTION_DOWN;
import static com.android.launcher3.CellLayout.FOLDER;
+import static com.android.launcher3.CellLayout.HOTSEAT;
import static com.android.launcher3.CellLayout.WORKSPACE;
import android.app.WallpaperManager;
@@ -146,7 +147,8 @@
// No need to add padding when cell layout border spacing is present.
boolean noPaddingX =
(dp.cellLayoutBorderSpacePx.x > 0 && mContainerType == WORKSPACE)
- || (dp.folderCellLayoutBorderSpacePx.x > 0 && mContainerType == FOLDER);
+ || (dp.folderCellLayoutBorderSpacePx.x > 0 && mContainerType == FOLDER)
+ || (dp.hotseatBorderSpace > 0 && mContainerType == HOTSEAT);
int cellPaddingX = noPaddingX
? 0
: mContainerType == WORKSPACE
@@ -251,7 +253,7 @@
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
// While the folder is open, the position of the icon cannot change.
lp.canReorder = false;
- if (mContainerType == CellLayout.HOTSEAT) {
+ if (mContainerType == HOTSEAT) {
CellLayout cl = (CellLayout) getParent();
cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
}
@@ -260,7 +262,7 @@
@Override
public void clearFolderLeaveBehind(FolderIcon child) {
((CellLayout.LayoutParams) child.getLayoutParams()).canReorder = true;
- if (mContainerType == CellLayout.HOTSEAT) {
+ if (mContainerType == HOTSEAT) {
CellLayout cl = (CellLayout) getParent();
cl.clearFolderLeaveBehind();
}
diff --git a/tests/Android.bp b/tests/Android.bp
index 8def20f..f3e0500 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -23,7 +23,10 @@
// Source code used for test
filegroup {
name: "launcher-tests-src",
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt"
+ ],
}
// Source code used for oop test helpers
diff --git a/tests/src/com/android/launcher3/DeviceProfileTest.kt b/tests/src/com/android/launcher3/DeviceProfileTest.kt
new file mode 100644
index 0000000..6c99a21
--- /dev/null
+++ b/tests/src/com/android/launcher3/DeviceProfileTest.kt
@@ -0,0 +1,192 @@
+/*
+ * 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
+
+ @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
+ )
+
+ assertThat(dp.isQsbInline).isFalse()
+ assertThat(dp.qsbWidth).isEqualTo(0)
+ }
+
+ @Test
+ fun qsbWidth_is_match_parent_for_tablet_portrait() {
+ initializeVarsForTablet()
+
+ val dp = DeviceProfile(
+ context,
+ inv,
+ info,
+ windowBounds,
+ isMultiWindowMode,
+ transposeLayoutWithOrientation,
+ useTwoPanels
+ )
+
+ assertThat(dp.isQsbInline).isFalse()
+ assertThat(dp.qsbWidth).isEqualTo(0)
+ }
+
+ @Test
+ fun qsbWidth_has_size_for_tablet_landscape() {
+ initializeVarsForTablet(true)
+
+ val dp = DeviceProfile(
+ context,
+ inv,
+ info,
+ windowBounds,
+ isMultiWindowMode,
+ transposeLayoutWithOrientation,
+ useTwoPanels
+ )
+
+ 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_two_panel_landscape() {
+ initializeVarsForTablet(true)
+ useTwoPanels = true
+
+ val dp = DeviceProfile(
+ context,
+ inv,
+ info,
+ windowBounds,
+ isMultiWindowMode,
+ transposeLayoutWithOrientation,
+ useTwoPanels
+ )
+
+ 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)
+
+ `when`(info.isTablet(any())).thenReturn(false)
+
+ scalableInvariantDeviceProfile()
+ }
+
+ private fun initializeVarsForTablet(isLandscape: Boolean = false) {
+ val (x, y) = if (isLandscape)
+ Pair(2560, 1600)
+ else
+ Pair(1600, 2560)
+
+ windowBounds = WindowBounds(x, y, x, y - 100)
+
+ `when`(info.isTablet(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()
+ }
+ }
+}
\ No newline at end of file