Loading res/values/config.xml +24 −0 Original line number Diff line number Diff line Loading @@ -217,4 +217,28 @@ <!-- Whether the floating rotation button should be on the left/right in the device's natural orientation --> <bool name="floating_rotation_button_position_left">true</bool> <!-- Mapping of visual icon size to XML value http://b/235886078 --> <dimen name="iconSize48dp">52dp</dimen> <dimen name="iconSize50dp">55dp</dimen> <dimen name="iconSize52dp">57dp</dimen> <dimen name="iconSize54dp">59dp</dimen> <dimen name="iconSize56dp">61dp</dimen> <dimen name="iconSize58dp">63dp</dimen> <dimen name="iconSize60dp">66dp</dimen> <dimen name="iconSize66dp">72dp</dimen> <dimen name="iconSize72dp">79dp</dimen> <!-- Icon size steps in dp --> <integer-array name="icon_size_steps"> <item>@dimen/iconSize48dp</item> <item>@dimen/iconSize50dp</item> <item>@dimen/iconSize52dp</item> <item>@dimen/iconSize54dp</item> <item>@dimen/iconSize56dp</item> <item>@dimen/iconSize58dp</item> <item>@dimen/iconSize60dp</item> <item>@dimen/iconSize66dp</item> <item>@dimen/iconSize72dp</item> </integer-array> </resources> src/com/android/launcher3/DeviceProfile.java +37 −6 Original line number Diff line number Diff line Loading @@ -58,6 +58,7 @@ import com.android.launcher3.responsive.CalculatedAllAppsSpec; import com.android.launcher3.uioverrides.ApiWrapper; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.DisplayController.Info; import com.android.launcher3.util.IconSizeSteps; import com.android.launcher3.util.ResourceHelper; import com.android.launcher3.util.WindowBounds; import com.android.launcher3.workspace.CalculatedWorkspaceSpec; Loading @@ -83,6 +84,7 @@ public class DeviceProfile { public final InvariantDeviceProfile inv; private final Info mInfo; private final DisplayMetrics mMetrics; private final IconSizeSteps mIconSizeSteps; // Device properties public final boolean isTablet; Loading Loading @@ -330,6 +332,8 @@ public class DeviceProfile { final Resources res = context.getResources(); mMetrics = res.getDisplayMetrics(); mIconSizeSteps = mIsResponsiveGrid ? new IconSizeSteps(res) : null; // Determine sizes. widthPx = windowBounds.bounds.width(); heightPx = windowBounds.bounds.height(); Loading Loading @@ -535,12 +539,16 @@ public class DeviceProfile { // for the available height to be correct if (mIsResponsiveGrid) { mWorkspaceSpecs = new WorkspaceSpecs(new ResourceHelper(context, inv.workspaceSpecsId)); int availableResponsiveWidth = availableWidthPx - (isVerticalBarLayout() ? hotseatBarSizePx : 0); // don't use availableHeightPx because it subtracts bottom padding, // but the workspace go behind it int availableResponsiveHeight = heightPx - mInsets.top - (isVerticalBarLayout() ? 0 : hotseatBarSizePx); mResponsiveWidthSpec = mWorkspaceSpecs.getCalculatedWidthSpec(inv.numColumns, availableWidthPx); availableResponsiveWidth); mResponsiveHeightSpec = mWorkspaceSpecs.getCalculatedHeightSpec(inv.numRows, // don't use availableHeightPx because it subtracts bottom padding, // but the hotseat go behind it heightPx - mInsets.top - hotseatBarSizePx); availableResponsiveHeight); mAllAppsSpecs = new AllAppsSpecs(new ResourceHelper(context, inv.allAppsSpecsId)); mAllAppsResponsiveWidthSpec = mAllAppsSpecs.getCalculatedWidthSpec(inv.numColumns, Loading Loading @@ -926,9 +934,32 @@ public class DeviceProfile { cellWidthPx = mResponsiveWidthSpec.getCellSizePx(); cellHeightPx = mResponsiveHeightSpec.getCellSizePx(); cellYPaddingPx = Math.max(0, cellHeightPx - cellContentHeight) / 2; // TODO(b/283929701): decrease icon size if content doesn't fit on cell if (cellWidthPx < iconSizePx) { // get a smaller icon size iconSizePx = mIconSizeSteps.getIconSmallerThan(cellWidthPx); // calculate new cellContentHeight cellContentHeight = iconSizePx + cellTextAndPaddingHeight; } while (iconSizePx > mIconSizeSteps.minimumIconSize() && cellContentHeight > cellHeightPx) { int extraHeightRequired = cellContentHeight - cellHeightPx; int newPadding = iconDrawablePaddingPx - extraHeightRequired; if (newPadding >= 0) { // Responsive uses the padding without scaling iconDrawablePaddingPx = iconDrawablePaddingOriginalPx = newPadding; cellTextAndPaddingHeight = iconDrawablePaddingPx + Utilities.calculateTextHeight(iconTextSizePx); } else { // get a smaller icon size iconSizePx = mIconSizeSteps.getNextLowerIconSize(iconSizePx); } // calculate new cellContentHeight cellContentHeight = iconSizePx + cellTextAndPaddingHeight; } cellYPaddingPx = Math.max(0, cellHeightPx - cellContentHeight) / 2; } else if (mIsScalableGrid) { cellWidthPx = pxFromDp(inv.minCellSize[mTypeIndex].x, mMetrics, scale); cellHeightPx = pxFromDp(inv.minCellSize[mTypeIndex].y, mMetrics, scale); Loading src/com/android/launcher3/util/IconSizeSteps.kt 0 → 100644 +47 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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 import android.content.res.Resources import androidx.core.content.res.getDimensionOrThrow import androidx.core.content.res.use import com.android.launcher3.R import kotlin.math.max class IconSizeSteps(res: Resources) { private val steps: List<Int> init { steps = res.obtainTypedArray(R.array.icon_size_steps).use { (0 until it.length()).map { step -> it.getDimensionOrThrow(step).toInt() }.sorted() } } fun minimumIconSize(): Int = steps[0] fun getNextLowerIconSize(iconSizePx: Int): Int { return steps[max(0, getIndexForIconSize(iconSizePx) - 1)] } fun getIconSmallerThan(cellWidth: Int): Int { return steps.lastOrNull { it <= cellWidth } ?: steps[0] } private fun getIndexForIconSize(iconSizePx: Int): Int { return max(0, steps.indexOfFirst { iconSizePx <= it }) } } tests/src/com/android/launcher3/util/IconSizeStepsTest.kt 0 → 100644 +67 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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 import android.content.Context import android.content.res.Configuration import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.google.common.truth.Truth.assertThat import org.junit.Assert.* import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class IconSizeStepsTest { private var context: Context? = null private val runningContext: Context = ApplicationProvider.getApplicationContext() private lateinit var iconSizeSteps: IconSizeSteps @Before fun setup() { // 160dp makes 1px = 1dp val config = Configuration(runningContext.resources.configuration).apply { this.densityDpi = 160 } context = runningContext.createConfigurationContext(config) iconSizeSteps = IconSizeSteps(context!!.resources) } @Test fun minimumIconSize() { assertThat(iconSizeSteps.minimumIconSize()).isEqualTo(52) } @Test fun getNextLowerIconSize() { assertThat(iconSizeSteps.getNextLowerIconSize(66)).isEqualTo(63) // Assert that never goes below minimum assertThat(iconSizeSteps.getNextLowerIconSize(52)).isEqualTo(52) assertThat(iconSizeSteps.getNextLowerIconSize(30)).isEqualTo(52) } @Test fun getIconSmallerThan() { assertThat(iconSizeSteps.getIconSmallerThan(60)).isEqualTo(59) // Assert that never goes below minimum assertThat(iconSizeSteps.getIconSmallerThan(52)).isEqualTo(52) assertThat(iconSizeSteps.getIconSmallerThan(30)).isEqualTo(52) } } Loading
res/values/config.xml +24 −0 Original line number Diff line number Diff line Loading @@ -217,4 +217,28 @@ <!-- Whether the floating rotation button should be on the left/right in the device's natural orientation --> <bool name="floating_rotation_button_position_left">true</bool> <!-- Mapping of visual icon size to XML value http://b/235886078 --> <dimen name="iconSize48dp">52dp</dimen> <dimen name="iconSize50dp">55dp</dimen> <dimen name="iconSize52dp">57dp</dimen> <dimen name="iconSize54dp">59dp</dimen> <dimen name="iconSize56dp">61dp</dimen> <dimen name="iconSize58dp">63dp</dimen> <dimen name="iconSize60dp">66dp</dimen> <dimen name="iconSize66dp">72dp</dimen> <dimen name="iconSize72dp">79dp</dimen> <!-- Icon size steps in dp --> <integer-array name="icon_size_steps"> <item>@dimen/iconSize48dp</item> <item>@dimen/iconSize50dp</item> <item>@dimen/iconSize52dp</item> <item>@dimen/iconSize54dp</item> <item>@dimen/iconSize56dp</item> <item>@dimen/iconSize58dp</item> <item>@dimen/iconSize60dp</item> <item>@dimen/iconSize66dp</item> <item>@dimen/iconSize72dp</item> </integer-array> </resources>
src/com/android/launcher3/DeviceProfile.java +37 −6 Original line number Diff line number Diff line Loading @@ -58,6 +58,7 @@ import com.android.launcher3.responsive.CalculatedAllAppsSpec; import com.android.launcher3.uioverrides.ApiWrapper; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.DisplayController.Info; import com.android.launcher3.util.IconSizeSteps; import com.android.launcher3.util.ResourceHelper; import com.android.launcher3.util.WindowBounds; import com.android.launcher3.workspace.CalculatedWorkspaceSpec; Loading @@ -83,6 +84,7 @@ public class DeviceProfile { public final InvariantDeviceProfile inv; private final Info mInfo; private final DisplayMetrics mMetrics; private final IconSizeSteps mIconSizeSteps; // Device properties public final boolean isTablet; Loading Loading @@ -330,6 +332,8 @@ public class DeviceProfile { final Resources res = context.getResources(); mMetrics = res.getDisplayMetrics(); mIconSizeSteps = mIsResponsiveGrid ? new IconSizeSteps(res) : null; // Determine sizes. widthPx = windowBounds.bounds.width(); heightPx = windowBounds.bounds.height(); Loading Loading @@ -535,12 +539,16 @@ public class DeviceProfile { // for the available height to be correct if (mIsResponsiveGrid) { mWorkspaceSpecs = new WorkspaceSpecs(new ResourceHelper(context, inv.workspaceSpecsId)); int availableResponsiveWidth = availableWidthPx - (isVerticalBarLayout() ? hotseatBarSizePx : 0); // don't use availableHeightPx because it subtracts bottom padding, // but the workspace go behind it int availableResponsiveHeight = heightPx - mInsets.top - (isVerticalBarLayout() ? 0 : hotseatBarSizePx); mResponsiveWidthSpec = mWorkspaceSpecs.getCalculatedWidthSpec(inv.numColumns, availableWidthPx); availableResponsiveWidth); mResponsiveHeightSpec = mWorkspaceSpecs.getCalculatedHeightSpec(inv.numRows, // don't use availableHeightPx because it subtracts bottom padding, // but the hotseat go behind it heightPx - mInsets.top - hotseatBarSizePx); availableResponsiveHeight); mAllAppsSpecs = new AllAppsSpecs(new ResourceHelper(context, inv.allAppsSpecsId)); mAllAppsResponsiveWidthSpec = mAllAppsSpecs.getCalculatedWidthSpec(inv.numColumns, Loading Loading @@ -926,9 +934,32 @@ public class DeviceProfile { cellWidthPx = mResponsiveWidthSpec.getCellSizePx(); cellHeightPx = mResponsiveHeightSpec.getCellSizePx(); cellYPaddingPx = Math.max(0, cellHeightPx - cellContentHeight) / 2; // TODO(b/283929701): decrease icon size if content doesn't fit on cell if (cellWidthPx < iconSizePx) { // get a smaller icon size iconSizePx = mIconSizeSteps.getIconSmallerThan(cellWidthPx); // calculate new cellContentHeight cellContentHeight = iconSizePx + cellTextAndPaddingHeight; } while (iconSizePx > mIconSizeSteps.minimumIconSize() && cellContentHeight > cellHeightPx) { int extraHeightRequired = cellContentHeight - cellHeightPx; int newPadding = iconDrawablePaddingPx - extraHeightRequired; if (newPadding >= 0) { // Responsive uses the padding without scaling iconDrawablePaddingPx = iconDrawablePaddingOriginalPx = newPadding; cellTextAndPaddingHeight = iconDrawablePaddingPx + Utilities.calculateTextHeight(iconTextSizePx); } else { // get a smaller icon size iconSizePx = mIconSizeSteps.getNextLowerIconSize(iconSizePx); } // calculate new cellContentHeight cellContentHeight = iconSizePx + cellTextAndPaddingHeight; } cellYPaddingPx = Math.max(0, cellHeightPx - cellContentHeight) / 2; } else if (mIsScalableGrid) { cellWidthPx = pxFromDp(inv.minCellSize[mTypeIndex].x, mMetrics, scale); cellHeightPx = pxFromDp(inv.minCellSize[mTypeIndex].y, mMetrics, scale); Loading
src/com/android/launcher3/util/IconSizeSteps.kt 0 → 100644 +47 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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 import android.content.res.Resources import androidx.core.content.res.getDimensionOrThrow import androidx.core.content.res.use import com.android.launcher3.R import kotlin.math.max class IconSizeSteps(res: Resources) { private val steps: List<Int> init { steps = res.obtainTypedArray(R.array.icon_size_steps).use { (0 until it.length()).map { step -> it.getDimensionOrThrow(step).toInt() }.sorted() } } fun minimumIconSize(): Int = steps[0] fun getNextLowerIconSize(iconSizePx: Int): Int { return steps[max(0, getIndexForIconSize(iconSizePx) - 1)] } fun getIconSmallerThan(cellWidth: Int): Int { return steps.lastOrNull { it <= cellWidth } ?: steps[0] } private fun getIndexForIconSize(iconSizePx: Int): Int { return max(0, steps.indexOfFirst { iconSizePx <= it }) } }
tests/src/com/android/launcher3/util/IconSizeStepsTest.kt 0 → 100644 +67 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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 import android.content.Context import android.content.res.Configuration import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.google.common.truth.Truth.assertThat import org.junit.Assert.* import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class IconSizeStepsTest { private var context: Context? = null private val runningContext: Context = ApplicationProvider.getApplicationContext() private lateinit var iconSizeSteps: IconSizeSteps @Before fun setup() { // 160dp makes 1px = 1dp val config = Configuration(runningContext.resources.configuration).apply { this.densityDpi = 160 } context = runningContext.createConfigurationContext(config) iconSizeSteps = IconSizeSteps(context!!.resources) } @Test fun minimumIconSize() { assertThat(iconSizeSteps.minimumIconSize()).isEqualTo(52) } @Test fun getNextLowerIconSize() { assertThat(iconSizeSteps.getNextLowerIconSize(66)).isEqualTo(63) // Assert that never goes below minimum assertThat(iconSizeSteps.getNextLowerIconSize(52)).isEqualTo(52) assertThat(iconSizeSteps.getNextLowerIconSize(30)).isEqualTo(52) } @Test fun getIconSmallerThan() { assertThat(iconSizeSteps.getIconSmallerThan(60)).isEqualTo(59) // Assert that never goes below minimum assertThat(iconSizeSteps.getIconSmallerThan(52)).isEqualTo(52) assertThat(iconSizeSteps.getIconSmallerThan(30)).isEqualTo(52) } }