[go: nahoru, domu]

blob: 5d3cea3cffdf78f6b3cde850755588d010a49bd0 [file] [log] [blame]
/*
* Copyright 2021 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 androidx.window.embedding
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.res.Resources
import android.content.res.XmlResourceParser
import android.util.LayoutDirection
import androidx.annotation.XmlRes
import androidx.window.R
import androidx.window.embedding.EmbeddingAspectRatio.Companion.buildAspectRatioFromValue
import androidx.window.embedding.SplitRule.Companion.FINISH_ALWAYS
import androidx.window.embedding.SplitRule.Companion.FINISH_NEVER
import org.xmlpull.v1.XmlPullParser
/**
* Parses the static rules defined in XML.
*/
internal object RuleParser {
internal fun parseRules(
context: Context,
@XmlRes staticRuleResourceId: Int,
): Set<EmbeddingRule>? {
val resources = context.resources
val parser: XmlResourceParser
try {
parser = resources.getXml(staticRuleResourceId)
} catch (e: Resources.NotFoundException) {
// Can't find the XML defining the split config
return null
}
val rules = HashSet<EmbeddingRule>()
val depth = parser.depth
var type = parser.next()
var lastSplitPairRule: SplitPairRule? = null
var lastSplitPlaceholderRule: SplitPlaceholderRule? = null
var lastActivityRule: ActivityRule? = null
while (type != XmlPullParser.END_DOCUMENT &&
(type != XmlPullParser.END_TAG || parser.depth > depth)
) {
if (parser.eventType != XmlPullParser.START_TAG || "split-config" == parser.name) {
type = parser.next()
continue
}
when (parser.name) {
"SplitPairRule" -> {
val splitConfig = parseSplitPairRule(context, parser)
lastSplitPairRule = splitConfig
rules.add(lastSplitPairRule)
lastSplitPlaceholderRule = null
lastActivityRule = null
}
"SplitPlaceholderRule" -> {
val placeholderConfig = parseSplitPlaceholderRule(context, parser)
lastSplitPlaceholderRule = placeholderConfig
rules.add(lastSplitPlaceholderRule)
lastActivityRule = null
lastSplitPairRule = null
}
"SplitPairFilter" -> {
if (lastSplitPairRule == null) {
throw IllegalArgumentException(
"Found orphaned SplitPairFilter outside of SplitPairRule"
)
}
val splitFilter = parseSplitPairFilter(context, parser)
rules.remove(lastSplitPairRule)
lastSplitPairRule += splitFilter
rules.add(lastSplitPairRule)
}
"ActivityRule" -> {
val activityConfig = parseActivityRule(context, parser)
rules.add(activityConfig)
lastSplitPairRule = null
lastSplitPlaceholderRule = null
lastActivityRule = activityConfig
}
"ActivityFilter" -> {
if (lastActivityRule == null && lastSplitPlaceholderRule == null) {
throw IllegalArgumentException(
"Found orphaned ActivityFilter"
)
}
val activityFilter = parseActivityFilter(context, parser)
if (lastActivityRule != null) {
rules.remove(lastActivityRule)
lastActivityRule += activityFilter
rules.add(lastActivityRule)
} else if (lastSplitPlaceholderRule != null) {
rules.remove(lastSplitPlaceholderRule)
lastSplitPlaceholderRule += activityFilter
rules.add(lastSplitPlaceholderRule)
}
}
}
type = parser.next()
}
return rules
}
private fun parseSplitPairRule(
context: Context,
parser: XmlResourceParser
): SplitPairRule {
val ratio: Float
val minWidthDp: Int
val minSmallestWidthDp: Int
val maxAspectRatioInPortrait: Float
val maxAspectRatioInLandscape: Float
val layoutDir: Int
val finishPrimaryWithSecondary: Int
val finishSecondaryWithPrimary: Int
val clearTop: Boolean
context.theme.obtainStyledAttributes(
parser,
R.styleable.SplitPairRule,
0,
0
).apply {
ratio = getFloat(R.styleable.SplitPairRule_splitRatio, 0.5f)
minWidthDp = getInteger(
R.styleable.SplitPairRule_splitMinWidthDp,
SplitRule.SPLIT_MIN_DIMENSION_DP_DEFAULT
)
minSmallestWidthDp = getInteger(
R.styleable.SplitPairRule_splitMinSmallestWidthDp,
SplitRule.SPLIT_MIN_DIMENSION_DP_DEFAULT
)
maxAspectRatioInPortrait = getFloat(
R.styleable.SplitPairRule_splitMaxAspectRatioInPortrait,
SplitRule.SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT.value
)
maxAspectRatioInLandscape = getFloat(
R.styleable.SplitPairRule_splitMaxAspectRatioInLandscape,
SplitRule.SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT.value
)
layoutDir = getInt(
R.styleable.SplitPairRule_splitLayoutDirection,
LayoutDirection.LOCALE
)
finishPrimaryWithSecondary =
getInt(R.styleable.SplitPairRule_finishPrimaryWithSecondary, FINISH_NEVER)
finishSecondaryWithPrimary =
getInt(R.styleable.SplitPairRule_finishSecondaryWithPrimary, FINISH_ALWAYS)
clearTop =
getBoolean(R.styleable.SplitPairRule_clearTop, false)
}
return SplitPairRule.Builder(emptySet())
.setMinWidthDp(minWidthDp)
.setMinSmallestWidthDp(minSmallestWidthDp)
.setMaxAspectRatioInPortrait(buildAspectRatioFromValue(maxAspectRatioInPortrait))
.setMaxAspectRatioInLandscape(buildAspectRatioFromValue(maxAspectRatioInLandscape))
.setFinishPrimaryWithSecondary(finishPrimaryWithSecondary)
.setFinishSecondaryWithPrimary(finishSecondaryWithPrimary)
.setClearTop(clearTop)
.setSplitRatio(ratio)
.setLayoutDirection(layoutDir)
.build()
}
private fun parseSplitPlaceholderRule(
context: Context,
parser: XmlResourceParser
): SplitPlaceholderRule {
val placeholderActivityIntentName: String?
val stickyPlaceholder: Boolean
val finishPrimaryWithPlaceholder: Int
val ratio: Float
val minWidthDp: Int
val minSmallestWidthDp: Int
val maxAspectRatioInPortrait: Float
val maxAspectRatioInLandscape: Float
val layoutDir: Int
context.theme.obtainStyledAttributes(
parser,
R.styleable.SplitPlaceholderRule,
0,
0
).apply {
placeholderActivityIntentName = getString(
R.styleable.SplitPlaceholderRule_placeholderActivityName
)
stickyPlaceholder = getBoolean(R.styleable.SplitPlaceholderRule_stickyPlaceholder,
false)
finishPrimaryWithPlaceholder =
getInt(R.styleable.SplitPlaceholderRule_finishPrimaryWithPlaceholder, FINISH_ALWAYS)
ratio = getFloat(R.styleable.SplitPlaceholderRule_splitRatio, 0.5f)
minWidthDp = getInteger(
R.styleable.SplitPlaceholderRule_splitMinWidthDp,
SplitRule.SPLIT_MIN_DIMENSION_DP_DEFAULT
)
minSmallestWidthDp = getInteger(
R.styleable.SplitPlaceholderRule_splitMinSmallestWidthDp,
SplitRule.SPLIT_MIN_DIMENSION_DP_DEFAULT
)
maxAspectRatioInPortrait = getFloat(
R.styleable.SplitPlaceholderRule_splitMaxAspectRatioInPortrait,
SplitRule.SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT.value
)
maxAspectRatioInLandscape = getFloat(
R.styleable.SplitPlaceholderRule_splitMaxAspectRatioInLandscape,
SplitRule.SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT.value
)
layoutDir = getInt(
R.styleable.SplitPlaceholderRule_splitLayoutDirection,
LayoutDirection.LOCALE
)
}
if (finishPrimaryWithPlaceholder == FINISH_NEVER) {
throw IllegalArgumentException(
"FINISH_NEVER is not a valid configuration for Placeholder activities. " +
"Please use FINISH_ALWAYS or FINISH_ADJACENT instead or refer to the " +
"current API")
}
val packageName = context.applicationContext.packageName
val placeholderActivityClassName = buildClassName(
packageName,
placeholderActivityIntentName
)
return SplitPlaceholderRule.Builder(
emptySet(),
Intent().setComponent(placeholderActivityClassName)
)
.setMinWidthDp(minWidthDp)
.setMinSmallestWidthDp(minSmallestWidthDp)
.setMaxAspectRatioInPortrait(buildAspectRatioFromValue(maxAspectRatioInPortrait))
.setMaxAspectRatioInLandscape(buildAspectRatioFromValue(maxAspectRatioInLandscape))
.setSticky(stickyPlaceholder)
.setFinishPrimaryWithPlaceholder(finishPrimaryWithPlaceholder)
.setSplitRatio(ratio)
.setLayoutDirection(layoutDir)
.build()
}
private fun parseSplitPairFilter(
context: Context,
parser: XmlResourceParser
): SplitPairFilter {
val primaryActivityName: String?
val secondaryActivityIntentName: String?
val secondaryActivityAction: String?
context.theme.obtainStyledAttributes(
parser,
R.styleable.SplitPairFilter,
0,
0
).apply {
primaryActivityName = getString(R.styleable.SplitPairFilter_primaryActivityName)
secondaryActivityIntentName = getString(
R.styleable.SplitPairFilter_secondaryActivityName
)
secondaryActivityAction = getString(
R.styleable.SplitPairFilter_secondaryActivityAction
)
}
val packageName = context.applicationContext.packageName
val primaryActivityClassName = buildClassName(packageName, primaryActivityName)
val secondaryActivityClassName = buildClassName(packageName, secondaryActivityIntentName)
return SplitPairFilter(
primaryActivityClassName,
secondaryActivityClassName,
secondaryActivityAction
)
}
private fun parseActivityRule(
context: Context,
parser: XmlResourceParser
): ActivityRule {
val alwaysExpand: Boolean
context.theme.obtainStyledAttributes(
parser,
R.styleable.ActivityRule,
0,
0
).apply {
alwaysExpand = getBoolean(R.styleable.ActivityRule_alwaysExpand, false)
}
return ActivityRule.Builder(emptySet()).setAlwaysExpand(alwaysExpand).build()
}
private fun parseActivityFilter(
context: Context,
parser: XmlResourceParser
): ActivityFilter {
val activityName: String?
val activityIntentAction: String?
context.theme.obtainStyledAttributes(
parser,
R.styleable.ActivityFilter,
0,
0
).apply {
activityName = getString(R.styleable.ActivityFilter_activityName)
activityIntentAction = getString(R.styleable.ActivityFilter_activityAction)
}
val packageName = context.applicationContext.packageName
return ActivityFilter(
buildClassName(packageName, activityName),
activityIntentAction
)
}
private fun buildClassName(pkg: String, clsSeq: CharSequence?): ComponentName {
if (clsSeq.isNullOrEmpty()) {
throw IllegalArgumentException("Activity name must not be null")
}
val cls = clsSeq.toString()
val c = cls[0]
if (c == '.') {
return ComponentName(pkg, pkg + cls)
}
var pkgString = pkg
var clsString = cls
val pkgDividerIndex = cls.indexOf('/')
if (pkgDividerIndex > 0) {
pkgString = cls.substring(0, pkgDividerIndex)
clsString = cls.substring(pkgDividerIndex + 1)
}
if (clsString != "*" && clsString.indexOf('.') < 0) {
val b = StringBuilder(pkgString)
b.append('.')
b.append(clsString)
return ComponentName(pkgString, b.toString())
}
return ComponentName(pkgString, clsString)
}
}