Merge "Create an XML parser for WorkspaceSpecs" into tm-qpr-dev am: 5d5aad3024

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/20643018

Change-Id: I6a3f38e28b5a381bfa2ac42cdeba77c1527fb028
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Thales Lima
2023-02-09 12:19:35 +00:00
committed by Automerger Merge Worker
15 changed files with 1008 additions and 7 deletions

View File

@@ -19,7 +19,7 @@ import android.graphics.Rect
import android.graphics.RectF
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.launcher3.DeviceProfileBaseTest
import com.android.launcher3.FakeInvariantDeviceProfileTest
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT
import com.android.quickstep.views.TaskView.FullscreenDrawParams
@@ -36,7 +36,7 @@ import org.mockito.Mockito.mock
/** Test for FullscreenDrawParams class. */
@SmallTest
@RunWith(AndroidJUnit4::class)
class FullscreenDrawParamsTest : DeviceProfileBaseTest() {
class FullscreenDrawParamsTest : FakeInvariantDeviceProfileTest() {
private val TASK_SCALE = 0.7f
private var mThumbnailData: ThumbnailData = mock(ThumbnailData::class.java)

View File

@@ -18,7 +18,7 @@ package com.android.quickstep
import android.graphics.Rect
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.launcher3.DeviceProfileBaseTest
import com.android.launcher3.FakeInvariantDeviceProfileTest
import com.android.launcher3.util.WindowBounds
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -26,7 +26,7 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
class HotseatWidthCalculationTest : DeviceProfileBaseTest() {
class HotseatWidthCalculationTest : FakeInvariantDeviceProfileTest() {
/**
* This is a case when after setting the hotseat, the space needs to be recalculated but it

View File

@@ -224,6 +224,21 @@
<attr name="alignOnIcon" format="boolean" />
</declare-styleable>
<!-- Responsive grids attributes -->
<declare-styleable name="WorkspaceSpec">
<attr name="specType" format="integer">
<enum name="height" value="0" />
<enum name="width" value="1" />
</attr>
<attr name="maxAvailableSize" format="dimension" />
</declare-styleable>
<declare-styleable name="SpecSize">
<attr name="fixedSize" format="dimension" />
<attr name="ofAvailableSpace" format="float" />
<attr name="ofRemainderSpace" format="float" />
</declare-styleable>
<declare-styleable name="ProfileDisplayOption">
<attr name="name" />
<attr name="minWidthDps" format="float" />

View File

@@ -0,0 +1,37 @@
/*
* 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.TypedArray
import android.content.res.XmlResourceParser
import android.util.AttributeSet
import kotlin.IntArray
/**
* This class is a helper that can be subclassed in tests to provide a way to parse attributes
* correctly.
*/
open class ResourceHelper(private val context: Context, private val specsFileId: Int) {
open fun getXml(): XmlResourceParser {
return context.resources.getXml(specsFileId)
}
open fun obtainStyledAttributes(attrs: AttributeSet, styleId: IntArray): TypedArray {
return context.obtainStyledAttributes(attrs, styleId)
}
}

View File

@@ -0,0 +1,252 @@
/*
* 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.workspace
import android.content.res.TypedArray
import android.content.res.XmlResourceParser
import android.util.AttributeSet
import android.util.Log
import android.util.TypedValue
import android.util.Xml
import com.android.launcher3.R
import com.android.launcher3.util.ResourceHelper
import java.io.IOException
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException
private const val TAG = "WorkspaceSpecs"
class WorkspaceSpecs(resourceHelper: ResourceHelper) {
object XmlTags {
const val WORKSPACE_SPECS = "workspaceSpecs"
const val WORKSPACE_SPEC = "workspaceSpec"
const val START_PADDING = "startPadding"
const val END_PADDING = "endPadding"
const val GUTTER = "gutter"
const val CELL_SIZE = "cellSize"
}
val workspaceHeightSpecList = mutableListOf<WorkspaceSpec>()
val workspaceWidthSpecList = mutableListOf<WorkspaceSpec>()
init {
try {
val parser: XmlResourceParser = resourceHelper.getXml()
val depth = parser.depth
var type: Int
while (
(parser.next().also { type = it } != XmlPullParser.END_TAG ||
parser.depth > depth) && type != XmlPullParser.END_DOCUMENT
) {
if (type == XmlPullParser.START_TAG && XmlTags.WORKSPACE_SPECS == parser.name) {
val displayDepth = parser.depth
while (
(parser.next().also { type = it } != XmlPullParser.END_TAG ||
parser.depth > displayDepth) && type != XmlPullParser.END_DOCUMENT
) {
if (
type == XmlPullParser.START_TAG && XmlTags.WORKSPACE_SPEC == parser.name
) {
val attrs =
resourceHelper.obtainStyledAttributes(
Xml.asAttributeSet(parser),
R.styleable.WorkspaceSpec
)
val maxAvailableSize =
attrs.getDimensionPixelSize(
R.styleable.WorkspaceSpec_maxAvailableSize,
0
)
val specType =
WorkspaceSpec.SpecType.values()[
attrs.getInt(
R.styleable.WorkspaceSpec_specType,
WorkspaceSpec.SpecType.HEIGHT.ordinal
)
]
attrs.recycle()
var startPadding: SizeSpec? = null
var endPadding: SizeSpec? = null
var gutter: SizeSpec? = null
var cellSize: SizeSpec? = null
val limitDepth = parser.depth
while (
(parser.next().also { type = it } != XmlPullParser.END_TAG ||
parser.depth > limitDepth) && type != XmlPullParser.END_DOCUMENT
) {
val attr: AttributeSet = Xml.asAttributeSet(parser)
if (type == XmlPullParser.START_TAG) {
when (parser.name) {
XmlTags.START_PADDING -> {
startPadding = SizeSpec(resourceHelper, attr)
}
XmlTags.END_PADDING -> {
endPadding = SizeSpec(resourceHelper, attr)
}
XmlTags.GUTTER -> {
gutter = SizeSpec(resourceHelper, attr)
}
XmlTags.CELL_SIZE -> {
cellSize = SizeSpec(resourceHelper, attr)
}
}
}
}
if (
startPadding == null ||
endPadding == null ||
gutter == null ||
cellSize == null
) {
throw IllegalStateException(
"All attributes in workspaceSpec must be defined"
)
}
val workspaceSpec =
WorkspaceSpec(
maxAvailableSize,
specType,
startPadding,
endPadding,
gutter,
cellSize
)
if (workspaceSpec.isValid()) {
if (workspaceSpec.specType == WorkspaceSpec.SpecType.HEIGHT)
workspaceHeightSpecList.add(workspaceSpec)
else workspaceWidthSpecList.add(workspaceSpec)
} else {
throw IllegalStateException("Invalid workspaceSpec found.")
}
}
}
if (workspaceWidthSpecList.isEmpty() || workspaceHeightSpecList.isEmpty()) {
throw IllegalStateException(
"WorkspaceSpecs is incomplete - " +
"height list size = ${workspaceHeightSpecList.size}; " +
"width list size = ${workspaceWidthSpecList.size}."
)
}
}
}
parser.close()
} catch (e: Exception) {
when (e) {
is IOException,
is XmlPullParserException -> {
throw RuntimeException("Failure parsing workspaces specs file.", e)
}
else -> throw e
}
}
}
}
data class WorkspaceSpec(
val maxAvailableSize: Int,
val specType: SpecType,
val startPadding: SizeSpec,
val endPadding: SizeSpec,
val gutter: SizeSpec,
val cellSize: SizeSpec
) {
enum class SpecType {
HEIGHT,
WIDTH
}
fun isValid(): Boolean {
if (maxAvailableSize <= 0) {
Log.e(TAG, "WorkspaceSpec#isValid - maxAvailableSize <= 0")
return false
}
// All specs need to be individually valid
if (!allSpecsAreValid()) {
Log.e(TAG, "WorkspaceSpec#isValid - !allSpecsAreValid()")
return false
}
return true
}
private fun allSpecsAreValid(): Boolean =
startPadding.isValid() && endPadding.isValid() && gutter.isValid() && cellSize.isValid()
}
class SizeSpec(resourceHelper: ResourceHelper, attrs: AttributeSet) {
val fixedSize: Float
val ofAvailableSpace: Float
val ofRemainderSpace: Float
init {
val styledAttrs = resourceHelper.obtainStyledAttributes(attrs, R.styleable.SpecSize)
fixedSize = getValue(styledAttrs, R.styleable.SpecSize_fixedSize)
ofAvailableSpace = getValue(styledAttrs, R.styleable.SpecSize_ofAvailableSpace)
ofRemainderSpace = getValue(styledAttrs, R.styleable.SpecSize_ofRemainderSpace)
styledAttrs.recycle()
}
private fun getValue(a: TypedArray, index: Int): Float {
if (a.getType(index) == TypedValue.TYPE_DIMENSION) {
return a.getDimensionPixelSize(index, 0).toFloat()
} else if (a.getType(index) == TypedValue.TYPE_FLOAT) {
return a.getFloat(index, 0f)
}
return 0f
}
fun isValid(): Boolean {
// All attributes are empty
if (fixedSize <= 0f && ofAvailableSpace <= 0f && ofRemainderSpace <= 0f) {
Log.e(TAG, "SizeSpec#isValid - all attributes are empty")
return false
}
// More than one attribute is filled
val attrCount =
(if (fixedSize > 0) 1 else 0) +
(if (ofAvailableSpace > 0) 1 else 0) +
(if (ofRemainderSpace > 0) 1 else 0)
if (attrCount > 1) {
Log.e(TAG, "SizeSpec#isValid - more than one attribute is filled")
return false
}
// Values should be between 0 and 1
if (ofAvailableSpace !in 0f..1f || ofRemainderSpace !in 0f..1f) {
Log.e(TAG, "SizeSpec#isValid - values should be between 0 and 1")
return false
}
return true
}
override fun toString(): String {
return "SizeSpec(fixedSize=$fixedSize, ofAvailableSpace=$ofAvailableSpace, " +
"ofRemainderSpace=$ofRemainderSpace)"
}
}

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?><!--
/* Copyright 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.
*/
-->
<!-- Attributes have to be copied to test for correct parsing of files -->
<resources>
<!-- Responsive grids attributes -->
<declare-styleable name="WorkspaceSpec">
<attr name="specType" format="integer">
<enum name="height" value="0" />
<enum name="width" value="1" />
</attr>
<attr name="maxAvailableSize" format="dimension" />
</declare-styleable>
<declare-styleable name="SpecSize">
<attr name="fixedSize" format="dimension" />
<attr name="ofAvailableSpace" format="float" />
<attr name="ofRemainderSpace" format="float" />
</declare-styleable>
</resources>

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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.
-->
<workspaceSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
<workspaceSpec
launcher:specType="height"
launcher:maxAvailableSize="648dp">
<!-- missing startPadding -->
<endPadding
launcher:ofAvailableSpace="0.05" />
<gutter
launcher:fixedSize="16dp" />
<cellSize
launcher:ofRemainderSpace="0.2" />
</workspaceSpec>
<workspaceSpec
launcher:specType="height"
launcher:maxAvailableSize="9999dp">
<startPadding
launcher:ofAvailableSpace="0.0306" />
<endPadding
launcher:ofAvailableSpace="0.068" />
<gutter
launcher:fixedSize="16dp" />
<cellSize
launcher:ofRemainderSpace="0.2" />
</workspaceSpec>
<!-- Width spec is always the same -->
<workspaceSpec
launcher:specType="width"
launcher:maxAvailableSize="9999dp">
<startPadding
launcher:ofRemainderSpace="0.21436227" />
<endPadding
launcher:ofRemainderSpace="0.21436227" />
<gutter
launcher:ofRemainderSpace="0.11425509" />
<cellSize
launcher:fixedSize="120dp" />
</workspaceSpec>
</workspaceSpecs>

View File

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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.
-->
<workspaceSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
<workspaceSpec
launcher:specType="height"
launcher:maxAvailableSize="648dp">
<startPadding
launcher:ofAvailableSpace="0.0125" />
<endPadding
launcher:ofAvailableSpace="0.05" />
<!-- more than 1 value in one tag -->
<gutter
launcher:ofAvailableSpace="0.0125"
launcher:fixedSize="16dp" />
<cellSize
launcher:ofRemainderSpace="0.2" />
</workspaceSpec>
<workspaceSpec
launcher:specType="height"
launcher:maxAvailableSize="9999dp">
<startPadding
launcher:ofAvailableSpace="0.0306" />
<endPadding
launcher:ofAvailableSpace="0.068" />
<gutter
launcher:fixedSize="16dp" />
<cellSize
launcher:ofRemainderSpace="0.2" />
</workspaceSpec>
<!-- Width spec is always the same -->
<workspaceSpec
launcher:specType="width"
launcher:maxAvailableSize="9999dp">
<startPadding
launcher:ofRemainderSpace="0.21436227" />
<endPadding
launcher:ofRemainderSpace="0.21436227" />
<gutter
launcher:ofRemainderSpace="0.11425509" />
<cellSize
launcher:fixedSize="120dp" />
</workspaceSpec>
</workspaceSpecs>

View File

@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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.
-->
<workspaceSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
<workspaceSpec
launcher:specType="height"
launcher:maxAvailableSize="648dp">
<startPadding
launcher:ofAvailableSpace="0.0125" />
<endPadding
launcher:ofAvailableSpace="0.05" />
<gutter
launcher:fixedSize="16dp" />
<!-- value bigger than 1 -->
<cellSize
launcher:ofRemainderSpace="1.001" />
</workspaceSpec>
<workspaceSpec
launcher:specType="height"
launcher:maxAvailableSize="9999dp">
<startPadding
launcher:ofAvailableSpace="0.0306" />
<endPadding
launcher:ofAvailableSpace="0.068" />
<gutter
launcher:fixedSize="16dp" />
<cellSize
launcher:ofRemainderSpace="0.2" />
</workspaceSpec>
<!-- Width spec is always the same -->
<workspaceSpec
launcher:specType="width"
launcher:maxAvailableSize="9999dp">
<startPadding
launcher:ofRemainderSpace="0.21436227" />
<endPadding
launcher:ofRemainderSpace="0.21436227" />
<gutter
launcher:ofRemainderSpace="0.11425509" />
<cellSize
launcher:fixedSize="120dp" />
</workspaceSpec>
</workspaceSpecs>

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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.
-->
<workspaceSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
<workspaceSpec
launcher:specType="height"
launcher:maxAvailableSize="648dp">
<startPadding
launcher:ofAvailableSpace="0.0125" />
<endPadding
launcher:ofAvailableSpace="0.05" />
<gutter
launcher:fixedSize="16dp" />
<cellSize
launcher:ofRemainderSpace="0.2" />
</workspaceSpec>
<workspaceSpec
launcher:specType="height"
launcher:maxAvailableSize="9999dp">
<startPadding
launcher:ofAvailableSpace="0.0306" />
<endPadding
launcher:ofAvailableSpace="0.068" />
<gutter
launcher:fixedSize="16dp" />
<cellSize
launcher:ofRemainderSpace="0.2" />
</workspaceSpec>
<!-- Width spec is always the same -->
<workspaceSpec
launcher:specType="width"
launcher:maxAvailableSize="9999dp">
<startPadding
launcher:ofRemainderSpace="0.21436227" />
<endPadding
launcher:ofRemainderSpace="0.21436227" />
<gutter
launcher:ofRemainderSpace="0.11425509" />
<cellSize
launcher:fixedSize="120dp" />
</workspaceSpec>
</workspaceSpecs>

View File

@@ -0,0 +1,272 @@
/*
* 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
import android.content.Context
import android.content.res.Configuration
import android.graphics.Point
import android.graphics.Rect
import android.util.DisplayMetrics
import android.view.Surface
import androidx.test.core.app.ApplicationProvider
import com.android.launcher3.util.DisplayController
import com.android.launcher3.util.NavigationMode
import com.android.launcher3.util.WindowBounds
import com.android.launcher3.util.window.CachedDisplayInfo
import com.android.launcher3.util.window.WindowManagerProxy
import kotlin.math.max
import kotlin.math.min
import org.junit.After
import org.junit.Before
import org.mockito.ArgumentMatchers
import org.mockito.Mockito.mock
import org.mockito.Mockito.`when` as whenever
/**
* This is an abstract class for DeviceProfile tests that create an InvariantDeviceProfile based on
* a real device spec.
*
* For an implementation that mocks InvariantDeviceProfile, use [FakeInvariantDeviceProfileTest]
*/
abstract class AbstractDeviceProfileTest {
protected var context: Context? = null
protected open val runningContext: Context = ApplicationProvider.getApplicationContext()
private var displayController: DisplayController = mock(DisplayController::class.java)
private var windowManagerProxy: WindowManagerProxy = mock(WindowManagerProxy::class.java)
private lateinit var originalDisplayController: DisplayController
private lateinit var originalWindowManagerProxy: WindowManagerProxy
@Before
fun setUp() {
val appContext: Context = ApplicationProvider.getApplicationContext()
originalWindowManagerProxy = WindowManagerProxy.INSTANCE.get(appContext)
originalDisplayController = DisplayController.INSTANCE.get(appContext)
WindowManagerProxy.INSTANCE.initializeForTesting(windowManagerProxy)
DisplayController.INSTANCE.initializeForTesting(displayController)
}
@After
fun tearDown() {
WindowManagerProxy.INSTANCE.initializeForTesting(originalWindowManagerProxy)
DisplayController.INSTANCE.initializeForTesting(originalDisplayController)
}
class DeviceSpec(
val naturalSize: Pair<Int, Int>,
val densityDpi: Int,
val statusBarNaturalPx: Int,
val statusBarRotatedPx: Int,
val gesturePx: Int,
val cutoutPx: Int
)
open val deviceSpecs =
mapOf(
"phone" to
DeviceSpec(
Pair(1080, 2400),
densityDpi = 420,
statusBarNaturalPx = 118,
statusBarRotatedPx = 74,
gesturePx = 63,
cutoutPx = 118
),
"tablet" to
DeviceSpec(
Pair(2560, 1600),
densityDpi = 320,
statusBarNaturalPx = 104,
statusBarRotatedPx = 104,
gesturePx = 0,
cutoutPx = 0
),
)
protected fun initializeVarsForPhone(
deviceSpec: DeviceSpec,
isGestureMode: Boolean = true,
isVerticalBar: Boolean = false
) {
val (naturalX, naturalY) = deviceSpec.naturalSize
val windowsBounds = phoneWindowsBounds(deviceSpec, isGestureMode, naturalX, naturalY)
val displayInfo =
CachedDisplayInfo(Point(naturalX, naturalY), Surface.ROTATION_0, Rect(0, 0, 0, 0))
val perDisplayBoundsCache = mapOf(displayInfo to windowsBounds)
initializeCommonVars(
perDisplayBoundsCache,
displayInfo,
rotation = if (isVerticalBar) Surface.ROTATION_90 else Surface.ROTATION_0,
isGestureMode,
densityDpi = deviceSpec.densityDpi
)
}
protected fun initializeVarsForTablet(
deviceSpec: DeviceSpec,
isLandscape: Boolean = false,
isGestureMode: Boolean = true
) {
val (naturalX, naturalY) = deviceSpec.naturalSize
val windowsBounds = tabletWindowsBounds(deviceSpec, naturalX, naturalY)
val displayInfo =
CachedDisplayInfo(Point(naturalX, naturalY), Surface.ROTATION_0, Rect(0, 0, 0, 0))
val perDisplayBoundsCache = mapOf(displayInfo to windowsBounds)
initializeCommonVars(
perDisplayBoundsCache,
displayInfo,
rotation = if (isLandscape) Surface.ROTATION_0 else Surface.ROTATION_90,
isGestureMode,
densityDpi = deviceSpec.densityDpi
)
}
protected fun initializeVarsForTwoPanel(
deviceTabletSpec: DeviceSpec,
deviceSpec: DeviceSpec,
isLandscape: Boolean = false,
isGestureMode: Boolean = true
) {
val (tabletNaturalX, tabletNaturalY) = deviceTabletSpec.naturalSize
val tabletWindowsBounds =
tabletWindowsBounds(deviceTabletSpec, tabletNaturalX, tabletNaturalY)
val tabletDisplayInfo =
CachedDisplayInfo(
Point(tabletNaturalX, tabletNaturalY),
Surface.ROTATION_0,
Rect(0, 0, 0, 0)
)
val (phoneNaturalX, phoneNaturalY) = deviceSpec.naturalSize
val phoneWindowsBounds =
phoneWindowsBounds(deviceSpec, isGestureMode, phoneNaturalX, phoneNaturalY)
val phoneDisplayInfo =
CachedDisplayInfo(
Point(phoneNaturalX, phoneNaturalY),
Surface.ROTATION_0,
Rect(0, 0, 0, 0)
)
val perDisplayBoundsCache =
mapOf(tabletDisplayInfo to tabletWindowsBounds, phoneDisplayInfo to phoneWindowsBounds)
initializeCommonVars(
perDisplayBoundsCache,
tabletDisplayInfo,
rotation = if (isLandscape) Surface.ROTATION_0 else Surface.ROTATION_90,
isGestureMode,
densityDpi = deviceTabletSpec.densityDpi
)
}
private fun phoneWindowsBounds(
deviceSpec: DeviceSpec,
isGestureMode: Boolean,
naturalX: Int,
naturalY: Int
): Array<WindowBounds> {
val buttonsNavHeight = Utilities.dpToPx(48f, deviceSpec.densityDpi)
val rotation0Insets =
Rect(
0,
max(deviceSpec.statusBarNaturalPx, deviceSpec.cutoutPx),
0,
if (isGestureMode) deviceSpec.gesturePx else buttonsNavHeight
)
val rotation90Insets =
Rect(
deviceSpec.cutoutPx,
deviceSpec.statusBarRotatedPx,
if (isGestureMode) 0 else buttonsNavHeight,
if (isGestureMode) deviceSpec.gesturePx else 0
)
val rotation180Insets =
Rect(
0,
deviceSpec.statusBarNaturalPx,
0,
max(
if (isGestureMode) deviceSpec.gesturePx else buttonsNavHeight,
deviceSpec.cutoutPx
)
)
val rotation270Insets =
Rect(
if (isGestureMode) 0 else buttonsNavHeight,
deviceSpec.statusBarRotatedPx,
deviceSpec.cutoutPx,
if (isGestureMode) deviceSpec.gesturePx else 0
)
return arrayOf(
WindowBounds(Rect(0, 0, naturalX, naturalY), rotation0Insets, Surface.ROTATION_0),
WindowBounds(Rect(0, 0, naturalY, naturalX), rotation90Insets, Surface.ROTATION_90),
WindowBounds(Rect(0, 0, naturalX, naturalY), rotation180Insets, Surface.ROTATION_180),
WindowBounds(Rect(0, 0, naturalY, naturalX), rotation270Insets, Surface.ROTATION_270)
)
}
private fun tabletWindowsBounds(
deviceSpec: DeviceSpec,
naturalX: Int,
naturalY: Int
): Array<WindowBounds> {
val naturalInsets = Rect(0, deviceSpec.statusBarNaturalPx, 0, 0)
val rotatedInsets = Rect(0, deviceSpec.statusBarRotatedPx, 0, 0)
return arrayOf(
WindowBounds(Rect(0, 0, naturalX, naturalY), naturalInsets, Surface.ROTATION_0),
WindowBounds(Rect(0, 0, naturalY, naturalX), rotatedInsets, Surface.ROTATION_90),
WindowBounds(Rect(0, 0, naturalX, naturalY), naturalInsets, Surface.ROTATION_180),
WindowBounds(Rect(0, 0, naturalY, naturalX), rotatedInsets, Surface.ROTATION_270)
)
}
private fun initializeCommonVars(
perDisplayBoundsCache: Map<CachedDisplayInfo, Array<WindowBounds>>,
displayInfo: CachedDisplayInfo,
rotation: Int,
isGestureMode: Boolean = true,
densityDpi: Int
) {
val windowsBounds = perDisplayBoundsCache[displayInfo]!!
val realBounds = windowsBounds[rotation]
whenever(windowManagerProxy.getDisplayInfo(ArgumentMatchers.any())).thenReturn(displayInfo)
whenever(windowManagerProxy.getRealBounds(ArgumentMatchers.any(), ArgumentMatchers.any()))
.thenReturn(realBounds)
whenever(windowManagerProxy.getRotation(ArgumentMatchers.any())).thenReturn(rotation)
whenever(windowManagerProxy.getNavigationMode(ArgumentMatchers.any()))
.thenReturn(
if (isGestureMode) NavigationMode.NO_BUTTON else NavigationMode.THREE_BUTTONS
)
val density = densityDpi / DisplayMetrics.DENSITY_DEFAULT.toFloat()
val config =
Configuration(runningContext.resources.configuration).apply {
this.densityDpi = densityDpi
screenWidthDp = (realBounds.bounds.width() / density).toInt()
screenHeightDp = (realBounds.bounds.height() / density).toInt()
smallestScreenWidthDp = min(screenWidthDp, screenHeightDp)
}
context = runningContext.createConfigurationContext(config)
val info = DisplayController.Info(context, windowManagerProxy, perDisplayBoundsCache)
whenever(displayController.info).thenReturn(info)
whenever(displayController.isTransientTaskbar).thenReturn(isGestureMode)
}
}

View File

@@ -31,7 +31,13 @@ import org.mockito.ArgumentMatchers.any
import org.mockito.Mockito.mock
import org.mockito.Mockito.`when` as whenever
abstract class DeviceProfileBaseTest {
/**
* This is an abstract class for DeviceProfile tests that don't need the real Context and mock an
* InvariantDeviceProfile instead of creating one based on real values.
*
* For an implementation that creates InvariantDeviceProfile, use [AbstractDeviceProfileTest]
*/
abstract class FakeInvariantDeviceProfileTest {
protected var context: Context? = null
protected var inv: InvariantDeviceProfile? = null

View File

@@ -18,7 +18,7 @@ package com.android.launcher3.nonquickstep
import android.graphics.Rect
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.launcher3.DeviceProfileBaseTest
import com.android.launcher3.FakeInvariantDeviceProfileTest
import com.android.launcher3.util.WindowBounds
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -26,7 +26,7 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
class HotseatWidthCalculationTest : DeviceProfileBaseTest() {
class HotseatWidthCalculationTest : FakeInvariantDeviceProfileTest() {
/**
* This is a case when after setting the hotseat, the space needs to be recalculated but it

View File

@@ -0,0 +1,34 @@
/*
* 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.TypedArray
import android.util.AttributeSet
import com.android.launcher3.R
import com.android.launcher3.tests.R as TestR
import kotlin.IntArray
class TestResourceHelper(private val context: Context, private val specsFileId: Int) :
ResourceHelper(context, specsFileId) {
override fun obtainStyledAttributes(attrs: AttributeSet, styleId: IntArray): TypedArray {
var clone = styleId.clone()
if (styleId == R.styleable.SpecSize) clone = TestR.styleable.SpecSize
else if (styleId == R.styleable.WorkspaceSpec) clone = TestR.styleable.WorkspaceSpec
return context.obtainStyledAttributes(attrs, clone)
}
}

View File

@@ -0,0 +1,120 @@
/*
* 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.workspace
import android.content.Context
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.AbstractDeviceProfileTest
import com.android.launcher3.tests.R as TestR
import com.android.launcher3.util.TestResourceHelper
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
class WorkspaceSpecsTest : AbstractDeviceProfileTest() {
override val runningContext: Context = InstrumentationRegistry.getInstrumentation().context
@Before
fun setup() {
initializeVarsForPhone(deviceSpecs["phone"]!!)
}
@Test
fun parseValidFile() {
val workspaceSpecs =
WorkspaceSpecs(TestResourceHelper(context!!, TestR.xml.valid_workspace_file))
assertThat(workspaceSpecs.workspaceHeightSpecList.size).isEqualTo(2)
assertThat(workspaceSpecs.workspaceHeightSpecList[0].toString())
.isEqualTo(
"WorkspaceSpec(" +
"maxAvailableSize=1701, " +
"specType=HEIGHT, " +
"startPadding=SizeSpec(fixedSize=0.0, " +
"ofAvailableSpace=0.0125, " +
"ofRemainderSpace=0.0), " +
"endPadding=SizeSpec(fixedSize=0.0, " +
"ofAvailableSpace=0.05, " +
"ofRemainderSpace=0.0), " +
"gutter=SizeSpec(fixedSize=42.0, " +
"ofAvailableSpace=0.0, " +
"ofRemainderSpace=0.0), " +
"cellSize=SizeSpec(fixedSize=0.0, " +
"ofAvailableSpace=0.0, " +
"ofRemainderSpace=0.2)" +
")"
)
assertThat(workspaceSpecs.workspaceHeightSpecList[1].toString())
.isEqualTo(
"WorkspaceSpec(" +
"maxAvailableSize=26247, " +
"specType=HEIGHT, " +
"startPadding=SizeSpec(fixedSize=0.0, " +
"ofAvailableSpace=0.0306, " +
"ofRemainderSpace=0.0), " +
"endPadding=SizeSpec(fixedSize=0.0, " +
"ofAvailableSpace=0.068, " +
"ofRemainderSpace=0.0), " +
"gutter=SizeSpec(fixedSize=42.0, " +
"ofAvailableSpace=0.0, " +
"ofRemainderSpace=0.0), " +
"cellSize=SizeSpec(fixedSize=0.0, " +
"ofAvailableSpace=0.0, " +
"ofRemainderSpace=0.2)" +
")"
)
assertThat(workspaceSpecs.workspaceWidthSpecList.size).isEqualTo(1)
assertThat(workspaceSpecs.workspaceWidthSpecList[0].toString())
.isEqualTo(
"WorkspaceSpec(" +
"maxAvailableSize=26247, " +
"specType=WIDTH, " +
"startPadding=SizeSpec(fixedSize=0.0, " +
"ofAvailableSpace=0.0, " +
"ofRemainderSpace=0.21436226), " +
"endPadding=SizeSpec(fixedSize=0.0, " +
"ofAvailableSpace=0.0, " +
"ofRemainderSpace=0.21436226), " +
"gutter=SizeSpec(fixedSize=0.0, " +
"ofAvailableSpace=0.0, " +
"ofRemainderSpace=0.11425509), " +
"cellSize=SizeSpec(fixedSize=315.0, " +
"ofAvailableSpace=0.0, " +
"ofRemainderSpace=0.0)" +
")"
)
}
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_missingTag_throwsError() {
WorkspaceSpecs(TestResourceHelper(context!!, TestR.xml.invalid_workspace_file_case_1))
}
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_moreThanOneValuePerTag_throwsError() {
WorkspaceSpecs(TestResourceHelper(context!!, TestR.xml.invalid_workspace_file_case_2))
}
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_valueBiggerThan1_throwsError() {
WorkspaceSpecs(TestResourceHelper(context!!, TestR.xml.invalid_workspace_file_case_3))
}
}