Merge "Make hotseat fit one line on tablets"
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;
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user