Create specs for hotseat
Some attributes of hotseat change depending on the size of the device. In the future more attributes could be moved to the spec, e.g. hotseat icons.
Fix: 292204436
Test: CalculatedHotseatSpecTest
Test: HotseatSpecsTest
Test: SizeSpecTest
Test: DeviceProfileResponsiveDumpTest
Test: DeviceProfileResponsiveAlternativeDisplaysDumpTest
Flag: ENABLE_RESPONSIVE_WORKSPACE
Change-Id: I6a4e05d75af819dbf1444a5ca45c2080f55dc203
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 83de98a..ee7bebf 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -56,8 +56,10 @@
import com.android.launcher3.responsive.AllAppsSpecs;
import com.android.launcher3.responsive.CalculatedAllAppsSpec;
import com.android.launcher3.responsive.CalculatedFolderSpec;
+import com.android.launcher3.responsive.CalculatedHotseatSpec;
import com.android.launcher3.responsive.CalculatedWorkspaceSpec;
import com.android.launcher3.responsive.FolderSpecs;
+import com.android.launcher3.responsive.HotseatSpecs;
import com.android.launcher3.responsive.WorkspaceSpecs;
import com.android.launcher3.uioverrides.ApiWrapper;
import com.android.launcher3.util.DisplayController;
@@ -121,6 +123,7 @@
private CalculatedAllAppsSpec mAllAppsResponsiveHeightSpec;
private CalculatedFolderSpec mResponsiveFolderWidthSpec;
private CalculatedFolderSpec mResponsiveFolderHeightSpec;
+ private CalculatedHotseatSpec mResponsiveHotseatSpec;
/**
* The maximum amount of left/right workspace padding as a percentage of the screen width.
@@ -316,7 +319,8 @@
// TODO(b/241386436): shouldn't change any launcher behaviour
mIsResponsiveGrid = inv.workspaceSpecsId != INVALID_RESOURCE_HANDLE
&& inv.allAppsSpecsId != INVALID_RESOURCE_HANDLE
- && inv.folderSpecsId != INVALID_RESOURCE_HANDLE;
+ && inv.folderSpecsId != INVALID_RESOURCE_HANDLE
+ && inv.hotseatSpecsId != INVALID_RESOURCE_HANDLE;
mIsScalableGrid = inv.isScalable && !isVerticalBarLayout() && !isMultiWindowMode;
// Determine device posture.
@@ -495,7 +499,17 @@
int hotseatBarBottomSpace = pxFromDp(inv.hotseatBarBottomSpace[mTypeIndex], mMetrics);
int minQsbMargin = res.getDimensionPixelSize(R.dimen.min_qsb_margin);
- hotseatQsbSpace = pxFromDp(inv.hotseatQsbSpace[mTypeIndex], mMetrics);
+
+ if (mIsResponsiveGrid) {
+ HotseatSpecs hotseatSpecs =
+ HotseatSpecs.create(new ResourceHelper(context,
+ isTwoPanels ? inv.hotseatSpecsTwoPanelId : inv.hotseatSpecsId));
+ mResponsiveHotseatSpec = hotseatSpecs.getCalculatedHeightSpec(heightPx);
+ hotseatQsbSpace = mResponsiveHotseatSpec.getHotseatQsbSpace();
+ } else {
+ hotseatQsbSpace = pxFromDp(inv.hotseatQsbSpace[mTypeIndex], mMetrics);
+ }
+
// Have a little space between the inset and the QSB
if (mInsets.bottom + minQsbMargin > hotseatBarBottomSpace) {
int availableSpace = hotseatQsbSpace - (mInsets.bottom - hotseatBarBottomSpace);
@@ -1952,6 +1966,7 @@
+ mAllAppsResponsiveWidthSpec.toString());
writer.println(prefix + "\tmResponsiveFolderHeightSpec:" + mResponsiveFolderHeightSpec);
writer.println(prefix + "\tmResponsiveFolderWidthSpec:" + mResponsiveFolderWidthSpec);
+ writer.println(prefix + "\tmResponsiveHotseatSpec:" + mResponsiveHotseatSpec);
}
}
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index dac6120..722991a 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -189,6 +189,8 @@
public int folderSpecsId = INVALID_RESOURCE_HANDLE;
@XmlRes
public int folderSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
+ public int hotseatSpecsId = INVALID_RESOURCE_HANDLE;
+ public int hotseatSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
public String dbFile;
public int defaultLayoutId;
@@ -368,6 +370,8 @@
allAppsSpecsTwoPanelId = closestProfile.mAllAppsSpecsTwoPanelId;
folderSpecsId = closestProfile.mFolderSpecsId;
folderSpecsTwoPanelId = closestProfile.mFolderSpecsTwoPanelId;
+ hotseatSpecsId = closestProfile.mHotseatSpecsId;
+ hotseatSpecsTwoPanelId = closestProfile.mHotseatSpecsTwoPanelId;
this.deviceType = deviceType;
inlineNavButtonsEndSpacing = closestProfile.inlineNavButtonsEndSpacing;
@@ -819,6 +823,8 @@
private final int mAllAppsSpecsTwoPanelId;
private final int mFolderSpecsId;
private final int mFolderSpecsTwoPanelId;
+ private final int mHotseatSpecsId;
+ private final int mHotseatSpecsTwoPanelId;
public GridOption(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(
@@ -896,6 +902,11 @@
mFolderSpecsTwoPanelId = a.getResourceId(
R.styleable.GridDisplayOption_folderSpecsTwoPanelId,
INVALID_RESOURCE_HANDLE);
+ mHotseatSpecsId = a.getResourceId(
+ R.styleable.GridDisplayOption_hotseatSpecsId, INVALID_RESOURCE_HANDLE);
+ mHotseatSpecsTwoPanelId = a.getResourceId(
+ R.styleable.GridDisplayOption_hotseatSpecsTwoPanelId,
+ INVALID_RESOURCE_HANDLE);
} else {
mWorkspaceSpecsId = INVALID_RESOURCE_HANDLE;
mWorkspaceSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
@@ -903,6 +914,8 @@
mAllAppsSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
mFolderSpecsId = INVALID_RESOURCE_HANDLE;
mFolderSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
+ mHotseatSpecsId = INVALID_RESOURCE_HANDLE;
+ mHotseatSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
}
int inlineForRotation = a.getInt(R.styleable.GridDisplayOption_inlineQsb,
diff --git a/src/com/android/launcher3/responsive/HotseatSpecs.kt b/src/com/android/launcher3/responsive/HotseatSpecs.kt
new file mode 100644
index 0000000..482508d
--- /dev/null
+++ b/src/com/android/launcher3/responsive/HotseatSpecs.kt
@@ -0,0 +1,122 @@
+/*
+ * 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.responsive
+
+import android.content.res.TypedArray
+import android.util.Log
+import com.android.launcher3.R
+import com.android.launcher3.util.ResourceHelper
+
+class HotseatSpecs(val specs: List<HotseatSpec>) {
+
+ fun getCalculatedHeightSpec(availableHeight: Int): CalculatedHotseatSpec {
+ val spec = specs.firstOrNull { availableHeight <= it.maxAvailableSize }
+ check(spec != null) { "No available height spec found within $availableHeight." }
+ return CalculatedHotseatSpec(availableHeight, spec)
+ }
+
+ companion object {
+ private const val XML_HOTSEAT_SPEC = "hotseatSpec"
+
+ @JvmStatic
+ fun create(resourceHelper: ResourceHelper): HotseatSpecs {
+ val parser = ResponsiveSpecsParser(resourceHelper)
+ val specs = parser.parseXML(XML_HOTSEAT_SPEC, ::HotseatSpec)
+ return HotseatSpecs(specs.filter { it.specType == ResponsiveSpec.SpecType.HEIGHT })
+ }
+ }
+}
+
+data class HotseatSpec(
+ val maxAvailableSize: Int,
+ val specType: ResponsiveSpec.SpecType,
+ val hotseatQsbSpace: SizeSpec
+) {
+
+ init {
+ check(isValid()) { "Invalid HotseatSpec found." }
+ }
+
+ constructor(
+ attrs: TypedArray,
+ specs: Map<String, SizeSpec>
+ ) : this(
+ maxAvailableSize =
+ attrs.getDimensionPixelSize(R.styleable.ResponsiveSpec_maxAvailableSize, 0),
+ specType =
+ ResponsiveSpec.SpecType.values()[
+ attrs.getInt(
+ R.styleable.ResponsiveSpec_specType,
+ ResponsiveSpec.SpecType.HEIGHT.ordinal
+ )],
+ hotseatQsbSpace = specs.getOrError(SizeSpec.XmlTags.HOTSEAT_QSB_SPACE)
+ )
+
+ fun isValid(): Boolean {
+ if (maxAvailableSize <= 0) {
+ Log.e(LOG_TAG, "${this::class.simpleName}#isValid - maxAvailableSize <= 0")
+ return false
+ }
+
+ // All specs need to be individually valid
+ if (!allSpecsAreValid()) {
+ Log.e(LOG_TAG, "${this::class.simpleName}#isValid - !allSpecsAreValid()")
+ return false
+ }
+
+ return true
+ }
+
+ private fun allSpecsAreValid(): Boolean {
+ return hotseatQsbSpace.isValid() && hotseatQsbSpace.onlyFixedSize()
+ }
+
+ companion object {
+ private const val LOG_TAG = "HotseatSpec"
+ }
+}
+
+class CalculatedHotseatSpec(val availableSpace: Int, val spec: HotseatSpec) {
+
+ var hotseatQsbSpace: Int = 0
+ private set
+
+ init {
+ hotseatQsbSpace = spec.hotseatQsbSpace.getCalculatedValue(availableSpace)
+ }
+
+ override fun hashCode(): Int {
+ var result = availableSpace.hashCode()
+ result = 31 * result + hotseatQsbSpace.hashCode()
+ result = 31 * result + spec.hashCode()
+ return result
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return other is CalculatedHotseatSpec &&
+ availableSpace == other.availableSpace &&
+ hotseatQsbSpace == other.hotseatQsbSpace &&
+ spec == other.spec
+ }
+
+ override fun toString(): String {
+ return "${this::class.simpleName}(" +
+ "availableSpace=$availableSpace, hotseatQsbSpace=$hotseatQsbSpace, " +
+ "${spec::class.simpleName}.maxAvailableSize=${spec.maxAvailableSize}" +
+ ")"
+ }
+}
diff --git a/src/com/android/launcher3/responsive/SizeSpec.kt b/src/com/android/launcher3/responsive/SizeSpec.kt
index d3868f0..c868c9f 100644
--- a/src/com/android/launcher3/responsive/SizeSpec.kt
+++ b/src/com/android/launcher3/responsive/SizeSpec.kt
@@ -107,11 +107,20 @@
return true
}
+ fun onlyFixedSize(): Boolean {
+ if (ofAvailableSpace > 0 || ofRemainderSpace > 0 || matchWorkspace) {
+ Log.e(TAG, "SizeSpec#onlyFixedSize - only fixed size allowed for this tag")
+ return false
+ }
+ return true
+ }
+
object XmlTags {
const val START_PADDING = "startPadding"
const val END_PADDING = "endPadding"
const val GUTTER = "gutter"
const val CELL_SIZE = "cellSize"
+ const val HOTSEAT_QSB_SPACE = "hotseatQsbSpace"
}
companion object {