use of com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoElement in project bundletool by google.
the class ManifestDeliveryElementTest method moduleConditions_missingNameOfFeature_throws.
@Test
public void moduleConditions_missingNameOfFeature_throws() throws Exception {
// Name attribute doesn't use distribution namespace.
XmlProtoElement badCondition = XmlProtoElementBuilder.create(DISTRIBUTION_NAMESPACE_URI, "device-feature").addAttribute(XmlProtoAttributeBuilder.create("name").setValueAsString("android.hardware.camera.ar")).build();
Optional<ManifestDeliveryElement> manifestDeliveryElement = ManifestDeliveryElement.fromManifestRootNode(createAndroidManifestWithConditions(badCondition), /* isFastFollowAllowed= */
false);
assertThat(manifestDeliveryElement).isPresent();
Throwable exception = assertThrows(InvalidBundleException.class, () -> manifestDeliveryElement.get().getModuleConditions());
assertThat(exception).hasMessageThat().contains("Missing required 'dist:name' attribute in the 'device-feature' condition element.");
}
use of com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoElement in project bundletool by google.
the class ManifestDeliveryElementTest method moduleConditions_wrongElementInsideDeviceGroupsCondition_throws.
@Test
public void moduleConditions_wrongElementInsideDeviceGroupsCondition_throws() {
XmlProtoElement badDeviceGroupCondition = XmlProtoElementBuilder.create(DISTRIBUTION_NAMESPACE_URI, "device-groups").addChildElement(XmlProtoElementBuilder.create(DISTRIBUTION_NAMESPACE_URI, "wrong")).build();
Optional<ManifestDeliveryElement> element = ManifestDeliveryElement.fromManifestRootNode(createAndroidManifestWithConditions(badDeviceGroupCondition), /* isFastFollowAllowed= */
false);
assertThat(element).isPresent();
InvalidBundleException exception = assertThrows(InvalidBundleException.class, () -> element.get().getModuleConditions());
assertThat(exception).hasMessageThat().contains("Expected only '<dist:device-group>' elements inside '<dist:device-groups>', but found" + " 'wrong'");
}
use of com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoElement in project bundletool by google.
the class ManifestDeliveryElement method getModuleConditions.
/**
* Returns all module conditions.
*
* <p>We support <dist:min-sdk-version>, <dist:device-feature> and <dist:user-countries>
* conditions today. Any other conditions types are not supported and will result in {@link
* InvalidBundleException}.
*/
@Memoized
public ModuleConditions getModuleConditions() {
ImmutableList<XmlProtoElement> conditionElements = getModuleConditionElements();
ImmutableMap<String, Long> conditionCounts = conditionElements.stream().collect(groupingByDeterministic(XmlProtoElement::getName, counting()));
for (String conditionName : CONDITIONS_ALLOWED_ONLY_ONCE) {
if (conditionCounts.getOrDefault(conditionName, 0L) > 1) {
throw InvalidBundleException.builder().withUserMessage("Multiple '<dist:%s>' conditions are not supported.", conditionName).build();
}
}
ModuleConditions.Builder moduleConditions = ModuleConditions.builder();
for (XmlProtoElement conditionElement : conditionElements) {
if (!conditionElement.getNamespaceUri().equals(DISTRIBUTION_NAMESPACE_URI)) {
throw InvalidBundleException.builder().withUserMessage("Invalid namespace found in the module condition element. " + "Expected '%s'; found '%s'.", DISTRIBUTION_NAMESPACE_URI, conditionElement.getNamespaceUri()).build();
}
switch(conditionElement.getName()) {
case CONDITION_DEVICE_FEATURE_NAME:
moduleConditions.addDeviceFeatureCondition(parseDeviceFeatureCondition(conditionElement));
break;
case CONDITION_MIN_SDK_VERSION_NAME:
moduleConditions.setMinSdkVersion(parseMinSdkVersionCondition(conditionElement));
break;
case CONDITION_MAX_SDK_VERSION_NAME:
moduleConditions.setMaxSdkVersion(parseMaxSdkVersionCondition(conditionElement));
break;
case CONDITION_USER_COUNTRIES_NAME:
moduleConditions.setUserCountriesCondition(parseUserCountriesCondition(conditionElement));
break;
case CONDITION_DEVICE_GROUPS_NAME:
moduleConditions.setDeviceGroupsCondition(parseDeviceGroupsCondition(conditionElement));
break;
default:
throw InvalidBundleException.builder().withUserMessage("Unrecognized module condition: '%s'", conditionElement.getName()).build();
}
}
ModuleConditions processedModuleConditions = moduleConditions.build();
if (processedModuleConditions.getMinSdkVersion().isPresent() && processedModuleConditions.getMaxSdkVersion().isPresent()) {
if (processedModuleConditions.getMinSdkVersion().get() > processedModuleConditions.getMaxSdkVersion().get()) {
throw InvalidBundleException.builder().withUserMessage("Illegal SDK-based conditional module targeting (min SDK must be less than or" + " equal to max SD). Provided min and max values, respectively, are %s and %s", processedModuleConditions.getMinSdkVersion(), processedModuleConditions.getMaxSdkVersion()).build();
}
}
return processedModuleConditions;
}
use of com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoElement in project bundletool by google.
the class FusingAndroidManifestMerger method mergeElements.
/**
* Merges element with the same name and type from different manifests. {@code elements} list in
* this method contains data from feature modules first and element from the base module is the
* last one in the list.
*
* <p>If element is presented in more than one feature module elements are sorted by name of
* feature module. Example: if service with name 'myService' is defined in base module and
* features 'a' and 'b', {@code elements} list will contain its declaration in the following
* order: 'a' feature, 'b' feature, base.
*/
private XmlProtoElement mergeElements(ApplicationElementId elementId, List<XmlProtoElement> elements) {
// we just take the first element from the list.
if (mode.equals(Mode.REPLACE) || elements.size() == 1) {
return elements.get(0);
}
// Remove source data from nested elements as this data is meaningless for functionality and
// just contains information about line/column where element appeared in the original xml.
List<XmlProtoElement> elementsNoSource = elements.stream().map(element -> element.toBuilder().removeSourceDataRecursive().build()).collect(toImmutableList());
// For intent filters we gather all distinct filters defined in all modules.
Set<XmlProtoElement> intentFilters = elementsNoSource.stream().flatMap(element -> element.getChildrenElements(AndroidManifest.INTENT_FILTER_ELEMENT_NAME)).collect(toImmutableSet());
// For meta-data we group them by name and take one per each name.
ImmutableMap<String, XmlProtoElement> metadataByName = elementsNoSource.stream().flatMap(element -> element.getChildrenElements(AndroidManifest.META_DATA_ELEMENT_NAME)).filter(meta -> meta.getAndroidAttribute(AndroidManifest.NAME_RESOURCE_ID).isPresent()).collect(toImmutableMap(meta -> meta.getAndroidAttribute(AndroidManifest.NAME_RESOURCE_ID).get().getValueAsString(), Function.identity(), (a, b) -> {
// different modules throw conflict exception.
if (!a.equals(b)) {
throw CommandExecutionException.builder().withInternalMessage("Multiple meta-data entries with the same name are found inside" + " %s:%s: %s, %s", elementId.getType(), elementId.getName(), a, b).build();
}
return a;
}));
// Take element declaration from feature module and add all intent filters and meta data to it.
XmlProtoElementBuilder builder = elementsNoSource.get(0).toBuilder();
builder.removeChildrenElementsIf(child -> {
if (!child.isElement()) {
return false;
}
XmlProtoElementBuilder childElement = child.getElement();
String tag = childElement.getName();
Optional<String> name = childElement.getAndroidAttribute(AndroidManifest.NAME_RESOURCE_ID).map(XmlProtoAttributeBuilder::getValueAsString);
return tag.equals(AndroidManifest.INTENT_FILTER_ELEMENT_NAME) || (tag.equals(AndroidManifest.META_DATA_ELEMENT_NAME) && name.map(metadataByName::containsKey).orElse(false));
});
intentFilters.forEach(e -> builder.addChildElement(e.toBuilder()));
metadataByName.values().forEach(e -> builder.addChildElement(e.toBuilder()));
return builder.build();
}
use of com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoElement in project bundletool by google.
the class WearApkLocator method extractWearApkName.
/**
* Parses the XML description file for the name of the wear APK.
*
* <p>According to
* https://developer.android.com/training/wearables/apps/packaging#PackageManually, it is the
* value inside the tag <rawPathResId>.
*/
private static Optional<String> extractWearApkName(ModuleEntry wearApkDescriptionXmlEntry) {
XmlProtoNode root;
try (InputStream content = wearApkDescriptionXmlEntry.getContent().openStream()) {
root = new XmlProtoNode(XmlNode.parseFrom(content));
} catch (InvalidProtocolBufferException e) {
throw InvalidBundleException.builder().withCause(e).withUserMessage("The wear APK description file '%s' could not be parsed.", wearApkDescriptionXmlEntry.getPath()).build();
} catch (IOException e) {
throw new UncheckedIOException(String.format("An unexpected error occurred while reading APK description file '%s'.", wearApkDescriptionXmlEntry.getPath()), e);
}
// If the Wear APK is unbundled, there is nothing to find.
if (root.getElement().getOptionalChildElement("unbundled").isPresent()) {
return Optional.empty();
}
// If 'unbundled' is not present, then 'rawPathResId' must be.
Optional<XmlProtoElement> rawPathResId = root.getElement().getOptionalChildElement("rawPathResId");
if (!rawPathResId.isPresent()) {
throw InvalidBundleException.builder().withUserMessage("The wear APK description file '%s' does not contain 'unbundled' or 'rawPathResId'.", wearApkDescriptionXmlEntry.getPath()).build();
}
return Optional.of(rawPathResId.get().getChildText().get().getText());
}
Aggregations