mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-27 15:26:58 +00:00
Make hotseat fit one line on tablets
When running on tablets in landscape, the QSB should inline with the hotseat icons. This does that and fixes small bugs that didn't account for the new hotseatBorderSpace. Bug: 210118169 Test: atest Launcher3Tests:DeviceProfileTest Test: visual, using HSV and Window Change-Id: I5a89c5449bf59fde736618151eceaacca443ef67
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -246,6 +248,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);
|
||||
@@ -270,7 +273,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) {
|
||||
@@ -333,6 +335,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 =
|
||||
@@ -340,10 +344,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.
|
||||
@@ -472,6 +483,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;
|
||||
@@ -717,6 +736,11 @@ public class DeviceProfile {
|
||||
|
||||
// Hotseat
|
||||
hotseatBorderSpace = pxFromDp(inv.hotseatBorderSpaces[mTypeIndex], mMetrics, scale);
|
||||
if (isQsbInline) {
|
||||
qsbWidth = calculateQsbWidth();
|
||||
} else {
|
||||
qsbWidth = 0;
|
||||
}
|
||||
updateHotseatIconSize(iconSizePx);
|
||||
|
||||
if (!isVerticalLayout) {
|
||||
@@ -895,15 +919,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())
|
||||
@@ -936,6 +969,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;
|
||||
@@ -953,7 +990,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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1113,6 +1154,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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
192
tests/src/com/android/launcher3/DeviceProfileTest.kt
Normal file
192
tests/src/com/android/launcher3/DeviceProfileTest.kt
Normal file
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user