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 { ...@@ -221,6 +221,8 @@ public final class Compiler {
new ConcurrentHashMap<String, Set<String>>(); new ConcurrentHashMap<String, Set<String>>();
private final ConcurrentMap<String, Set<String>> broadcastReceiversNeeded = private final ConcurrentMap<String, Set<String>> broadcastReceiversNeeded =
new ConcurrentHashMap<String, Set<String>>(); new ConcurrentHashMap<String, Set<String>>();
private final ConcurrentMap<String, Set<String>> queriesNeeded =
new ConcurrentHashMap<>();
private final ConcurrentMap<String, Set<String>> servicesNeeded = private final ConcurrentMap<String, Set<String>> servicesNeeded =
new ConcurrentHashMap<String, Set<String>>(); new ConcurrentHashMap<String, Set<String>>();
private final ConcurrentMap<String, Set<String>> contentProvidersNeeded = private final ConcurrentMap<String, Set<String>> contentProvidersNeeded =
...@@ -480,6 +482,11 @@ public final class Compiler { ...@@ -480,6 +482,11 @@ public final class Compiler {
return broadcastReceiversNeeded; return broadcastReceiversNeeded;
} }
@VisibleForTesting
Map<String, Set<String>> getQueries() {
return queriesNeeded;
}
// Just used for testing // Just used for testing
@VisibleForTesting @VisibleForTesting
Map<String, Set<String>> getServices() { Map<String, Set<String>> getServices() {
...@@ -672,6 +679,24 @@ public final class Compiler { ...@@ -672,6 +679,24 @@ public final class Compiler {
mergeConditionals(conditionals.get(ComponentDescriptorConstants.BROADCAST_RECEIVERS_TARGET), broadcastReceiversNeeded); 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. * Generate a set of conditionally included services needed by this project.
*/ */
...@@ -1056,6 +1081,18 @@ public final class Compiler { ...@@ -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()); int minSdk = Integer.parseInt((project.getMinSdk() == null) ? DEFAULT_MIN_SDK : project.getMinSdk());
if (!isForCompanion) { if (!isForCompanion) {
for (Set<String> minSdks : minSdksNeeded.values()) { for (Set<String> minSdks : minSdksNeeded.values()) {
...@@ -1390,28 +1427,30 @@ public final class Compiler { ...@@ -1390,28 +1427,30 @@ public final class Compiler {
reporter.report(0); reporter.report(0);
} }
statReporter.nextStage(compiler, "generateAssets");
compiler.generateAssets();
statReporter.nextStage(compiler, "generateActivities"); statReporter.nextStage(compiler, "generateActivities");
compiler.generateActivities(); compiler.generateActivities();
statReporter.nextStage(compiler, "generateMetadata");
compiler.generateMetadata();
statReporter.nextStage(compiler, "generateActivityMetadata"); statReporter.nextStage(compiler, "generateActivityMetadata");
compiler.generateActivityMetadata(); compiler.generateActivityMetadata();
statReporter.nextStage(compiler, "generateAssets");
compiler.generateAssets();
statReporter.nextStage(compiler, "generateBroadcastReceivers"); statReporter.nextStage(compiler, "generateBroadcastReceivers");
compiler.generateBroadcastReceivers(); compiler.generateBroadcastReceivers();
statReporter.nextStage(compiler, "generateServices");
compiler.generateServices();
statReporter.nextStage(compiler, "generateContentProviders"); statReporter.nextStage(compiler, "generateContentProviders");
compiler.generateContentProviders(); compiler.generateContentProviders();
statReporter.nextStage(compiler, "generateLibNames"); statReporter.nextStage(compiler, "generateLibNames");
compiler.generateLibNames(); compiler.generateLibNames();
statReporter.nextStage(compiler, "generateMetadata");
compiler.generateMetadata();
statReporter.nextStage(compiler, "generateMinSdks");
compiler.generateMinSdks();
statReporter.nextStage(compiler, "generateNativeLibNames"); statReporter.nextStage(compiler, "generateNativeLibNames");
compiler.generateNativeLibNames(); compiler.generateNativeLibNames();
statReporter.nextStage(compiler, "generatePermissions"); statReporter.nextStage(compiler, "generatePermissions");
compiler.generatePermissions(); compiler.generatePermissions();
statReporter.nextStage(compiler, "generateMinSdks"); statReporter.nextStage(compiler, "generateQueries");
compiler.generateMinSdks(); compiler.generateQueries();
statReporter.nextStage(compiler, "generateServices");
compiler.generateServices();
// TODO(Will): Remove the following call once the deprecated // TODO(Will): Remove the following call once the deprecated
// @SimpleBroadcastReceiver annotation is removed. It should // @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 { ...@@ -26,6 +26,7 @@ public final class ComponentDescriptorConstants {
public static final String NATIVE_TARGET = "native"; public static final String NATIVE_TARGET = "native";
public static final String PERMISSIONS_TARGET = "permissions"; public static final String PERMISSIONS_TARGET = "permissions";
public static final String BROADCAST_RECEIVERS_TARGET = "broadcastReceivers"; 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 SERVICES_TARGET = "services";
public static final String CONTENT_PROVIDERS_TARGET = "contentProviders"; public static final String CONTENT_PROVIDERS_TARGET = "contentProviders";
public static final String ANDROIDMINSDK_TARGET = "androidMinSdk"; public static final String ANDROIDMINSDK_TARGET = "androidMinSdk";
......
...@@ -250,10 +250,11 @@ public final class ComponentDescriptorGenerator extends ComponentProcessor { ...@@ -250,10 +250,11 @@ public final class ComponentDescriptorGenerator extends ComponentProcessor {
* @param sb The StringBuilder to receive the JSON descriptor. * @param sb The StringBuilder to receive the JSON descriptor.
*/ */
private void outputConditionalAnnotations(ComponentInfo component, StringBuilder sb) { private void outputConditionalAnnotations(ComponentInfo component, StringBuilder sb) {
if (component.conditionalPermissions.size() + if (component.conditionalBroadcastReceivers.size()
component.conditionalBroadcastReceivers.size() + + component.conditionalContentProviders.size()
component.conditionalServices.size() + + component.conditionalPermissions.size()
component.conditionalContentProviders.size() == 0) { + component.conditionalQueries.size()
+ component.conditionalServices.size() == 0) {
return; return;
} }
sb.append(",\n \"conditionals\":{\n "); sb.append(",\n \"conditionals\":{\n ");
...@@ -269,6 +270,14 @@ public final class ComponentDescriptorGenerator extends ComponentProcessor { ...@@ -269,6 +270,14 @@ public final class ComponentDescriptorGenerator extends ComponentProcessor {
outputMultimap(sb, " ", component.conditionalBroadcastReceivers); outputMultimap(sb, " ", component.conditionalBroadcastReceivers);
first = false; 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 (component.conditionalServices.size() > 0) {
if (!first) sb.append(",\n "); if (!first) sb.append(",\n ");
sb.append("\"services\": "); sb.append("\"services\": ");
......
...@@ -85,6 +85,7 @@ public final class ComponentListGenerator extends ComponentProcessor { ...@@ -85,6 +85,7 @@ public final class ComponentListGenerator extends ComponentProcessor {
appendComponentInfo(sb, ComponentDescriptorConstants.ACTIVITY_METADATA_TARGET, component.activityMetadata); appendComponentInfo(sb, ComponentDescriptorConstants.ACTIVITY_METADATA_TARGET, component.activityMetadata);
appendComponentInfo(sb, ComponentDescriptorConstants.ANDROIDMINSDK_TARGET, Collections.singleton(Integer.toString(component.getAndroidMinSdk()))); appendComponentInfo(sb, ComponentDescriptorConstants.ANDROIDMINSDK_TARGET, Collections.singleton(Integer.toString(component.getAndroidMinSdk())));
appendComponentInfo(sb, ComponentDescriptorConstants.BROADCAST_RECEIVERS_TARGET, component.broadcastReceivers); 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.SERVICES_TARGET, component.services);
appendComponentInfo(sb, ComponentDescriptorConstants.CONTENT_PROVIDERS_TARGET, component.contentProviders); appendComponentInfo(sb, ComponentDescriptorConstants.CONTENT_PROVIDERS_TARGET, component.contentProviders);
appendConditionalComponentInfo(component, sb); appendConditionalComponentInfo(component, sb);
...@@ -104,10 +105,11 @@ public final class ComponentListGenerator extends ComponentProcessor { ...@@ -104,10 +105,11 @@ public final class ComponentListGenerator extends ComponentProcessor {
* @param sb Target StringBuilder to receive the conditional description * @param sb Target StringBuilder to receive the conditional description
*/ */
private static void appendConditionalComponentInfo(ComponentInfo component, StringBuilder sb) { private static void appendConditionalComponentInfo(ComponentInfo component, StringBuilder sb) {
if (component.conditionalPermissions.size() + if (component.conditionalBroadcastReceivers.size()
component.conditionalBroadcastReceivers.size() + + component.conditionalContentProviders.size()
component.conditionalServices.size() + + component.conditionalPermissions.size()
component.conditionalContentProviders.size() == 0) { + component.conditionalQueries.size()
+ component.conditionalServices.size() == 0) {
return; return;
} }
sb.append(", \"" + ComponentDescriptorConstants.CONDITIONALS_TARGET + "\": { "); sb.append(", \"" + ComponentDescriptorConstants.CONDITIONALS_TARGET + "\": { ");
...@@ -115,6 +117,8 @@ public final class ComponentListGenerator extends ComponentProcessor { ...@@ -115,6 +117,8 @@ public final class ComponentListGenerator extends ComponentProcessor {
appendMap(sb, component.conditionalPermissions); appendMap(sb, component.conditionalPermissions);
sb.append(", \"" + ComponentDescriptorConstants.BROADCAST_RECEIVERS_TARGET + "\": "); sb.append(", \"" + ComponentDescriptorConstants.BROADCAST_RECEIVERS_TARGET + "\": ");
appendMap(sb, component.conditionalBroadcastReceivers); appendMap(sb, component.conditionalBroadcastReceivers);
sb.append(", \"" + ComponentDescriptorConstants.QUERIES_TARGET + "\": ");
appendMap(sb, component.conditionalQueries);
sb.append(", \"" + ComponentDescriptorConstants.SERVICES_TARGET + "\": "); sb.append(", \"" + ComponentDescriptorConstants.SERVICES_TARGET + "\": ");
appendMap(sb, component.conditionalServices); appendMap(sb, component.conditionalServices);
sb.append(", \"" + ComponentDescriptorConstants.CONTENT_PROVIDERS_TARGET + "\": "); sb.append(", \"" + ComponentDescriptorConstants.CONTENT_PROVIDERS_TARGET + "\": ");
......
...@@ -24,6 +24,7 @@ import com.google.appinventor.components.annotations.UsesPermissions; ...@@ -24,6 +24,7 @@ import com.google.appinventor.components.annotations.UsesPermissions;
import com.google.appinventor.components.annotations.UsesActivities; import com.google.appinventor.components.annotations.UsesActivities;
import com.google.appinventor.components.annotations.UsesBroadcastReceivers; import com.google.appinventor.components.annotations.UsesBroadcastReceivers;
import com.google.appinventor.components.annotations.UsesContentProviders; 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.UsesServices;
import com.google.appinventor.components.annotations.androidmanifest.ActivityElement; import com.google.appinventor.components.annotations.androidmanifest.ActivityElement;
import com.google.appinventor.components.annotations.androidmanifest.ReceiverElement; import com.google.appinventor.components.annotations.androidmanifest.ReceiverElement;
...@@ -160,6 +161,7 @@ public abstract class ComponentProcessor extends AbstractProcessor { ...@@ -160,6 +161,7 @@ public abstract class ComponentProcessor extends AbstractProcessor {
"com.google.appinventor.components.annotations.UsesActivities", "com.google.appinventor.components.annotations.UsesActivities",
"com.google.appinventor.components.annotations.UsesBroadcastReceivers", "com.google.appinventor.components.annotations.UsesBroadcastReceivers",
"com.google.appinventor.components.annotations.UsesPermissions", "com.google.appinventor.components.annotations.UsesPermissions",
"com.google.appinventor.components.annotations.UsesQueries",
"com.google.appinventor.components.annotations.UsesServices", "com.google.appinventor.components.annotations.UsesServices",
"com.google.appinventor.components.annotations.UsesContentProviders"); "com.google.appinventor.components.annotations.UsesContentProviders");
...@@ -1086,6 +1088,12 @@ public abstract class ComponentProcessor extends AbstractProcessor { ...@@ -1086,6 +1088,12 @@ public abstract class ComponentProcessor extends AbstractProcessor {
*/ */
protected final Map<String, String[]> conditionalBroadcastReceivers; 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 * Mapping of component block names to services that should be
* included if the block is used. * included if the block is used.
...@@ -1133,6 +1141,11 @@ public abstract class ComponentProcessor extends AbstractProcessor { ...@@ -1133,6 +1141,11 @@ public abstract class ComponentProcessor extends AbstractProcessor {
*/ */
protected final Set<String> broadcastReceivers; protected final Set<String> broadcastReceivers;
/**
* Queries required by this component.
*/
protected final Set<String> queries;
/** /**
* Services required by this component. * Services required by this component.
*/ */
...@@ -1214,21 +1227,26 @@ public abstract class ComponentProcessor extends AbstractProcessor { ...@@ -1214,21 +1227,26 @@ public abstract class ComponentProcessor extends AbstractProcessor {
"Component", false, elementUtils.isDeprecated(element)); "Component", false, elementUtils.isDeprecated(element));
type = element.asType().toString(); type = element.asType().toString();
displayName = getDisplayNameForComponentType(name); displayName = getDisplayNameForComponentType(name);
permissions = Sets.newHashSet();
conditionalPermissions = Maps.newTreeMap();
conditionalBroadcastReceivers = Maps.newTreeMap(); conditionalBroadcastReceivers = Maps.newTreeMap();
conditionalServices = Maps.newTreeMap();
conditionalContentProviders = Maps.newTreeMap(); conditionalContentProviders = Maps.newTreeMap();
libraries = Sets.newHashSet(); conditionalPermissions = Maps.newTreeMap();
nativeLibraries = Sets.newHashSet(); conditionalQueries = Maps.newTreeMap();
conditionalServices = Maps.newTreeMap();
assets = Sets.newHashSet(); assets = Sets.newHashSet();
activities = Sets.newHashSet(); activities = Sets.newHashSet();
metadata = Sets.newHashSet();
activityMetadata = Sets.newHashSet(); activityMetadata = Sets.newHashSet();
broadcastReceivers = Sets.newHashSet(); broadcastReceivers = Sets.newHashSet();
services = Sets.newHashSet();
contentProviders = Sets.newHashSet();
classNameAndActionsBR = 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(); designerProperties = Maps.newTreeMap();
properties = Maps.newTreeMap(); properties = Maps.newTreeMap();
methods = Maps.newTreeMap(); methods = Maps.newTreeMap();
...@@ -1587,6 +1605,7 @@ public abstract class ComponentProcessor extends AbstractProcessor { ...@@ -1587,6 +1605,7 @@ public abstract class ComponentProcessor extends AbstractProcessor {
componentInfo.metadata.addAll(parentComponent.metadata); componentInfo.metadata.addAll(parentComponent.metadata);
componentInfo.activityMetadata.addAll(parentComponent.activityMetadata); componentInfo.activityMetadata.addAll(parentComponent.activityMetadata);
componentInfo.broadcastReceivers.addAll(parentComponent.broadcastReceivers); componentInfo.broadcastReceivers.addAll(parentComponent.broadcastReceivers);
componentInfo.queries.addAll(parentComponent.queries);
componentInfo.services.addAll(parentComponent.services); componentInfo.services.addAll(parentComponent.services);
componentInfo.contentProviders.addAll(parentComponent.contentProviders); componentInfo.contentProviders.addAll(parentComponent.contentProviders);
// TODO(Will): Remove the following call once the deprecated // TODO(Will): Remove the following call once the deprecated
...@@ -1730,6 +1749,30 @@ public abstract class ComponentProcessor extends AbstractProcessor { ...@@ -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. // Gather the required services and build their element strings.
UsesServices usesServices = element.getAnnotation(UsesServices.class); UsesServices usesServices = element.getAnnotation(UsesServices.class);
if (usesServices != null) { if (usesServices != null) {
...@@ -2279,6 +2322,21 @@ public abstract class ComponentProcessor extends AbstractProcessor { ...@@ -2279,6 +2322,21 @@ public abstract class ComponentProcessor extends AbstractProcessor {
return elementString.append(" </intent-filter>\\n").toString(); 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 // Transform an @ActionElement into an XML element String for use later
// in creating AndroidManifest.xml. // in creating AndroidManifest.xml.
private static String actionElementToString(ActionElement element) private static String actionElementToString(ActionElement element)
...@@ -2686,6 +2744,32 @@ public abstract class ComponentProcessor extends AbstractProcessor { ...@@ -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); UsesServices service = element.getAnnotation(UsesServices.class);
if (service != null) { if (service != null) {
try { 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