package org.robolectric.annotation; import android.app.Application; import android.content.pm.PackageInfo; import java.lang.annotation.Annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Properties; import java.util.Set; import javax.annotation.Nonnull; /** * Configuration settings that can be used on a per-class or per-test basis. */ @Documented @Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) @SuppressWarnings(value = {"BadAnnotationImplementation", "ImmutableAnnotationChecker"}) public @interface Config { /** * TODO(vnayar): Create named constants for default values instead of magic numbers. * Array named constants must be avoided in order to dodge a JDK 1.7 bug. * error: annotation Config is missing value for the attribute <clinit> * See JDK-8013485. */ String NONE = "--none"; String DEFAULT_VALUE_STRING = "--default"; int DEFAULT_VALUE_INT = -1; String DEFAULT_MANIFEST_NAME = "AndroidManifest.xml"; Class extends Application> DEFAULT_APPLICATION = DefaultApplication.class; String DEFAULT_PACKAGE_NAME = ""; String DEFAULT_QUALIFIERS = ""; String DEFAULT_RES_FOLDER = "res"; String DEFAULT_ASSET_FOLDER = "assets"; int ALL_SDKS = -2; int TARGET_SDK = -3; int OLDEST_SDK = -4; int NEWEST_SDK = -5; /** * The Android SDK level to emulate. This value will also be set as Build.VERSION.SDK_INT. */ int[] sdk() default {}; // DEFAULT_SDK /** * The minimum Android SDK level to emulate when running tests on multiple API versions. */ int minSdk() default -1; /** * The maximum Android SDK level to emulate when running tests on multiple API versions. */ int maxSdk() default -1; /** * The Android manifest file to load; Robolectric will look relative to the current directory. * Resources and assets will be loaded relative to the manifest. * * If not specified, Robolectric defaults to {@code AndroidManifest.xml}. * * If your project has no manifest or resources, use {@link Config#NONE}. * @deprecated If you are using at least Android Studio 3.0 alpha 5 or Bazel's android_local_test * please migrate to the preferred way to configure * builds http://robolectric.org/getting-started/ * * @return The Android manifest file to load. */ @Deprecated String manifest() default DEFAULT_VALUE_STRING; /** * The {@link android.app.Application} class to use in the test, this takes precedence over any application * specified in the AndroidManifest.xml. * * @return The {@link android.app.Application} class to use in the test. */ Class extends Application> application() default DefaultApplication.class; // DEFAULT_APPLICATION /** * Java package name where the "R.class" file is located. This only needs to be specified if you * define an {@code applicationId} associated with {@code productFlavors} or specify {@code * applicationIdSuffix} in your build.gradle. * *
If not specified, Robolectric defaults to the {@code applicationId}.
*
* @return The java package name for R.class.
* @deprecated To change your package name please override the applicationId in your build system.
* Changing package name here is broken as the package name will no longer match the package
* name encoded in the arsc resources file. If you are looking to simulate another application
* you can create another applications Context using {@link
* android.content.Context#createPackageContext(String, int)}. Note that you must add this
* package to {@link org.robolectric.shadows.ShadowPackageManager#addPackage(PackageInfo)}
* first.
*/
@Deprecated
String packageName() default DEFAULT_PACKAGE_NAME;
/**
* Qualifiers specifying device configuration for this test, such as "fr-normal-port-hdpi".
*
* If the string is prefixed with '+', the qualifiers that follow are overlayed on any more
* broadly-scoped qualifiers.
*
* See [Device Configuration](http://robolectric.org/device-configuration/) for details.
*
* @return Qualifiers used for device configuration and resource resolution.
*/
String qualifiers() default DEFAULT_QUALIFIERS;
/**
* The directory from which to load resources. This should be relative to the directory containing AndroidManifest.xml.
*
* If not specified, Robolectric defaults to {@code res}.
* @deprecated If you are using at least Android Studio 3.0 alpha 5 or Bazel's android_local_test
* please migrate to the preferred way to configure
*
* @return Android resource directory.
*/
@Deprecated
String resourceDir() default DEFAULT_RES_FOLDER;
/**
* The directory from which to load assets. This should be relative to the directory containing AndroidManifest.xml.
*
* If not specified, Robolectric defaults to {@code assets}.
* @deprecated If you are using at least Android Studio 3.0 alpha 5 or Bazel's android_local_test
* please migrate to the preferred way to configure
*
* @return Android asset directory.
*/
@Deprecated
String assetDir() default DEFAULT_ASSET_FOLDER;
/**
* A list of shadow classes to enable, in addition to those that are already present.
*
* @return A list of additional shadow classes to enable.
*/
Class>[] shadows() default {}; // DEFAULT_SHADOWS
/**
* A list of instrumented packages, in addition to those that are already instrumented.
*
* @return A list of additional instrumented packages.
*/
String[] instrumentedPackages() default {}; // DEFAULT_INSTRUMENTED_PACKAGES
/**
* A list of folders containing Android Libraries on which this project depends.
*
* @deprecated If you are using at least Android Studio 3.0 alpha 5 or Bazel's android_local_test
* please migrate to the preferred way to configure
*
* @return A list of Android Libraries.
*/
@Deprecated
String[] libraries() default {}; // DEFAULT_LIBRARIES;
class Implementation implements Config {
private final int[] sdk;
private final int minSdk;
private final int maxSdk;
private final String manifest;
private final String qualifiers;
private final String resourceDir;
private final String assetDir;
private final String packageName;
private final Class>[] shadows;
private final String[] instrumentedPackages;
private final Class extends Application> application;
private final String[] libraries;
public static Config fromProperties(Properties properties) {
if (properties == null || properties.size() == 0) return null;
return new Implementation(
parseSdkArrayProperty(properties.getProperty("sdk", "")),
parseSdkInt(properties.getProperty("minSdk", "-1")),
parseSdkInt(properties.getProperty("maxSdk", "-1")),
properties.getProperty("manifest", DEFAULT_VALUE_STRING),
properties.getProperty("qualifiers", DEFAULT_QUALIFIERS),
properties.getProperty("packageName", DEFAULT_PACKAGE_NAME),
properties.getProperty("resourceDir", DEFAULT_RES_FOLDER),
properties.getProperty("assetDir", DEFAULT_ASSET_FOLDER),
parseClasses(properties.getProperty("shadows", "")),
parseStringArrayProperty(properties.getProperty("instrumentedPackages", "")),
parseApplication(
properties.getProperty("application", DEFAULT_APPLICATION.getCanonicalName())),
parseStringArrayProperty(properties.getProperty("libraries", "")));
}
private static Class> parseClass(String className) {
if (className.isEmpty()) return null;
try {
return Implementation.class.getClassLoader().loadClass(className);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Could not load class: " + className);
}
}
private static Class>[] parseClasses(String input) {
if (input.isEmpty()) return new Class[0];
final String[] classNames = input.split("[, ]+", 0);
final Class[] classes = new Class[classNames.length];
for (int i = 0; i < classNames.length; i++) {
classes[i] = parseClass(classNames[i]);
}
return classes;
}
@SuppressWarnings("unchecked")
private static