blob: 215f8e77212b95ac1c0349edb2fd3d2d65cd2a9e [file] [log] [blame]
/*
* Copyright (C) 2018 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.manifmerger
import com.android.SdkConstants.ANDROID_URI
import com.android.SdkConstants.ATTR_NAME
import com.android.SdkConstants
import com.google.common.base.Joiner
import com.google.common.collect.ImmutableList
import java.util.ArrayList
import java.util.Collections
import org.w3c.dom.Element
import org.w3c.dom.Node
/**
* Specific [NodeKeyResolver] for intent-filter and intent elements. These two elements do not have
* a proper key, therefore their identity is really carried by the presence of their sub-elements.
* We concatenate the sub-elements' attributes' names and values (after sorting them to work around
* declaration order) and use that for the intent-filter unique key. An example key might look like
* "action:name:android.intent.action.VIEW+category:name:android.intent.category.DEFAULT+data:host:www.example.com"
*/
internal open class CompositeNodeKeyResolver(val nodeType: String) : NodeKeyResolver {
private val model = ManifestModel()
private val dataAttributeNames = ImmutableList.of(
SdkConstants.ATTR_SCHEME,
SdkConstants.ATTR_HOST,
SdkConstants.ATTR_MIME_TYPE,
SdkConstants.ATTR_PORT,
SdkConstants.ATTR_PATH,
SdkConstants.ATTR_PATH_PATTERN,
SdkConstants.ATTR_PATH_PREFIX)
override val keyAttributesNames: ImmutableList<String>
get() {
val builder = ImmutableList.builder<String>()
builder.add("action#name", "category#name")
for (dataAttributeName in dataAttributeNames) {
builder.add("data#$dataAttributeName")
}
return builder.build()
}
override fun getKey(element: Element): String? {
val xmlElement = OrphanXmlElement(element, model)
assert(xmlElement.name.localName == nodeType)
// concatenate attribute info for action, category, and data sub-elements.
val subElementAttributes = ArrayList<String>()
val childNodes = element.childNodes
for (i in 0 until childNodes.length) {
val child = childNodes.item(i)
if (child.nodeType != Node.ELEMENT_NODE) continue
val subElement = OrphanXmlElement(child as Element, model)
if (subElement.type == ManifestModel.NodeTypes.ACTION ||
subElement.type == ManifestModel.NodeTypes.CATEGORY) {
val attributeInfo = getAttributeInfo(subElement, ATTR_NAME)
if (attributeInfo != null) {
subElementAttributes.add(attributeInfo)
}
} else if (subElement.type == ManifestModel.NodeTypes.DATA) {
for (dataAttributeName in dataAttributeNames) {
val attributeInfo = getAttributeInfo(subElement, dataAttributeName)
if (attributeInfo != null) {
subElementAttributes.add(attributeInfo)
}
}
}
}
subElementAttributes.sort()
return Joiner.on('+').join(subElementAttributes)
}
private fun getAttributeInfo(
xmlElement: OrphanXmlElement, attributeName: String): String? {
val element = xmlElement.xml
val attr = element.getAttributeNodeNS(ANDROID_URI, attributeName)
return if (attr == null) {
null
} else {
element.tagName + ":" + attributeName + ":" + attr.value
}
}
}
internal object IntentNodeKeyResolver: CompositeNodeKeyResolver(SdkConstants.TAG_INTENT)
internal object IntentFilterNodeKeyResolver: CompositeNodeKeyResolver(SdkConstants.TAG_INTENT_FILTER)