Commit 20fd772d authored by Evan W. Patton's avatar Evan W. Patton Committed by Jeffrey Schiller

Implement @UsesQueries for SDK 30 <queries> manifest emelent

Change-Id: I2c608f084512171bb91b4c72338c4fd9af4a6304
parent 48dbedf4
......@@ -221,6 +221,8 @@ public final class Compiler {
new ConcurrentHashMap<String, Set<String>>();
private final ConcurrentMap<String, Set<String>> broadcastReceiversNeeded =
new ConcurrentHashMap<String, Set<String>>();
private final ConcurrentMap<String, Set<String>> queriesNeeded =
new ConcurrentHashMap<>();
private final ConcurrentMap<String, Set<String>> servicesNeeded =
new ConcurrentHashMap<String, Set<String>>();
private final ConcurrentMap<String, Set<String>> contentProvidersNeeded =
......@@ -480,6 +482,11 @@ public final class Compiler {
return broadcastReceiversNeeded;
}
@VisibleForTesting
Map<String, Set<String>> getQueries() {
return queriesNeeded;
}
// Just used for testing
@VisibleForTesting
Map<String, Set<String>> getServices() {
......@@ -672,6 +679,24 @@ public final class Compiler {
mergeConditionals(conditionals.get(ComponentDescriptorConstants.BROADCAST_RECEIVERS_TARGET), broadcastReceiversNeeded);
}
/*
* Generate a set of conditionally included queries needed by this project.
*/
@VisibleForTesting
void generateQueries() {
try {
loadJsonInfo(queriesNeeded, ComponentDescriptorConstants.QUERIES_TARGET);
} catch (IOException e) {
// This is fatal.
userErrors.print(String.format(ERROR_IN_STAGE, "Services"));
} catch (JSONException e) {
// This is fatal, but shouldn't actually ever happen.
userErrors.print(String.format(ERROR_IN_STAGE, "Services"));
}
mergeConditionals(conditionals.get(ComponentDescriptorConstants.QUERIES_TARGET), queriesNeeded);
}
/*
* Generate a set of conditionally included services needed by this project.
*/
......@@ -1056,6 +1081,18 @@ public final class Compiler {
}
}
if (queriesNeeded.size() > 0) {
out.write(" <queries>\n");
for (Map.Entry<String, Set<String>> componentSubElSetPair : queriesNeeded.entrySet()) {
Set<String> subelementSet = componentSubElSetPair.getValue();
for (String subelement : subelementSet) {
// replace %packageName% with the actual packageName
out.write(subelement.replace("%packageName%", packageName));
}
}
out.write(" </queries>\n");
}
int minSdk = Integer.parseInt((project.getMinSdk() == null) ? DEFAULT_MIN_SDK : project.getMinSdk());
if (!isForCompanion) {
for (Set<String> minSdks : minSdksNeeded.values()) {
......@@ -1390,28 +1427,30 @@ public final class Compiler {
reporter.report(0);
}
statReporter.nextStage(compiler, "generateAssets");
compiler.generateAssets();
statReporter.nextStage(compiler, "generateActivities");
compiler.generateActivities();
statReporter.nextStage(compiler, "generateMetadata");
compiler.generateMetadata();
statReporter.nextStage(compiler, "generateActivityMetadata");
compiler.generateActivityMetadata();
statReporter.nextStage(compiler, "generateAssets");
compiler.generateAssets();
statReporter.nextStage(compiler, "generateBroadcastReceivers");
compiler.generateBroadcastReceivers();
statReporter.nextStage(compiler, "generateServices");
compiler.generateServices();
statReporter.nextStage(compiler, "generateContentProviders");
compiler.generateContentProviders();
statReporter.nextStage(compiler, "generateLibNames");
compiler.generateLibNames();
statReporter.nextStage(compiler, "generateMetadata");
compiler.generateMetadata();
statReporter.nextStage(compiler, "generateMinSdks");
compiler.generateMinSdks();
statReporter.nextStage(compiler, "generateNativeLibNames");
compiler.generateNativeLibNames();
statReporter.nextStage(compiler, "generatePermissions");
compiler.generatePermissions();
statReporter.nextStage(compiler, "generateMinSdks");
compiler.generateMinSdks();
statReporter.nextStage(compiler, "generateQueries");
compiler.generateQueries();
statReporter.nextStage(compiler, "generateServices");
compiler.generateServices();
// TODO(Will): Remove the following call once the deprecated
// @SimpleBroadcastReceiver annotation is removed. It should
......
// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2021 MIT, All rights reserved
// Released under the Apache License, Version 2.0
// http://www.apache.org/licenses/LICENSE-2.0
package com.google.appinventor.components.annotations;
import com.google.appinventor.components.annotations.androidmanifest.IntentFilterElement;
import com.google.appinventor.components.annotations.androidmanifest.ProviderElement;
/**
* Annotation to describe a &lt;queries&gt; entry required by Android SDK 30.
*/
public @interface UsesQueries {
/**
* An array of intents that will be included in the &lt;queries&gt; element.
*
* @return the array of intents of interest
*/
IntentFilterElement[] intents() default {};
/**
* A package name that will be included in the &lt;queries&gt; element.
*
* @return the array containing package names of interest
*/
String[] packageNames() default {};
/**
* A provider element that will be included in the &lt;queries&gt;
*
* @return the array containing provider elements of interest
*/
ProviderElement[] providers() default {};
}
......@@ -26,6 +26,7 @@ public final class ComponentDescriptorConstants {
public static final String NATIVE_TARGET = "native";
public static final String PERMISSIONS_TARGET = "permissions";
public static final String BROADCAST_RECEIVERS_TARGET = "broadcastReceivers";
public static final String QUERIES_TARGET = "queries";
public static final String SERVICES_TARGET = "services";
public static final String CONTENT_PROVIDERS_TARGET = "contentProviders";
public static final String ANDROIDMINSDK_TARGET = "androidMinSdk";
......
......@@ -250,10 +250,11 @@ public final class ComponentDescriptorGenerator extends ComponentProcessor {
* @param sb The StringBuilder to receive the JSON descriptor.
*/
private void outputConditionalAnnotations(ComponentInfo component, StringBuilder sb) {
if (component.conditionalPermissions.size() +
component.conditionalBroadcastReceivers.size() +
component.conditionalServices.size() +
component.conditionalContentProviders.size() == 0) {
if (component.conditionalBroadcastReceivers.size()
+ component.conditionalContentProviders.size()
+ component.conditionalPermissions.size()
+ component.conditionalQueries.size()
+ component.conditionalServices.size() == 0) {
return;
}
sb.append(",\n \"conditionals\":{\n ");
......@@ -269,6 +270,14 @@ public final class ComponentDescriptorGenerator extends ComponentProcessor {
outputMultimap(sb, " ", component.conditionalBroadcastReceivers);
first = false;
}
if (component.conditionalQueries.size() > 0) {
if (!first) {
sb.append(",\n ");
}
sb.append("\"queries\": ");
outputMultimap(sb, " ", component.conditionalQueries);
first = false;
}
if (component.conditionalServices.size() > 0) {
if (!first) sb.append(",\n ");
sb.append("\"services\": ");
......
......@@ -85,6 +85,7 @@ public final class ComponentListGenerator extends ComponentProcessor {
appendComponentInfo(sb, ComponentDescriptorConstants.ACTIVITY_METADATA_TARGET, component.activityMetadata);
appendComponentInfo(sb, ComponentDescriptorConstants.ANDROIDMINSDK_TARGET, Collections.singleton(Integer.toString(component.getAndroidMinSdk())));
appendComponentInfo(sb, ComponentDescriptorConstants.BROADCAST_RECEIVERS_TARGET, component.broadcastReceivers);
appendComponentInfo(sb, ComponentDescriptorConstants.QUERIES_TARGET, component.queries);
appendComponentInfo(sb, ComponentDescriptorConstants.SERVICES_TARGET, component.services);
appendComponentInfo(sb, ComponentDescriptorConstants.CONTENT_PROVIDERS_TARGET, component.contentProviders);
appendConditionalComponentInfo(component, sb);
......@@ -104,10 +105,11 @@ public final class ComponentListGenerator extends ComponentProcessor {
* @param sb Target StringBuilder to receive the conditional description
*/
private static void appendConditionalComponentInfo(ComponentInfo component, StringBuilder sb) {
if (component.conditionalPermissions.size() +
component.conditionalBroadcastReceivers.size() +
component.conditionalServices.size() +
component.conditionalContentProviders.size() == 0) {
if (component.conditionalBroadcastReceivers.size()
+ component.conditionalContentProviders.size()
+ component.conditionalPermissions.size()
+ component.conditionalQueries.size()
+ component.conditionalServices.size() == 0) {
return;
}
sb.append(", \"" + ComponentDescriptorConstants.CONDITIONALS_TARGET + "\": { ");
......@@ -115,6 +117,8 @@ public final class ComponentListGenerator extends ComponentProcessor {
appendMap(sb, component.conditionalPermissions);
sb.append(", \"" + ComponentDescriptorConstants.BROADCAST_RECEIVERS_TARGET + "\": ");
appendMap(sb, component.conditionalBroadcastReceivers);
sb.append(", \"" + ComponentDescriptorConstants.QUERIES_TARGET + "\": ");
appendMap(sb, component.conditionalQueries);
sb.append(", \"" + ComponentDescriptorConstants.SERVICES_TARGET + "\": ");
appendMap(sb, component.conditionalServices);
sb.append(", \"" + ComponentDescriptorConstants.CONTENT_PROVIDERS_TARGET + "\": ");
......
......@@ -24,6 +24,7 @@ import com.google.appinventor.components.annotations.UsesPermissions;
import com.google.appinventor.components.annotations.UsesActivities;
import com.google.appinventor.components.annotations.UsesBroadcastReceivers;
import com.google.appinventor.components.annotations.UsesContentProviders;
import com.google.appinventor.components.annotations.UsesQueries;
import com.google.appinventor.components.annotations.UsesServices;
import com.google.appinventor.components.annotations.androidmanifest.ActivityElement;
import com.google.appinventor.components.annotations.androidmanifest.ReceiverElement;
......@@ -160,6 +161,7 @@ public abstract class ComponentProcessor extends AbstractProcessor {
"com.google.appinventor.components.annotations.UsesActivities",
"com.google.appinventor.components.annotations.UsesBroadcastReceivers",
"com.google.appinventor.components.annotations.UsesPermissions",
"com.google.appinventor.components.annotations.UsesQueries",
"com.google.appinventor.components.annotations.UsesServices",
"com.google.appinventor.components.annotations.UsesContentProviders");
......@@ -1086,6 +1088,12 @@ public abstract class ComponentProcessor extends AbstractProcessor {
*/
protected final Map<String, String[]> conditionalBroadcastReceivers;
/**
* Mapping of component block names to queries that should be included
* if the block is used.
*/
protected final Map<String, String[]> conditionalQueries;
/**
* Mapping of component block names to services that should be
* included if the block is used.
......@@ -1133,6 +1141,11 @@ public abstract class ComponentProcessor extends AbstractProcessor {
*/
protected final Set<String> broadcastReceivers;
/**
* Queries required by this component.
*/
protected final Set<String> queries;
/**
* Services required by this component.
*/
......@@ -1214,21 +1227,26 @@ public abstract class ComponentProcessor extends AbstractProcessor {
"Component", false, elementUtils.isDeprecated(element));
type = element.asType().toString();
displayName = getDisplayNameForComponentType(name);
permissions = Sets.newHashSet();
conditionalPermissions = Maps.newTreeMap();
conditionalBroadcastReceivers = Maps.newTreeMap();
conditionalServices = Maps.newTreeMap();
conditionalContentProviders = Maps.newTreeMap();
libraries = Sets.newHashSet();
nativeLibraries = Sets.newHashSet();
conditionalPermissions = Maps.newTreeMap();
conditionalQueries = Maps.newTreeMap();
conditionalServices = Maps.newTreeMap();
assets = Sets.newHashSet();
activities = Sets.newHashSet();
metadata = Sets.newHashSet();
activityMetadata = Sets.newHashSet();
broadcastReceivers = Sets.newHashSet();
services = Sets.newHashSet();
contentProviders = Sets.newHashSet();
classNameAndActionsBR = Sets.newHashSet();
contentProviders = Sets.newHashSet();
libraries = Sets.newHashSet();
metadata = Sets.newHashSet();
nativeLibraries = Sets.newHashSet();
permissions = Sets.newHashSet();
queries = Sets.newHashSet();
services = Sets.newHashSet();
designerProperties = Maps.newTreeMap();
properties = Maps.newTreeMap();
methods = Maps.newTreeMap();
......@@ -1587,6 +1605,7 @@ public abstract class ComponentProcessor extends AbstractProcessor {
componentInfo.metadata.addAll(parentComponent.metadata);
componentInfo.activityMetadata.addAll(parentComponent.activityMetadata);
componentInfo.broadcastReceivers.addAll(parentComponent.broadcastReceivers);
componentInfo.queries.addAll(parentComponent.queries);
componentInfo.services.addAll(parentComponent.services);
componentInfo.contentProviders.addAll(parentComponent.contentProviders);
// TODO(Will): Remove the following call once the deprecated
......@@ -1730,6 +1749,30 @@ public abstract class ComponentProcessor extends AbstractProcessor {
}
}
// Gather the required queries and build their element strings.
UsesQueries usesQueries = element.getAnnotation(UsesQueries.class);
if (usesQueries != null) {
try {
for (String packageName : usesQueries.packageNames()) {
componentInfo.queries.add("<package android:name=\"" + packageName + "\" />");
}
for (IntentFilterElement intent : usesQueries.intents()) {
updateWithNonEmptyValue(componentInfo.queries, intentFilterElementToIntentString(intent));
}
for (ProviderElement provider : usesQueries.providers()) {
updateWithNonEmptyValue(componentInfo.queries, providerElementToString(provider));
}
} catch (IllegalAccessException e) {
messager.printMessage(Diagnostic.Kind.ERROR, "IllegalAccessException when gathering "
+ "service attributes and subelements for component " + componentInfo.name);
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
messager.printMessage(Diagnostic.Kind.ERROR, "InvocationTargetException when gathering "
+ "service attributes and subelements for component " + componentInfo.name);
throw new RuntimeException(e);
}
}
// Gather the required services and build their element strings.
UsesServices usesServices = element.getAnnotation(UsesServices.class);
if (usesServices != null) {
......@@ -2279,6 +2322,21 @@ public abstract class ComponentProcessor extends AbstractProcessor {
return elementString.append(" </intent-filter>\\n").toString();
}
private static String intentFilterElementToIntentString(IntentFilterElement element)
throws IllegalAccessException, InvocationTargetException {
// First, we build the <intent-filter> element's opening tag including any
// receiver element attributes.
StringBuilder elementString = new StringBuilder(" <intent>\\n");
// Now, we collect any <intent-filter> subelements.
elementString.append(subelementsToString(element.actionElements()));
elementString.append(subelementsToString(element.categoryElements()));
elementString.append(subelementsToString(element.dataElements()));
// Finally, we close the <intent-filter> element and create its String.
return elementString.append(" </intent>\\n").toString();
}
// Transform an @ActionElement into an XML element String for use later
// in creating AndroidManifest.xml.
private static String actionElementToString(ActionElement element)
......@@ -2686,6 +2744,32 @@ public abstract class ComponentProcessor extends AbstractProcessor {
}
}
// Gather the required queries and build their element strings.
UsesQueries usesQueries = element.getAnnotation(UsesQueries.class);
if (usesQueries != null) {
try {
Set<String> queries = new HashSet<>();
for (String packageName : usesQueries.packageNames()) {
updateWithNonEmptyValue(queries, "<package android:name=\"" + packageName + "\" />");
}
for (IntentFilterElement intent : usesQueries.intents()) {
updateWithNonEmptyValue(queries, intentFilterElementToIntentString(intent));
}
for (ProviderElement provider : usesQueries.providers()) {
updateWithNonEmptyValue(queries, providerElementToString(provider));
}
componentInfo.conditionalQueries.put(blockName, queries.toArray(new String[0]));
} catch (IllegalAccessException e) {
messager.printMessage(Diagnostic.Kind.ERROR, "IllegalAccessException when gathering "
+ "service attributes and subelements for component " + componentInfo.name);
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
messager.printMessage(Diagnostic.Kind.ERROR, "InvocationTargetException when gathering "
+ "service attributes and subelements for component " + componentInfo.name);
throw new RuntimeException(e);
}
}
UsesServices service = element.getAnnotation(UsesServices.class);
if (service != null) {
try {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment