Merge "Make hotseat fit one line on tablets"

This commit is contained in:
Thales Lima
2022-01-25 14:03:34 +00:00
committed by Android (Google) Code Review
6 changed files with 264 additions and 14 deletions
+1
View File
@@ -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
+51 -8
View File
@@ -61,6 +61,7 @@ public class DeviceProfile {
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 class DeviceProfile {
public final float qsbBottomMarginOriginalPx;
public int qsbBottomMarginPx;
public int qsbWidth; // only used when isQsbInline
// All apps
public Point allAppsBorderSpacePx;
@@ -247,6 +249,7 @@ public class DeviceProfile {
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 @@ public class DeviceProfile {
}
}
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 @@ public class DeviceProfile {
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 @@ public class DeviceProfile {
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 @@ public class DeviceProfile {
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 @@ public class DeviceProfile {
// Hotseat
hotseatBorderSpace = pxFromDp(inv.hotseatBorderSpaces[mTypeIndex], mMetrics, scale);
if (isQsbInline) {
qsbWidth = calculateQsbWidth();
} else {
qsbWidth = 0;
}
updateHotseatIconSize(iconSizePx);
if (!isVerticalLayout) {
@@ -901,15 +925,24 @@ public class DeviceProfile {
} 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 @@ public class DeviceProfile {
* 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 @@ public class DeviceProfile {
* 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 @@ public class DeviceProfile {
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);
+11 -2
View File
@@ -173,7 +173,10 @@ public class Hotseat extends CellLayout implements Insettable {
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 @@ public class Hotseat extends CellLayout implements Insettable {
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();
@@ -19,6 +19,7 @@ package com.android.launcher3;
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 @@ public class ShortcutAndWidgetContainer extends ViewGroup implements FolderIcon.
// 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 @@ public class ShortcutAndWidgetContainer extends ViewGroup implements FolderIcon.
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 @@ public class ShortcutAndWidgetContainer extends ViewGroup implements FolderIcon.
@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();
}
+4 -1
View File
@@ -23,7 +23,10 @@ package {
// 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
@@ -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()
}
}
}