Unverified Commit c503528d authored by Shreyash Saitwal's avatar Shreyash Saitwal Committed by GitHub

Implement annotations for Services and Content Providers (#2217)

parent 1843bdd6
......@@ -170,6 +170,10 @@ 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>> servicesNeeded =
new ConcurrentHashMap<String, Set<String>>();
private final ConcurrentMap<String, Set<String>> contentProvidersNeeded =
new ConcurrentHashMap<String, Set<String>>();
private final ConcurrentMap<String, Set<String>> libsNeeded =
new ConcurrentHashMap<String, Set<String>>();
private final ConcurrentMap<String, Set<String>> nativeLibsNeeded =
......@@ -420,6 +424,18 @@ public final class Compiler {
return broadcastReceiversNeeded;
}
// Just used for testing
@VisibleForTesting
Map<String, Set<String>> getServices() {
return servicesNeeded;
}
// Just used for testing
@VisibleForTesting
Map<String, Set<String>> getContentProviders() {
return contentProvidersNeeded;
}
// Just used for testing
@VisibleForTesting
Map<String, Set<String>> getActivities() {
......@@ -600,6 +616,46 @@ public final class Compiler {
mergeConditionals(conditionals.get(ComponentDescriptorConstants.BROADCAST_RECEIVERS_TARGET), broadcastReceiversNeeded);
}
/*
* Generate a set of conditionally included services needed by this project.
*/
@VisibleForTesting
void generateServices() {
try {
loadJsonInfo(servicesNeeded, ComponentDescriptorConstants.SERVICES_TARGET);
} catch (IOException e) {
// This is fatal.
e.printStackTrace();
userErrors.print(String.format(ERROR_IN_STAGE, "Services"));
} catch (JSONException e) {
// This is fatal, but shouldn't actually ever happen.
e.printStackTrace();
userErrors.print(String.format(ERROR_IN_STAGE, "Services"));
}
mergeConditionals(conditionals.get(ComponentDescriptorConstants.SERVICES_TARGET), servicesNeeded);
}
/*
* Generate a set of conditionally included content providers needed by this project.
*/
@VisibleForTesting
void generateContentProviders() {
try {
loadJsonInfo(contentProvidersNeeded, ComponentDescriptorConstants.CONTENT_PROVIDERS_TARGET);
} catch (IOException e) {
// This is fatal.
e.printStackTrace();
userErrors.print(String.format(ERROR_IN_STAGE, "Content Providers"));
} catch (JSONException e) {
// This is fatal, but shouldn't actually ever happen.
e.printStackTrace();
userErrors.print(String.format(ERROR_IN_STAGE, "Content Providers"));
}
mergeConditionals(conditionals.get(ComponentDescriptorConstants.CONTENT_PROVIDERS_TARGET), contentProvidersNeeded);
}
/*
* TODO(Will): Remove this method once the deprecated @SimpleBroadcastReceiver
* annotation is removed. This should remain for the time being so
......@@ -1119,10 +1175,13 @@ public final class Compiler {
subelements.addAll(activitiesNeeded.entrySet());
subelements.addAll(metadataNeeded.entrySet());
subelements.addAll(broadcastReceiversNeeded.entrySet());
subelements.addAll(servicesNeeded.entrySet());
subelements.addAll(contentProvidersNeeded.entrySet());
// If any component needs to register additional activities or
// broadcast receivers, insert them into the manifest here.
// If any component needs to register additional activities,
// broadcast receivers, services or content providers, insert
// them into the manifest here.
if (!subelements.isEmpty()) {
for (Map.Entry<String, Set<String>> componentSubElSetPair : subelements) {
Set<String> subelementSet = componentSubElSetPair.getValue();
......@@ -1234,6 +1293,8 @@ public final class Compiler {
compiler.generateMetadata();
compiler.generateActivityMetadata();
compiler.generateBroadcastReceivers();
compiler.generateServices();
compiler.generateContentProviders();
compiler.generateLibNames();
compiler.generateNativeLibNames();
compiler.generatePermissions();
......@@ -2514,7 +2575,8 @@ public final class Compiler {
* @param type The name of the type being processed
* @param targetInfo Name of the annotation target being processed (e.g.,
* permissions). Any of: PERMISSIONS_TARGET,
* BROADCAST_RECEIVERS_TARGET
* BROADCAST_RECEIVERS_TARGET, SERVICES_TARGET,
* CONTENT_PROVIDERS_TARGET
*/
private void processConditionalInfo(JSONObject compJson, String type, String targetInfo) {
// Strip off the package name since SCM and BKY use unqualified names
......
// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2020 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.ProviderElement;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation to indicate any content providers used by
* a component so that corresponding <provider> elements can be
* created in AndroidManifest.xml.
*
* @author https://github.com/ShreyashSaitwal (Shreyash Saitwal)
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface UsesContentProviders {
/**
* An array containing each {@link ProviderElement}
* that is required by the component.
*
* @return the array containing the relevant providers
*/
ProviderElement[] providers();
}
// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2020 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.ServiceElement;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation to indicate any services used by
* a component so that corresponding <service> elements can be
* created in AndroidManifest.xml.
*
* @author https://github.com/ShreyashSaitwal (Shreyash Saitwal)
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface UsesServices {
/**
* An array containing each {@link ServiceElement}
* that is required by the component.
*
* @return the array containing the relevant services
*/
ServiceElement[] services();
}
// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2020 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.androidmanifest;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specifies the subsets of app data that the parent content provider has permission to access.
* Data subsets are indicated by the path part of a content: URI. (The authority part of the URI
* identifies the content provider.) Granting permission is a way of enabling clients of the <provider>
* that don't normally have permission to access its data to overcome that restriction on a one-time
* basis.
*
* If a content provider's grantUriPermissions attribute is "true", permission can be granted for
* any the data under the provider's purview. However, if that attribute is "false", permission can
* be granted only to data subsets that are specified by this element. A provider can contain any
* number of <grant-uri-permission> elements. Each one can specify only one path (only one of the
* three possible attributes).
*
* Note: Most of this documentation is adapted from the Android framework specification
* linked below. That documentation is licensed under the
* {@link <a href="https://creativecommons.org/licenses/by/2.5/">
* Creative Commons Attribution license v2.5
* </a>}.
*
* See {@link <a href="https://developer.android.com/guide/topics/manifest/grant-uri-permission-element">
* https://developer.android.com/guide/topics/manifest/grant-uri-permission-element
* </a>}.
*
* @author https://github.com/ShreyashSaitwal (Shreyash Saitwal)
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface GrantUriPermissionElement {
/**
* A complete URI path for a subset of content provider data. Permission can be granted only
* to the particular data identified by this path. When used to provide search suggestion
* content, it must be appended with "/search_suggest_query".
*
* @return the grant uri permission path attribute
*/
String path() default "";
/**
* The initial part of a URI path for a subset of content provider data. Permission can be
* granted to all data subsets with paths that share this initial part.
*
* @return the grant uri permission pathPrefix attribute
*/
String pathPrefix() default "";
/**
* A path identifying the data subset or subsets that permission can be granted for. The path
* attribute specifies a complete path; permission can be granted only to the particular data
* subset identified by that path. The pathPrefix attribute specifies the initial part of a path;
* permission can be granted to all data subsets with paths that share that initial part. The
* pathPattern attribute specifies a complete path, but one that can contain the following wildcards:
* - An asterisk ('*') matches a sequence of 0 to many occurrences of the immediately preceding
* character.
* - A period followed by an asterisk (".*") matches any sequence of 0 to many characters.
* Because '\' is used as an escape character when the string is read from XML (before it is parsed
* as a pattern), you will need to double-escape: For example, a literal '*' would be written as "\\*"
* and a literal '\' would be written as "\\\\". This is basically the same as what you would need to
* write if constructing the string in Java code.
*
* @return the grant uri permission pathPattern attribute respectively
*/
String pathPattern() default "";
}
// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2020 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.androidmanifest;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Defines the path and required permissions for a specific subset of data within a
* <provider>. This element can be specified multiple times to supply multiple paths.
*
* Note: Most of this documentation is adapted from the Android framework specification
* linked below. That documentation is licensed under the
* {@link <a href="https://creativecommons.org/licenses/by/2.5/">
* Creative Commons Attribution license v2.5
* </a>}.
*
* See {@link <a href="https://developer.android.com/guide/topics/manifest/path-permission-element">
* https://developer.android.com/guide/topics/manifest/path-permission-element
* </a>}.
*
* @author https://github.com/ShreyashSaitwal (Shreyash Saitwal)
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface PathPermissionElement {
/**
* A complete URI path for a subset of content provider data. Permission can be granted only
* to the particular data identified by this path. When used to provide search suggestion
* content, it must be appended with "/search_suggest_query".
*
* @return the path permission path attribute
*/
String path() default "";
/**
* The initial part of a URI path for a subset of content provider data. Permission can be
* granted to all data subsets with paths that share this initial part.
*
* @return the path permission pathPrefix attribute
*/
String pathPrefix() default "";
/**
* A complete URI path for a subset of content provider data, but one that can use the following
* wildcards:
* - An asterisk ('*'). This matches a sequence of 0 to many occurrences of the immediately
* preceding character.
* - A period followed by an asterisk (".*"). This matches any sequence of 0 or more characters.
*
* Because '\' is used as an escape character when the string is read from XML (before it is parsed
* as a pattern), you will need to double-escape. For example, a literal '*' would be written as "\\*"
* and a literal '\' would be written as "\\". This is basically the same as what you would need to
* write if constructing the string in Java code.
*
* @return the path permission pathPattern attribute
*/
String pathPattern() default "";
/**
* The name of a permission that clients must have in order to read or write the content provider's
* data. This attribute is a convenient way of setting a single permission for both reading and
* writing. However, the {@link #readPermission()} and {@link #writePermission()} attributes take
* precedence over this one.
*
* @return the path permission permission attribute
*/
String permission() default "";
/**
* A permission that clients must have in order to query the content provider.
*
* @return the path permission readPermission attribute
*/
String readPermission() default "";
/**
* A permission that clients must have in order to make changes to the data controlled by the
* content provider.
*
* @return the path permission writePermission attribute
*/
String writePermission() default "";
}
// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2020 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.androidmanifest;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation to describe a <provider> element required by a component so that
* it can be added to AndroidManifest.xml. <provider> elements indicate that
* a component is a content provider. <provider> element attributes that are not
* set explicitly default to "" or {} and are ignored when the element is created
* in the manifest.
*
* Note: Most of this documentation is adapted from the Android framework specification
* linked below. That documentation is licensed under the
* {@link <a href="https://creativecommons.org/licenses/by/2.5/">
* Creative Commons Attribution license v2.5
* </a>}.
*
* See {@link <a href="https://developer.android.com/guide/topics/manifest/provider-element">
* https://developer.android.com/guide/topics/manifest/provider-element
* </a>}.
*
* @author https://github.com/ShreyashSaitwal (Shreyash Saitwal)
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ProviderElement {
/**
* An array containing any meta data used by this <provider> element.
*
* @return an array containing the <meta-data> subelements for this
* <provider> element
*/
MetaDataElement[] metaDataElements() default {};
/**
* An array containing any path permissions used by this <provider> element.
*
* @return an array containing the <path-permission> subelements for this
* <provider> element
*/
PathPermissionElement[] pathPermissionElement() default {};
/**
* An array containing any path permissions used by this <provider> element.
*
* @return an array containing the <path-permission> subelements for this
* <provider> element
*/
GrantUriPermissionElement[] grantUriPermissionElement() default {};
/**
* A list of one or more URI authorities that identify data offered by the content provider.
* Multiple authorities are listed by separating their names with a semicolon. To avoid conflicts,
* authority names should use a Java-style naming convention (such as com.example.provider.cartoonprovider).
* Typically, it's the name of the ContentProvider subclass that implements the provider
*
* There is no default. At least one authority must be specified.
*
* @return the provider authorities attribute
*/
String authorities();
/**
* Whether or not the content provider can be instantiated by the system — "true" if it can be,
* and "false" if not. The default value is "true".
* The <application> element has its own enabled attribute that applies to all application components,
* including content providers. The <application> and <provider> attributes must both be "true" (as
* they both are by default) for the content provider to be enabled. If either is "false", the provider
* is disabled; it cannot be instantiated.
*
* @return the provider enabled attribute
*/
String enabled() default "";
/**
* Whether or not the content provider is direct-boot aware; that is, whether or not it can run
* before the user unlocks the device.
*
* The default value is "false".
*
* @return the provider directBootAware attribute
*/
String directBootAware() default "false";
/**
* Whether the content provider is available for other applications to use:
* - true: The provider is available to other applications. Any application can use the provider's
* content URI to access it, subject to the permissions specified for the provider.
* - false: The provider is not available to other applications. Set android:exported="false" to
* limit access to the provider to your applications. Only applications that have the same
* user ID (UID) as the provider, or applications that have been temporarily granted access
* to the provider through the android:grantUriPermissions element, have access to it.
*
* Because this attribute was introduced in API level 17, all devices running API level 16 and lower
* behave as though this attribute is set "true". If you set android:targetSdkVersion to 17 or higher,
* then the default value is "false" for devices running API level 17 and higher.
* You can set android:exported="false" and still limit access to your provider by setting permissions
* with the {@link #permission()} attribute.
*
* @return the provider exported attribute
*/
String exported() default "";
/**
* Whether or not those who ordinarily would not have permission to access the content
* provider's data can be granted permission to do so, temporarily overcoming the restriction
* imposed by the readPermission, writePermission, permission, and exported attributes — "true"
* if permission can be granted, and "false" if not. If "true", permission can be granted to
* any of the content provider's data. If "false", permission can be granted only to the data
* subsets listed in <grant-uri-permission> subelements, if any. The default value is "false".
*
* Granting permission is a way of giving an application component one-time access to data
* protected by a permission. For example, when an e-mail message contains an attachment, the
* mail application may call upon the appropriate viewer to open it, even though the viewer
* doesn't have general permission to look at all the content provider's data.
* In such cases, permission is granted by FLAG_GRANT_READ_URI_PERMISSION and FLAG_GRANT_WRITE_URI_PERMISSION
* flags in the Intent object that activates the component. For example, the mail application
* might put FLAG_GRANT_READ_URI_PERMISSION in the Intent passed to Context.startActivity().
* The permission is specific to the URI in the Intent.
* If you enable this feature, either by setting this attribute to "true" or by defining
* <grant-uri-permission> subelements, you must call Context.revokeUriPermission() when a
* covered URI is deleted from the provider.
* See also the {@link #GrantUriPermissionElement}.
*
* @return the provider grantUriPermission attribute
*/
String grantUriPermissions() default "";
/**
* An icon representing the content provider. This attribute must be set as a reference to a
* drawable resource containing the image definition. If it is not set, the icon specified
* for the application as a whole is used instead.
*
* @return the provider icon attribute
*/
String icon() default "";
/**
* The order in which the content provider should be instantiated, relative to other content
* providers hosted by the same process. When there are dependencies among content providers,
* setting this attribute for each of them ensures that they are created in the order required
* by those dependencies. The value is a simple integer, with higher numbers being initialized
* first.
*
* @return the provider initOrder attribute
*/
String initOrder() default "";
/**
* A user-readable label for the content provided. If this attribute is not set, the label
* set for the application as a whole is used instead (see the <application> element's label
* attribute).
*
* The label should be set as a reference to a string resource, so that it can be localized
* like other strings in the user interface. However, as a convenience while you're developing
* the application, it can also be set as a raw string.
*
* @return the provider label attribute
*/
String label() default "";
/**
* If the app runs in multiple processes, this attribute determines whether multiple instances
* of the content provider are created. If true, each of the app's processes has its own content
* provider object. If false, the app's processes share only one content provider object. The
* default value is false.
* Setting this flag to true may improve performance by reducing the overhead of interprocess
* communication, but it also increases the memory footprint of each process.
*
* @return the provider multiprocess attribute
*/
String multiprocess() default "";
/**
* The name of the class that implements the content provider, a subclass of ContentProvider.
* This should be a fully qualified class name (such as, "com.example.project.TransportationProvider").
* However, as a shorthand, if the first character of the name is a period, it is appended to
* the package name specified in the <manifest> element.
*
* There is no default. The name must be specified.
*
* @return the provider class name
*/
String name();
/**
* The name of a permission that clients must have to read or write the content provider's data.
* This attribute is a convenient way of setting a single permission for both reading and writing.
* However, the {@link #readPermission()}, {@link #writePermission()}, and {@link #grantUriPermissions()}
* attributes take precedence over this one. If the {@link #readPermission()} attribute is also set,
* it controls access for querying the content provider. And if the writePermission attribute is set,
* it controls access for modifying the provider's data.
*
* @return the provider permission attribute
*/
String permission() default "";
/**
* The name of the process in which the content provider should run. Normally, all components of an
* application run in the default process created for the application. It has the same name as the
* application package. The <application> element's process attribute can set a different default
* for all components. But each component can override the default with its own process attribute,
* allowing you to spread your application across multiple processes.
*
* If the name assigned to this attribute begins with a colon (':'), a new process, private to the
* application, is created when it's needed and the activity runs in that process. If the process
* name begins with a lowercase character, the activity will run in a global process of that name,
* provided that it has permission to do so. This allows components in different applications to
* share a process, reducing resource usage.
*
* @return the provider process attribute
*/
String process() default "";
/**
* A permission that clients must have to query the content provider.
*
* If the provider sets android:grantUriPermissions to true, or if a given client satisfies the
* conditions of a <grant-uri-permission> subelement, the client can gain temporary read access
* to the content provider's data.
*
* See also the {@link #permission()} and {@link #writePermission()} attributes.
*
* @return the provider readPermission attribute
*/
String readPermission() default "";
/**
* Whether or not the data under the content provider's control is to be synchronized with data on
* a server — "true" if it is to be synchronized, and "false" if not.
*
* @return the provider syncable attribute
*/
String syncable() default "";
/**
* A permission that clients must have to make changes to the data controlled by the content provider.
* If the provider sets android:grantUriPermissions to true, or if a given client satisfies the conditions
* of a <grant-uri-permission> subelement, the client can gain temporary write access to modify the content
* provider's data.
* See also the {@link #permission()} and {@link #readPermission()} attributes.
*
* @return
*/
String writePermission() default "";
}
// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2020 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.androidmanifest;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation to describe a <service> element required by a component so that
* it can be added to AndroidManifest.xml. <service> elements indicate that
* a component is a service. <service> element attributes that are not
* set explicitly default to "" or {} and are ignored when the element is created
* in the manifest.
*
* Note: Most of this documentation is adapted from the Android framework specification
* linked below. That documentation is licensed under the
* {@link <a href="https://creativecommons.org/licenses/by/2.5/">
* Creative Commons Attribution license v2.5
* </a>}.
*
* See {@link <a href="https://developer.android.com/guide/topics/manifest/service-element">
* https://developer.android.com/guide/topics/manifest/service-element
* </a>}.
*
* @author https://github.com/ShreyashSaitwal (Shreyash Saitwal)
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ServiceElement {
/**
* An array containing any intent filters used by this <service> element.
*
* @return an array containing the <intent-filter> subelements for this
* <service> element
*/
IntentFilterElement[] intentFilters() default {};
/**
* An array containing any meta data used by this <service> element.
*
* @return an array containing the <meta-data> subelements for this
* <service> element
*/
MetaDataElement[] metaDataElements() default {};
/**
* The name of the class that implements the service. This should
* be a fully qualified class name (such as, "com.example.project.RoomService").
* However, as a shorthand, if the first character of the name is
* a period (for example, ".RoomService"), it is appended to the
* package name of the application.
*
* @return the Service class name
*/
String name();
/**
* If set to true, this service will run under a special process that
* is isolated from the rest of the system and has no permissions
* of its own. The only communication with it is through the Service
* API (binding and starting).
*
* @return the service isolatedProcess attribute
*/
String isolatedProcess() default "";
/**
* Specify that the service is a foreground service that satisfies a
* particular use case. For example, a foreground service type of
* "location" indicates that an app is getting the device's current
* location, usually to continue a user-initiated action related to
* device location.
* You can assign multiple foreground service types to a particular service.
*
* @return the service foregroundServiceType attribute
*/
String foregroundServiceType() default "";
/**
* A string that describes the service to users. The label should be
* set as a reference to a string resource, so that it can be localized
* like other strings in the user interface.
*
* @return the service desciption attribute
*/
String description() default "";
/**
* Whether or not the service is direct-boot aware; that is, whether or
* not it can run before the user unlocks the device.
*
* @return the service directBootAware attribute
*/
String directBootAware() default "";
/**
* Whether or not the service can be instantiated by the system — "true"
* if it can be, and "false" if not. The default value is "true".
* The <application> element has its own enabled attribute that applies
* to all application components, including services. The <application>
* and <service> attributes must both be "true" (as they both are by default)
* for the service to be enabled. If either is "false", the service is
* disabled; it cannot be instantiated.
*
* @return the service enabled attribute
*/
String enabled() default "";
/**
* Whether or not components of other applications can invoke the service
* or interact with it — "true" if they can, and "false" if not. When the
* value is "false", only components of the same application or applications
* with the same user ID can start the service or bind to it.
*
* The default value depends on whether the service contains intent filters.
* The absence of any filters means that it can be invoked only by specifying
* its exact class name. This implies that the service is intended only for
* application-internal use (since others would not know the class name). So
* in this case, the default value is "false". On the other hand, the presence
* of at least one filter implies that the service is intended for external use,
* so the default value is "true".
* This attribute is not the only way to limit the exposure of a service to other
* applications. You can also use a permission to limit the external entities that
* can interact with the service.
*
* @return the service exported attribute
*/
String exported() default "";
/**
* An icon representing the service. This attribute must be set as
* a reference to a drawable resource containing the image definition. If it is
* not set, the icon specified for the application as a whole is used instead.
*
* The service's icon — whether set here or by the <application>
* element — is also the default icon for all the service's intent filters
* (see the {@link IntentFilterElement#icon()} attribute).
*
* @return the service icon attribute
*/
String icon() default "";
/**
* A name for the service that can be displayed to users. If this attribute
* is not set, the label set for the application as a whole is used instead.
*
* The service's label — whether set here or by the <application> element —
* is also the default label for all the service's intent filters.
*
* @return the service label attribute
*/
String label() default "";
/**
* The name of a permission that an entity must have in order to launch the
* service or bind to it. If a caller of startService(), bindService(), or
* stopService(), has not been granted this permission, the method will not
* work and the Intent object will not be delivered to the service.
*
* If this attribute is not set, the permission set by the <application>
* element's permission attribute applies to the service. If neither attribute
* is set, the service is not protected by a permission.
*
* @return the service permission attribute
*/
String permission() default "";
/**
* The name of the process in which the service should run. Normally,
* all components of an application run in the default process created for the
* application. For our purposes, those components are services and
* activities. It has the same name as the application package. Each component
* can override the default with its own process attribute, allowing you to
* spread your application across multiple processes.
*
* If the name assigned to this attribute begins with a colon (':'), a new
* process, private to the application, is created when it's needed and the
* service runs in that process. If the process name begins with
* a lowercase character, the service will run in a global process of that
* name, provided that it has permission to do so. This allows components
* ( services and activities) in different applications to share
* a process, reducing resource usage.
*
* @return the service process attribute
*/
String process() default "";
}
......@@ -26,6 +26,8 @@ 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 SERVICES_TARGET = "services";
public static final String CONTENT_PROVIDERS_TARGET = "contentProviders";
public static final String ANDROIDMINSDK_TARGET = "androidMinSdk";
public static final String CONDITIONALS_TARGET = "conditionals";
......
......@@ -213,7 +213,9 @@ public final class ComponentDescriptorGenerator extends ComponentProcessor {
*/
private void outputConditionalAnnotations(ComponentInfo component, StringBuilder sb) {
if (component.conditionalPermissions.size() +
component.conditionalBroadcastReceivers.size() == 0) {
component.conditionalBroadcastReceivers.size() +
component.conditionalServices.size() +
component.conditionalContentProviders.size() == 0) {
return;
}
sb.append(",\n \"conditionals\":{\n ");
......@@ -229,6 +231,18 @@ public final class ComponentDescriptorGenerator extends ComponentProcessor {
outputMultimap(sb, " ", component.conditionalBroadcastReceivers);
first = false;
}
if (component.conditionalServices.size() > 0) {
if (!first) sb.append(",\n ");
sb.append("\"services\": ");
outputMultimap(sb, " ", component.conditionalServices);
first = false;
}
if (component.conditionalContentProviders.size() > 0) {
if (!first) sb.append(",\n ");
sb.append("\"contentProviders\": ");
outputMultimap(sb, " ", component.conditionalContentProviders);
first = false;
}
// Add other annotations here as needed
sb.append("\n }");
}
......
......@@ -18,8 +18,8 @@ import javax.tools.Diagnostic;
import javax.tools.FileObject;
/**
* Tool to generate a list of the simple component types, permissions, libraries, activities
* and Broadcast Receivers (build info) required for each component.
* Tool to generate a list of the simple component types, permissions, libraries, activities,
* Broadcast Receivers, Services and Content Providers (build info) required for each component.
*
* @author lizlooney@google.com (Liz Looney)
*/
......@@ -85,6 +85,8 @@ 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.SERVICES_TARGET, component.services);
appendComponentInfo(sb, ComponentDescriptorConstants.CONTENT_PROVIDERS_TARGET, component.contentProviders);
appendConditionalComponentInfo(component, sb);
// TODO(Will): Remove the following call once the deprecated
// @SimpleBroadcastReceiver annotation is removed. It should
......@@ -103,7 +105,9 @@ public final class ComponentListGenerator extends ComponentProcessor {
*/
private static void appendConditionalComponentInfo(ComponentInfo component, StringBuilder sb) {
if (component.conditionalPermissions.size() +
component.conditionalBroadcastReceivers.size() == 0) {
component.conditionalBroadcastReceivers.size() +
component.conditionalServices.size() +
component.conditionalContentProviders.size() == 0) {
return;
}
sb.append(", \"" + ComponentDescriptorConstants.CONDITIONALS_TARGET + "\": { ");
......@@ -111,6 +115,10 @@ public final class ComponentListGenerator extends ComponentProcessor {
appendMap(sb, component.conditionalPermissions);
sb.append(", \"" + ComponentDescriptorConstants.BROADCAST_RECEIVERS_TARGET + "\": ");
appendMap(sb, component.conditionalBroadcastReceivers);
sb.append(", \"" + ComponentDescriptorConstants.SERVICES_TARGET + "\": ");
appendMap(sb, component.conditionalServices);
sb.append(", \"" + ComponentDescriptorConstants.CONTENT_PROVIDERS_TARGET + "\": ");
appendMap(sb, component.conditionalContentProviders);
sb.append("}");
}
......
......@@ -23,6 +23,8 @@ import com.google.appinventor.components.annotations.UsesNativeLibraries;
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.UsesServices;
import com.google.appinventor.components.annotations.androidmanifest.ActivityElement;
import com.google.appinventor.components.annotations.androidmanifest.ReceiverElement;
import com.google.appinventor.components.annotations.androidmanifest.IntentFilterElement;
......@@ -30,6 +32,10 @@ import com.google.appinventor.components.annotations.androidmanifest.MetaDataEle
import com.google.appinventor.components.annotations.androidmanifest.ActionElement;
import com.google.appinventor.components.annotations.androidmanifest.DataElement;
import com.google.appinventor.components.annotations.androidmanifest.CategoryElement;
import com.google.appinventor.components.annotations.androidmanifest.ServiceElement;
import com.google.appinventor.components.annotations.androidmanifest.ProviderElement;
import com.google.appinventor.components.annotations.androidmanifest.PathPermissionElement;
import com.google.appinventor.components.annotations.androidmanifest.GrantUriPermissionElement;
import com.google.appinventor.components.common.PropertyTypeConstants;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
......@@ -141,7 +147,9 @@ public abstract class ComponentProcessor extends AbstractProcessor {
"com.google.appinventor.components.annotations.UsesNativeLibraries",
"com.google.appinventor.components.annotations.UsesActivities",
"com.google.appinventor.components.annotations.UsesBroadcastReceivers",
"com.google.appinventor.components.annotations.UsesPermissions");
"com.google.appinventor.components.annotations.UsesPermissions",
"com.google.appinventor.components.annotations.UsesServices",
"com.google.appinventor.components.annotations.UsesContentProviders");
// Returned by getRwString()
private static final String READ_WRITE = "read-write";
......@@ -653,6 +661,18 @@ public abstract class ComponentProcessor extends AbstractProcessor {
*/
protected final Map<String, String[]> conditionalBroadcastReceivers;
/**
* Mapping of component block names to services that should be
* included if the block is used.
*/
protected final Map<String, String[]> conditionalServices;
/**
* Mapping of component block names to content providers that should be
* included if the block is used.
*/
protected final Map<String, String[]> conditionalContentProviders;
/**
* Libraries required by this component.
*/
......@@ -687,6 +707,16 @@ public abstract class ComponentProcessor extends AbstractProcessor {
* Broadcast receivers required by this component.
*/
protected final Set<String> broadcastReceivers;
/**
* Services required by this component.
*/
protected final Set<String> services;
/**
* Content providers required by this component.
*/
protected final Set<String> contentProviders;
/**
* TODO(Will): Remove the following field once the deprecated {@link SimpleBroadcastReceiver}
......@@ -761,6 +791,8 @@ public abstract class ComponentProcessor extends AbstractProcessor {
permissions = Sets.newHashSet();
conditionalPermissions = Maps.newTreeMap();
conditionalBroadcastReceivers = Maps.newTreeMap();
conditionalServices = Maps.newTreeMap();
conditionalContentProviders = Maps.newTreeMap();
libraries = Sets.newHashSet();
nativeLibraries = Sets.newHashSet();
assets = Sets.newHashSet();
......@@ -768,6 +800,8 @@ public abstract class ComponentProcessor extends AbstractProcessor {
metadata = Sets.newHashSet();
activityMetadata = Sets.newHashSet();
broadcastReceivers = Sets.newHashSet();
services = Sets.newHashSet();
contentProviders = Sets.newHashSet();
classNameAndActionsBR = Sets.newHashSet();
designerProperties = Maps.newTreeMap();
properties = Maps.newTreeMap();
......@@ -1116,6 +1150,8 @@ public abstract class ComponentProcessor extends AbstractProcessor {
componentInfo.metadata.addAll(parentComponent.metadata);
componentInfo.activityMetadata.addAll(parentComponent.activityMetadata);
componentInfo.broadcastReceivers.addAll(parentComponent.broadcastReceivers);
componentInfo.services.addAll(parentComponent.services);
componentInfo.contentProviders.addAll(parentComponent.contentProviders);
// TODO(Will): Remove the following call once the deprecated
// @SimpleBroadcastReceiver annotation is removed. It should
// should remain for the time being because otherwise we'll break
......@@ -1256,6 +1292,42 @@ public abstract class ComponentProcessor extends AbstractProcessor {
throw new RuntimeException(e);
}
}
// Gather the required services and build their element strings.
UsesServices usesServices = element.getAnnotation(UsesServices.class);
if (usesServices != null) {
try {
for (ServiceElement se : usesServices.services()) {
updateWithNonEmptyValue(componentInfo.services, serviceElementToString(se));
}
} 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 content providers and build their element strings.
UsesContentProviders usesContentProviders = element.getAnnotation(UsesContentProviders.class);
if (usesContentProviders != null) {
try {
for (ProviderElement pe : usesContentProviders.providers()) {
updateWithNonEmptyValue(componentInfo.contentProviders, providerElementToString(pe));
}
} catch (IllegalAccessException e) {
messager.printMessage(Diagnostic.Kind.ERROR, "IllegalAccessException when gathering " +
"provider attributes and subelements for component " + componentInfo.name);
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
messager.printMessage(Diagnostic.Kind.ERROR, "InvocationTargetException when gathering " +
"provider attributes and subelements for component " + componentInfo.name);
throw new RuntimeException(e);
}
}
// TODO(Will): Remove the following legacy code once the deprecated
// @SimpleBroadcastReceiver annotation is removed. It should
......@@ -1425,6 +1497,43 @@ public abstract class ComponentProcessor extends AbstractProcessor {
return elementString.append(" </receiver>\\n").toString();
}
// Transform a @ServiceElement into an XML element String for use later
// in creating AndroidManifest.xml.
private static String serviceElementToString(ServiceElement element)
throws IllegalAccessException, InvocationTargetException {
// First, we build the <service> element's opening tag including any
// service element attributes.
StringBuilder elementString = new StringBuilder(" <service ");
elementString.append(elementAttributesToString(element));
elementString.append(">\\n");
// Now, we collect any <service> subelements.
elementString.append(subelementsToString(element.metaDataElements()));
elementString.append(subelementsToString(element.intentFilters()));
// Finally, we close the <service> element and create its String.
return elementString.append(" </service>\\n").toString();
}
// Transform a @ProviderElement into an XML element String for use later
// in creating AndroidManifest.xml.
private static String providerElementToString(ProviderElement element)
throws IllegalAccessException, InvocationTargetException {
// First, we build the <provider> element's opening tag including any
// content provider element attributes.
StringBuilder elementString = new StringBuilder(" <provider ");
elementString.append(elementAttributesToString(element));
elementString.append(">\\n");
// Now, we collect any <provider> subelements.
elementString.append(subelementsToString(element.metaDataElements()));
elementString.append(subelementsToString(element.pathPermissionElement()));
elementString.append(subelementsToString(element.grantUriPermissionElement()));
// Finally, we close the <provider> element and create its String.
return elementString.append(" </provider>\\n").toString();
}
// Transform a @MetaDataElement into an XML element String for use later
// in creating AndroidManifest.xml.
private static String metaDataElementToString(MetaDataElement element)
......@@ -1468,7 +1577,7 @@ public abstract class ComponentProcessor extends AbstractProcessor {
return elementString.append("/>\\n").toString();
}
// Transform an @CategoryElement into an XML element String for use later
// Transform a @CategoryElement into an XML element String for use later
// in creating AndroidManifest.xml.
private static String categoryElementToString(CategoryElement element)
throws IllegalAccessException, InvocationTargetException {
......@@ -1480,7 +1589,7 @@ public abstract class ComponentProcessor extends AbstractProcessor {
return elementString.append("/>\\n").toString();
}
// Transform an @DataElement into an XML element String for use later
// Transform a @DataElement into an XML element String for use later
// in creating AndroidManifest.xml.
private static String dataElementToString(DataElement element)
throws IllegalAccessException, InvocationTargetException {
......@@ -1492,6 +1601,30 @@ public abstract class ComponentProcessor extends AbstractProcessor {
return elementString.append("/>\\n").toString();
}
// Transform a @PathPermissionElement into an XML element String for use later
// in creating AndroidManifest.xml.
private static String pathPermissionElementToString(PathPermissionElement element)
throws IllegalAccessException, InvocationTargetException {
// First, we build the <path-permission> element's opening tag including any
// receiver element attributes.
StringBuilder elementString = new StringBuilder(" <path-permission ");
elementString.append(elementAttributesToString(element));
// Finally, we close the <path-permission> element and create its String.
return elementString.append("/>\\n").toString();
}
// Transform a @GrantUriPermissionElement into an XML element String for use later
// in creating AndroidManifest.xml.
private static String grantUriPermissionElementToString(GrantUriPermissionElement element)
throws IllegalAccessException, InvocationTargetException {
// First, we build the <grant-uri-permission> element's opening tag including any
// receiver element attributes.
StringBuilder elementString = new StringBuilder(" <grant-uri-permission ");
elementString.append(elementAttributesToString(element));
// Finally, we close the <grant-uri-permission> element and create its String.
return elementString.append("/>\\n").toString();
}
// Build the attribute String for a given XML element modeled by an
// annotation.
//
......@@ -1541,6 +1674,10 @@ public abstract class ComponentProcessor extends AbstractProcessor {
subelementString.append(categoryElementToString((CategoryElement) subelement));
} else if (subelement instanceof DataElement) {
subelementString.append(dataElementToString((DataElement) subelement));
} else if (subelement instanceof PathPermissionElement) {
subelementString.append(pathPermissionElementToString((PathPermissionElement) subelement));
} else if (subelement instanceof GrantUriPermissionElement) {
subelementString.append(grantUriPermissionElementToString((GrantUriPermissionElement) subelement));
}
}
return subelementString.toString();
......@@ -1793,7 +1930,7 @@ public abstract class ComponentProcessor extends AbstractProcessor {
*
* @param componentInfo Component info in which to store the conditional information.
* @param element The currently processed Java language element. This should be a method
* annotated with either @UsesPermission or @UsesBroadcastReceivers.
* annotated with either @UsesPermission, @UsesBroadcastReceivers, @UsesServices or @UsesContentProviders
* @param blockName The name of the block as it appears in the sources.
*/
private void processConditionalAnnotations(ComponentInfo componentInfo, Element element,
......@@ -1816,10 +1953,36 @@ public abstract class ComponentProcessor extends AbstractProcessor {
messager.printMessage(Kind.ERROR, "Unable to process broadcast receiver", element);
}
}
UsesServices service = element.getAnnotation(UsesServices.class);
if (service != null) {
try {
Set<String> services = new HashSet<>();
for (ServiceElement se : service.services()) {
updateWithNonEmptyValue(services, serviceElementToString(se));
}
componentInfo.conditionalServices.put(blockName, services.toArray(new String[0]));
} catch (Exception e) {
messager.printMessage(Kind.ERROR, "Unable to process service", element);
}
}
UsesContentProviders contentProvider = element.getAnnotation(UsesContentProviders.class);
if (contentProvider != null) {
try {
Set<String> providers = new HashSet<>();
for (ProviderElement pe : contentProvider.providers()) {
updateWithNonEmptyValue(providers, providerElementToString(pe));
}
componentInfo.conditionalContentProviders.put(blockName, providers.toArray(new String[0]));
} catch (Exception e) {
messager.printMessage(Kind.ERROR, "Unable to process content provider", element);
}
}
}
/**
* <p>Outputs the required component information in the desired format. It is called by
* <p>Outputs the required component information in the desired format. It is called by
* {@link #process} after the fields {@link #components} and {@link #messager}
* have been populated.</p>
*
......
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