Merge "Update internal gradle plugin." am: 66c78e3f88
am: cc321c9422
* commit 'cc321c94228a0435bceb242087624e99ab0028ba':
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index edb5bcd..dd73d93 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -16,6 +16,7 @@
<entry name="?*.ftl" />
<entry name="?*.xsd" />
<entry name="?*.css" />
+ <entry name="?*.trace" />
</wildcardResourcePatterns>
<annotationProcessing>
<profile default="true" name="Default" enabled="false">
diff --git a/.idea/dictionaries/adt.xml b/.idea/dictionaries/adt.xml
index 06a25e9..6a5aa48 100644
--- a/.idea/dictionaries/adt.xml
+++ b/.idea/dictionaries/adt.xml
@@ -307,6 +307,8 @@
<w>xmlns</w>
<w>xxhdpi</w>
<w>xxhigh</w>
+ <w>xxxhdpi</w>
+ <w>xxxhigh</w>
<w>ydpi</w>
<w>yyyy</w>
<w>zipalign</w>
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index 251cf5e..ac27d9f 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -131,6 +131,7 @@
<option name="myCustomValuesEnabled" value="true" />
</inspection_tool>
<inspection_tool class="HtmlUnknownTarget" enabled="false" level="WARNING" enabled_by_default="false" />
+ <inspection_tool class="InnerClassMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="InstanceVariableNamingConvention" enabled="false" level="WARNING" enabled_by_default="false">
<option name="m_regex" value="m[A-Z][A-Za-z\d]*" />
<option name="m_minLength" value="3" />
@@ -142,6 +143,10 @@
<inspection_tool class="JavaLangImport" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="LengthOneStringInIndexOf" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="MapReplaceableByEnumMap" enabled="true" level="WARNING" enabled_by_default="true" />
+ <inspection_tool class="MethodMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true">
+ <option name="m_onlyPrivateOrFinal" value="false" />
+ <option name="m_ignoreEmptyMethods" value="true" />
+ </inspection_tool>
<inspection_tool class="MissingDeprecatedAnnotation" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="MissingOverrideAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="ignoreObjectMethods" value="true" />
diff --git a/.idea/libraries/ant_1_8_0.xml b/.idea/libraries/ant_1_8_0.xml
deleted file mode 100644
index 0399fec..0000000
--- a/.idea/libraries/ant_1_8_0.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<component name="libraryTable">
- <library name="ant-1.8.0">
- <CLASSES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/internal/org/apache/ant/ant/1.8.0/ant-1.8.0.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
-</component>
\ No newline at end of file
diff --git a/.idea/libraries/builder_model.xml b/.idea/libraries/builder_model.xml
new file mode 100644
index 0000000..6ed964d
--- /dev/null
+++ b/.idea/libraries/builder_model.xml
@@ -0,0 +1,11 @@
+<component name="libraryTable">
+ <library name="builder-model">
+ <CLASSES>
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/builder-model/builder-model-0.7.0.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/builder-model/builder-model-0.7.0-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component>
\ No newline at end of file
diff --git a/.idea/libraries/gradle_tooling_api_1_9.xml b/.idea/libraries/gradle_tooling_api_1_9.xml
new file mode 100644
index 0000000..5b6c54a
--- /dev/null
+++ b/.idea/libraries/gradle_tooling_api_1_9.xml
@@ -0,0 +1,11 @@
+<component name="libraryTable">
+ <library name="gradle-tooling-api-1.9">
+ <CLASSES>
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/internal/org/gradle/gradle-tooling-api/1.9/gradle-tooling-api-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/internal/org/gradle/gradle-tooling-api/1.9/gradle-tooling-api-1.9-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component>
\ No newline at end of file
diff --git a/.idea/libraries/javawriter.xml b/.idea/libraries/javawriter.xml
new file mode 100644
index 0000000..14d3470
--- /dev/null
+++ b/.idea/libraries/javawriter.xml
@@ -0,0 +1,11 @@
+<component name="libraryTable">
+ <library name="javawriter">
+ <CLASSES>
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/squareup/javawriter/2.2.1/javawriter-2.2.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/com/squareup/javawriter/2.2.1/javawriter-2.2.1-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component>
\ No newline at end of file
diff --git a/.idea/libraries/lombok_ast.xml b/.idea/libraries/lombok_ast.xml
index 250dbb9..e92068d 100644
--- a/.idea/libraries/lombok_ast.xml
+++ b/.idea/libraries/lombok_ast.xml
@@ -3,14 +3,16 @@
<CLASSES>
<root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/lombok-ast/lombok-ast-0.2.1.jar!/" />
</CLASSES>
- <JAVADOC />
+ <JAVADOC>
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/lombok-ast/lombok-ast-0.2.1-javadoc.jar!/" />
+ </JAVADOC>
<SOURCES>
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/lombok-ast/lombok-ast-0.2.1-src.zip!/src/template" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/lombok-ast/lombok-ast-0.2.1-src.zip!/src/ecjTransformer" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/lombok-ast/lombok-ast-0.2.1-src.zip!/src/javacTransformer" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/lombok-ast/lombok-ast-0.2.1-src.zip!/src/printer" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/lombok-ast/lombok-ast-0.2.1-src.zip!/src/main" />
- <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/lombok-ast/lombok-ast-0.2.1-src.zip!/build/lombok.ast_generatedSource" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/lombok-ast/lombok-ast-0.2.1-sources.jar!/src/main" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/lombok-ast/lombok-ast-0.2.1-sources.jar!/src/printer" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/lombok-ast/lombok-ast-0.2.1-sources.jar!/src/template" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/lombok-ast/lombok-ast-0.2.1-sources.jar!/src/ecjTransformer" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/lombok-ast/lombok-ast-0.2.1-sources.jar!/src/javacTransformer" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/lombok-ast/lombok-ast-0.2.1-sources.jar!/build/lombok.ast_generatedSource" />
</SOURCES>
</library>
</component>
\ No newline at end of file
diff --git a/.idea/libraries/proguard_gradle.xml b/.idea/libraries/proguard_gradle.xml
new file mode 100644
index 0000000..b7d046f
--- /dev/null
+++ b/.idea/libraries/proguard_gradle.xml
@@ -0,0 +1,13 @@
+<component name="libraryTable">
+ <library name="proguard-gradle">
+ <CLASSES>
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/net/sf/proguard/proguard-gradle/4.10/proguard-gradle-4.10.jar!/" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/net/sf/proguard/proguard-base/4.10/proguard-base-4.10.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/net/sf/proguard/proguard-gradle/4.10/proguard-gradle-4.10-sources.jar!/" />
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/repository/net/sf/proguard/proguard-base/4.10/proguard-base-4.10-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component>
\ No newline at end of file
diff --git a/.idea/libraries/slf4j_api.xml b/.idea/libraries/slf4j_api.xml
new file mode 100644
index 0000000..69c282f
--- /dev/null
+++ b/.idea/libraries/slf4j_api.xml
@@ -0,0 +1,11 @@
+<component name="libraryTable">
+ <library name="slf4j-api">
+ <CLASSES>
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/internal/org/slf4j/slf4j-api/1.7.2/slf4j-api-1.7.2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/internal/org/slf4j/slf4j-api/1.7.2/slf4j-api-1.7.2-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component>
\ No newline at end of file
diff --git a/.idea/libraries/slf4j_simple.xml b/.idea/libraries/slf4j_simple.xml
new file mode 100644
index 0000000..16e76fa
--- /dev/null
+++ b/.idea/libraries/slf4j_simple.xml
@@ -0,0 +1,11 @@
+<component name="libraryTable">
+ <library name="slf4j-simple">
+ <CLASSES>
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/internal/org/slf4j/slf4j-simple/1.7.2/slf4j-simple-1.7.2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$PROJECT_DIR$/../../prebuilts/tools/common/m2/internal/org/slf4j/slf4j-simple/1.7.2/slf4j-simple-1.7.2-sources.jar!/" />
+ </SOURCES>
+ </library>
+</component>
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
index d9e87cb..28423eb 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -5,15 +5,20 @@
<module fileurl="file://$PROJECT_DIR$/adt.iml" filepath="$PROJECT_DIR$/adt.iml" />
<module fileurl="file://$PROJECT_DIR$/legacy/ant-tasks/ant-tasks.iml" filepath="$PROJECT_DIR$/legacy/ant-tasks/ant-tasks.iml" />
<module fileurl="file://$PROJECT_DIR$/asset-studio/assetstudio.iml" filepath="$PROJECT_DIR$/asset-studio/assetstudio.iml" />
+ <module fileurl="file://$PROJECT_DIR$/build-system/builder/builder.iml" filepath="$PROJECT_DIR$/build-system/builder/builder.iml" />
+ <module fileurl="file://$PROJECT_DIR$/build-system/builder-model/builder-model.iml" filepath="$PROJECT_DIR$/build-system/builder-model/builder-model.iml" />
+ <module fileurl="file://$PROJECT_DIR$/build-system/builder-test-api/builder-test-api.iml" filepath="$PROJECT_DIR$/build-system/builder-test-api/builder-test-api.iml" />
<module fileurl="file://$PROJECT_DIR$/common/common.iml" filepath="$PROJECT_DIR$/common/common.iml" />
<module fileurl="file://$PROJECT_DIR$/ddmlib/ddmlib.iml" filepath="$PROJECT_DIR$/ddmlib/ddmlib.iml" />
<module fileurl="file://$PROJECT_DIR$/draw9patch/draw9patch.iml" filepath="$PROJECT_DIR$/draw9patch/draw9patch.iml" />
<module fileurl="file://$PROJECT_DIR$/device_validator/dvlib/dvlib.iml" filepath="$PROJECT_DIR$/device_validator/dvlib/dvlib.iml" />
+ <module fileurl="file://$PROJECT_DIR$/build-system/gradle/gradle.iml" filepath="$PROJECT_DIR$/build-system/gradle/gradle.iml" />
+ <module fileurl="file://$PROJECT_DIR$/build-system/gradle-model/gradle-model.iml" filepath="$PROJECT_DIR$/build-system/gradle-model/gradle-model.iml" />
<module fileurl="file://$PROJECT_DIR$/layoutlib-api/layoutlib-api.iml" filepath="$PROJECT_DIR$/layoutlib-api/layoutlib-api.iml" />
<module fileurl="file://$PROJECT_DIR$/lint/libs/lint-api/lint-api.iml" filepath="$PROJECT_DIR$/lint/libs/lint-api/lint-api.iml" />
<module fileurl="file://$PROJECT_DIR$/lint/libs/lint-checks/lint-checks.iml" filepath="$PROJECT_DIR$/lint/libs/lint-checks/lint-checks.iml" />
<module fileurl="file://$PROJECT_DIR$/lint/cli/lint-cli.iml" filepath="$PROJECT_DIR$/lint/cli/lint-cli.iml" />
- <module fileurl="file://$PROJECT_DIR$/manifest-merger/manifest-merger.iml" filepath="$PROJECT_DIR$/manifest-merger/manifest-merger.iml" />
+ <module fileurl="file://$PROJECT_DIR$/build-system/manifest-merger/manifest-merger.iml" filepath="$PROJECT_DIR$/build-system/manifest-merger/manifest-merger.iml" />
<module fileurl="file://$PROJECT_DIR$/ninepatch/ninepatch.iml" filepath="$PROJECT_DIR$/ninepatch/ninepatch.iml" />
<module fileurl="file://$PROJECT_DIR$/perflib/perflib.iml" filepath="$PROJECT_DIR$/perflib/perflib.iml" />
<module fileurl="file://$PROJECT_DIR$/rule-api/rule-api.iml" filepath="$PROJECT_DIR$/rule-api/rule-api.iml" />
diff --git a/apps/DeviceConfig/.classpath b/apps/DeviceConfig/.classpath
index 6aed2eb..d57ec02 100644
--- a/apps/DeviceConfig/.classpath
+++ b/apps/DeviceConfig/.classpath
@@ -1,8 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
- <classpathentry kind="src" path="src"/>
- <classpathentry kind="src" path="gen"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="src" path="gen"/>
+ <classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
<classpathentry kind="output" path="bin/classes"/>
</classpath>
diff --git a/apps/DeviceConfig/.gitignore b/apps/DeviceConfig/.gitignore
new file mode 100644
index 0000000..021b420
--- /dev/null
+++ b/apps/DeviceConfig/.gitignore
@@ -0,0 +1,2 @@
+gen
+bin
diff --git a/apps/DeviceConfig/project.properties b/apps/DeviceConfig/project.properties
index 8da376a..73fc6610 100644
--- a/apps/DeviceConfig/project.properties
+++ b/apps/DeviceConfig/project.properties
@@ -8,4 +8,4 @@
# project structure.
# Project target.
-target=android-15
+target=android-18
diff --git a/asset-studio/build.gradle b/asset-studio/build.gradle
index c5ceef9..2b95089 100644
--- a/asset-studio/build.gradle
+++ b/asset-studio/build.gradle
@@ -1,3 +1,6 @@
+apply plugin: 'java'
+apply plugin: 'distrib'
+
group = 'com.android.tools'
archivesBaseName = 'asset-studio'
@@ -12,3 +15,4 @@
test.resources.srcDir 'src/test/java'
}
+apply from: '../baseVersion.gradle'
\ No newline at end of file
diff --git a/asset-studio/src/main/java/com/android/assetstudiolib/ActionBarIconGenerator.java b/asset-studio/src/main/java/com/android/assetstudiolib/ActionBarIconGenerator.java
index d060bae..55f3409 100644
--- a/asset-studio/src/main/java/com/android/assetstudiolib/ActionBarIconGenerator.java
+++ b/asset-studio/src/main/java/com/android/assetstudiolib/ActionBarIconGenerator.java
@@ -50,7 +50,11 @@
Graphics2D g2 = (Graphics2D) tempImage.getGraphics();
Util.drawCenterInside(g2, options.sourceImage, targetRect);
- if (actionBarOptions.theme == Theme.HOLO_LIGHT) {
+ if (actionBarOptions.theme == Theme.CUSTOM) {
+ Util.drawEffects(g, tempImage, 0, 0, new Effect[] {
+ new FillEffect(new Color(actionBarOptions.customThemeColor), 0.8),
+ });
+ } else if (actionBarOptions.theme == Theme.HOLO_LIGHT) {
Util.drawEffects(g, tempImage, 0, 0, new Effect[] {
new FillEffect(new Color(0x333333), 0.6),
});
@@ -74,6 +78,9 @@
/** Whether or not the source image is a clipart source */
public boolean sourceIsClipart = false;
+
+ /** Custom color for use with the custom theme */
+ public int customThemeColor = 0;
}
/** The themes to generate action bar icons for */
@@ -82,6 +89,9 @@
HOLO_DARK,
/** Theme.HoloLight - a light version of the Honeycomb theme */
- HOLO_LIGHT
+ HOLO_LIGHT,
+
+ /** Theme.Custom - custom colors */
+ CUSTOM
}
}
diff --git a/asset-studio/src/main/java/com/android/assetstudiolib/GraphicGenerator.java b/asset-studio/src/main/java/com/android/assetstudiolib/GraphicGenerator.java
index 797338e..2b948f4 100644
--- a/asset-studio/src/main/java/com/android/assetstudiolib/GraphicGenerator.java
+++ b/asset-studio/src/main/java/com/android/assetstudiolib/GraphicGenerator.java
@@ -17,6 +17,7 @@
package com.android.assetstudiolib;
import com.android.resources.Density;
+import com.android.utils.SdkUtils;
import com.google.common.collect.Lists;
import com.google.common.io.Closeables;
@@ -164,7 +165,7 @@
continue;
}
if (density == Density.LOW || density == Density.TV ||
- (density == Density.XXHIGH && !(this instanceof LauncherIconGenerator))) {
+ density == Density.XXXHIGH) {
// TODO don't manually check and instead gracefully handle missing stencils.
// Not yet supported -- missing stencil image
continue;
@@ -207,6 +208,9 @@
@SuppressWarnings("resource") // Eclipse doesn't know about Closeables#closeQuietly yet
public static BufferedImage getStencilImage(String relativePath) throws IOException {
InputStream is = GraphicGenerator.class.getResourceAsStream(relativePath);
+ if (is == null) {
+ return null;
+ }
try {
return ImageIO.read(is);
} finally {
@@ -267,12 +271,7 @@
ProtectionDomain protectionDomain = GraphicGenerator.class.getProtectionDomain();
URL url = protectionDomain.getCodeSource().getLocation();
if (url != null) {
- File file;
- try {
- file = new File(url.toURI());
- } catch (URISyntaxException e) {
- file = new File(url.getPath());
- }
+ File file = SdkUtils.urlToFile(url);
zipFile = new JarFile(file);
} else {
Enumeration<URL> en =
diff --git a/asset-studio/src/test/java/com/android/assetstudiolib/ActionBarIconGeneratorTest.java b/asset-studio/src/test/java/com/android/assetstudiolib/ActionBarIconGeneratorTest.java
index a097f0a..ed5ea04 100644
--- a/asset-studio/src/test/java/com/android/assetstudiolib/ActionBarIconGeneratorTest.java
+++ b/asset-studio/src/test/java/com/android/assetstudiolib/ActionBarIconGeneratorTest.java
@@ -28,7 +28,7 @@
options.theme = theme;
ActionBarIconGenerator generator = new ActionBarIconGenerator();
- checkGraphic(3, "actions", baseName, generator, options);
+ checkGraphic(4, "actions", baseName, generator, options);
}
public void testDark() throws Exception {
diff --git a/asset-studio/src/test/java/com/android/assetstudiolib/GeneratorTest.java b/asset-studio/src/test/java/com/android/assetstudiolib/GeneratorTest.java
index 4a96f30..c74d0ec 100644
--- a/asset-studio/src/test/java/com/android/assetstudiolib/GeneratorTest.java
+++ b/asset-studio/src/test/java/com/android/assetstudiolib/GeneratorTest.java
@@ -55,6 +55,8 @@
String relativePath = entry.getKey();
BufferedImage image = entry.getValue();
+ if (image == null) continue;
+
String path = "testdata" + File.separator + folderName + File.separator
+ relativePath;
InputStream is = GeneratorTest.class.getResourceAsStream(path);
@@ -88,7 +90,7 @@
assertEquals("Wrong number of generated files", expectedFileCount, fileCount);
}
- private void assertImageSimilar(String imageName, BufferedImage goldenImage,
+ public static void assertImageSimilar(String imageName, BufferedImage goldenImage,
BufferedImage image, float maxPercentDifferent) throws IOException {
assertTrue("Widths differ too much for " + imageName, Math.abs(goldenImage.getWidth()
- image.getWidth()) < 2);
@@ -187,7 +189,7 @@
g.dispose();
}
- protected File getTempDir() {
+ protected static File getTempDir() {
if (System.getProperty("os.name").equals("Mac OS X")) {
return new File("/tmp"); //$NON-NLS-1$
}
diff --git a/asset-studio/src/test/java/com/android/assetstudiolib/MenuIconGeneratorTest.java b/asset-studio/src/test/java/com/android/assetstudiolib/MenuIconGeneratorTest.java
index 544777d..700be4b 100644
--- a/asset-studio/src/test/java/com/android/assetstudiolib/MenuIconGeneratorTest.java
+++ b/asset-studio/src/test/java/com/android/assetstudiolib/MenuIconGeneratorTest.java
@@ -22,7 +22,7 @@
public class MenuIconGeneratorTest extends GeneratorTest {
private void checkGraphic(String baseName) throws IOException {
MenuIconGenerator generator = new MenuIconGenerator();
- checkGraphic(3, "menus", baseName, generator, new GraphicGenerator.Options());
+ checkGraphic(4, "menus", baseName, generator, new GraphicGenerator.Options());
}
public void testMenu() throws Exception {
diff --git a/asset-studio/src/test/java/com/android/assetstudiolib/NotificationIconGeneratorTest.java b/asset-studio/src/test/java/com/android/assetstudiolib/NotificationIconGeneratorTest.java
index 39fd7ac..1880d2b 100644
--- a/asset-studio/src/test/java/com/android/assetstudiolib/NotificationIconGeneratorTest.java
+++ b/asset-studio/src/test/java/com/android/assetstudiolib/NotificationIconGeneratorTest.java
@@ -32,7 +32,7 @@
}
private void checkGraphic(String baseName) throws IOException {
- checkGraphic(baseName, 1, "notification", 9);
+ checkGraphic(baseName, 1, "notification", 12);
}
public void testNotification1() throws Exception {
@@ -40,10 +40,10 @@
}
public void testNotification2() throws Exception {
- checkGraphic("ic_stat_1", 9 /* minSdk */, "notification-v9+", 6 /* fileCount */);
+ checkGraphic("ic_stat_1", 9 /* minSdk */, "notification-v9+", 8 /* fileCount */);
}
public void testNotification3() throws Exception {
- checkGraphic("ic_stat_1", 11, "notification-v11+", 3);
+ checkGraphic("ic_stat_1", 11, "notification-v11+", 4);
}
}
diff --git a/asset-studio/src/test/java/com/android/assetstudiolib/TabIconGeneratorTest.java b/asset-studio/src/test/java/com/android/assetstudiolib/TabIconGeneratorTest.java
index fb7849c..4231f54 100644
--- a/asset-studio/src/test/java/com/android/assetstudiolib/TabIconGeneratorTest.java
+++ b/asset-studio/src/test/java/com/android/assetstudiolib/TabIconGeneratorTest.java
@@ -29,10 +29,10 @@
}
public void testTabs1() throws Exception {
- checkGraphic("tabs", "ic_tab_1", 1 /* minSdk */, 12 /* expectedFileCount */);
+ checkGraphic("tabs", "ic_tab_1", 1 /* minSdk */, 16 /* expectedFileCount */);
}
public void testTabs2() throws Exception {
- checkGraphic("tabs-v5+", "ic_tab_1", 5, 6);
+ checkGraphic("tabs-v5+", "ic_tab_1", 5, 8);
}
}
diff --git a/asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-xxhdpi/ic_action_dark.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-xxhdpi/ic_action_dark.png
new file mode 100644
index 0000000..a2029bc
--- /dev/null
+++ b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-xxhdpi/ic_action_dark.png
Binary files differ
diff --git a/asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-xxhdpi/ic_action_light.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-xxhdpi/ic_action_light.png
new file mode 100644
index 0000000..ef03b59
--- /dev/null
+++ b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/actions/res/drawable-xxhdpi/ic_action_light.png
Binary files differ
diff --git a/asset-studio/src/test/java/com/android/assetstudiolib/testdata/menus/res/drawable-xxhdpi/ic_menu_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/menus/res/drawable-xxhdpi/ic_menu_1.png
new file mode 100644
index 0000000..c2ebd65
--- /dev/null
+++ b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/menus/res/drawable-xxhdpi/ic_menu_1.png
Binary files differ
diff --git a/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v11+/res/drawable-xxhdpi/ic_stat_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v11+/res/drawable-xxhdpi/ic_stat_1.png
new file mode 100644
index 0000000..d7d2512
--- /dev/null
+++ b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v11+/res/drawable-xxhdpi/ic_stat_1.png
Binary files differ
diff --git a/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-xxhdpi-v11/ic_stat_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-xxhdpi-v11/ic_stat_1.png
new file mode 100644
index 0000000..d7d2512
--- /dev/null
+++ b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-xxhdpi-v11/ic_stat_1.png
Binary files differ
diff --git a/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-xxhdpi/ic_stat_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-xxhdpi/ic_stat_1.png
new file mode 100644
index 0000000..fe10d17
--- /dev/null
+++ b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification-v9+/res/drawable-xxhdpi/ic_stat_1.png
Binary files differ
diff --git a/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xxhdpi-v11/ic_stat_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xxhdpi-v11/ic_stat_1.png
new file mode 100644
index 0000000..d7d2512
--- /dev/null
+++ b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xxhdpi-v11/ic_stat_1.png
Binary files differ
diff --git a/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xxhdpi-v9/ic_stat_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xxhdpi-v9/ic_stat_1.png
new file mode 100644
index 0000000..fe10d17
--- /dev/null
+++ b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xxhdpi-v9/ic_stat_1.png
Binary files differ
diff --git a/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xxhdpi/ic_stat_1.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xxhdpi/ic_stat_1.png
new file mode 100644
index 0000000..ee526a9
--- /dev/null
+++ b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/notification/res/drawable-xxhdpi/ic_stat_1.png
Binary files differ
diff --git a/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xxhdpi/ic_action_dark.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xxhdpi/ic_action_dark.png
new file mode 100644
index 0000000..a2029bc
--- /dev/null
+++ b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xxhdpi/ic_action_dark.png
Binary files differ
diff --git a/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xxhdpi/ic_action_light.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xxhdpi/ic_action_light.png
new file mode 100644
index 0000000..ef03b59
--- /dev/null
+++ b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xxhdpi/ic_action_light.png
Binary files differ
diff --git a/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xxhdpi/ic_tab_1_selected.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xxhdpi/ic_tab_1_selected.png
new file mode 100644
index 0000000..9a3518f
--- /dev/null
+++ b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xxhdpi/ic_tab_1_selected.png
Binary files differ
diff --git a/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xxhdpi/ic_tab_1_unselected.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xxhdpi/ic_tab_1_unselected.png
new file mode 100644
index 0000000..5404d8e
--- /dev/null
+++ b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs-v5+/res/drawable-xxhdpi/ic_tab_1_unselected.png
Binary files differ
diff --git a/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xxhdpi-v5/ic_tab_1_selected.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xxhdpi-v5/ic_tab_1_selected.png
new file mode 100644
index 0000000..9a3518f
--- /dev/null
+++ b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xxhdpi-v5/ic_tab_1_selected.png
Binary files differ
diff --git a/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xxhdpi-v5/ic_tab_1_unselected.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xxhdpi-v5/ic_tab_1_unselected.png
new file mode 100644
index 0000000..5404d8e
--- /dev/null
+++ b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xxhdpi-v5/ic_tab_1_unselected.png
Binary files differ
diff --git a/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xxhdpi/ic_tab_1_selected.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xxhdpi/ic_tab_1_selected.png
new file mode 100644
index 0000000..6af169b
--- /dev/null
+++ b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xxhdpi/ic_tab_1_selected.png
Binary files differ
diff --git a/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xxhdpi/ic_tab_1_unselected.png b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xxhdpi/ic_tab_1_unselected.png
new file mode 100644
index 0000000..dbd9d05
--- /dev/null
+++ b/asset-studio/src/test/java/com/android/assetstudiolib/testdata/tabs/res/drawable-xxhdpi/ic_tab_1_unselected.png
Binary files differ
diff --git a/base.gradle b/base.gradle
new file mode 100644
index 0000000..ea96d17
--- /dev/null
+++ b/base.gradle
@@ -0,0 +1,29 @@
+apply plugin: 'findbugs'
+
+// find bug dependencies is added dynamically so it's hard for the
+// clone artifact plugin to find it. This custom config lets us manually
+// add such dependencies.
+configurations {
+ hidden
+}
+dependencies {
+ hidden "com.google.code.findbugs:findbugs:2.0.1"
+}
+
+// set all java compilation to use UTF-8 encoding.
+tasks.withType(JavaCompile) {
+ options.encoding = 'UTF-8'
+}
+
+task disableTestFailures << {
+ tasks.withType(Test) {
+ ignoreFailures = true
+ }
+}
+
+findbugs {
+ ignoreFailures = true
+ effort = "max"
+ reportLevel = "high"
+}
+
diff --git a/baseVersion.gradle b/baseVersion.gradle
new file mode 100644
index 0000000..c6cfacb
--- /dev/null
+++ b/baseVersion.gradle
@@ -0,0 +1,9 @@
+def getVersion() {
+ if (project.has("release")) {
+ return rootProject.ext.baseVersion
+ }
+
+ return rootProject.ext.baseVersion + '-SNAPSHOT'
+}
+
+version = getVersion()
diff --git a/build-system/.gitignore b/build-system/.gitignore
new file mode 100644
index 0000000..4c266ae
--- /dev/null
+++ b/build-system/.gitignore
@@ -0,0 +1,23 @@
+local.properties
+tests/*/build
+tests/api/*/build
+tests/applibtest/*/build
+tests/assets/*/build
+tests/attrOrder/*/build
+tests/3rdPartyTests/*/build
+tests/dependencies/jarProject/build
+tests/flavorlib/*/build
+tests/flavorlibWithFailedTests/*/build
+tests/libProguardJarDep/*/build
+tests/libProguardLibDep/*/build
+tests/libsTest/*/build
+tests/multiproject/*/build
+tests/localJars/*/build
+tests/ndkJniLib/*/build
+tests/proguardLib/*/build
+tests/renderscriptInLib/*/build
+tests/repo/*/build
+tests/sameNamedLibs/*/build
+tests/sameNamedLibs/*/*/build
+tests/tictactoe/*/build
+lint-results
diff --git a/build-system/builder-model/NOTICE b/build-system/builder-model/NOTICE
new file mode 100644
index 0000000..faed58a
--- /dev/null
+++ b/build-system/builder-model/NOTICE
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2005-2013, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/build-system/builder-model/build.gradle b/build-system/builder-model/build.gradle
new file mode 100644
index 0000000..9906989
--- /dev/null
+++ b/build-system/builder-model/build.gradle
@@ -0,0 +1,18 @@
+apply plugin: 'java'
+apply plugin: 'clone-artifacts'
+apply plugin: 'distrib'
+
+dependencies {
+ compile project(':common')
+}
+
+group = 'com.android.tools.build'
+archivesBaseName = 'builder-model'
+project.ext.pomName = 'Android Builder Model library'
+project.ext.pomDesc = 'Model for the Builder library.'
+
+apply from: '../../buildVersion.gradle'
+apply from: '../../publish.gradle'
+apply from: '../../javadoc.gradle'
+
+jar.manifest.attributes("Model-Version": "$version")
diff --git a/build-system/builder-model/builder-model.iml b/build-system/builder-model/builder-model.iml
new file mode 100644
index 0000000..7e8f9fd
--- /dev/null
+++ b/build-system/builder-model/builder-model.iml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module relativePaths="true" type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module" module-name="common" exported="" />
+ <orderEntry type="library" exported="" name="guava-tools" level="project" />
+ </component>
+</module>
+
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/AaptOptions.java b/build-system/builder-model/src/main/java/com/android/builder/model/AaptOptions.java
new file mode 100644
index 0000000..4cb9b36
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/AaptOptions.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.model;
+
+import java.util.Collection;
+
+/**
+ * Options for aapt.
+ */
+public interface AaptOptions {
+ /**
+ * Returns the value for the --ignore-assets option, or null
+ */
+ String getIgnoreAssets();
+
+ /**
+ * Returns the list of values for the -0 (disabled compression) option, or null
+ */
+ Collection<String> getNoCompress();
+}
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/AndroidArtifact.java b/build-system/builder-model/src/main/java/com/android/builder/model/AndroidArtifact.java
new file mode 100644
index 0000000..3cfd2f2
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/AndroidArtifact.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.io.File;
+import java.util.Collection;
+
+/**
+ * The information for a generated Android artifact.
+ */
+public interface AndroidArtifact extends BaseArtifact {
+
+ /**
+ * Returns the output file for this artifact. Depending on whether the project is an app
+ * or a library project, this could be an apk or an aar file.
+ *
+ * For test artifact for a library project, this would also be an apk.
+ *
+ * @return the output file.
+ */
+ @NonNull
+ File getOutputFile();
+
+ /**
+ * Returns whether the output file is signed. This is always false for the main artifact
+ * of a library project.
+ *
+ * @return true if the app is signed.
+ */
+ boolean isSigned();
+
+ /**
+ * Returns the name of the {@link SigningConfig} used for the signing. If none are setup or
+ * if this is the main artifact of a library project, then this is null.
+ *
+ * @return the name of the setup signing config.
+ */
+ @Nullable
+ String getSigningConfigName();
+
+ /**
+ * Returns the package name of this artifact.
+ *
+ * @return the package name.
+ */
+ @NonNull
+ String getPackageName();
+
+ /**
+ * Returns the name of the task used to generate the source code. The actual value might
+ * depend on the build system front end.
+ *
+ * @return the name of the code generating task.
+ */
+ @NonNull
+ String getSourceGenTaskName();
+
+ /**
+ * The generated manifest for this variant's artifact.
+ */
+ @NonNull
+ File getGeneratedManifest();
+
+ /**
+ * Returns all the source folders that are generated. This is typically folders for the R,
+ * the aidl classes, and the renderscript classes.
+ *
+ * @return a list of folders.
+ */
+ @NonNull
+ Collection<File> getGeneratedSourceFolders();
+
+ /**
+ * Returns all the resource folders that are generated. This is typically the renderscript
+ * output and the merged resources.
+ *
+ * @return a list of folder.
+ */
+ @NonNull
+ Collection<File> getGeneratedResourceFolders();
+}
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/AndroidLibrary.java b/build-system/builder-model/src/main/java/com/android/builder/model/AndroidLibrary.java
new file mode 100644
index 0000000..a46cb7e
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/AndroidLibrary.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Represents an Android Library dependency, its content and its own dependencies
+ */
+public interface AndroidLibrary {
+
+ /**
+ * Returns the project identifier if the library is output
+ * by a module.
+ *
+ * @return the project identifier
+ */
+ @Nullable
+ String getProject();
+
+ /**
+ * Returns the location of the library aar bundle.
+ */
+ @NonNull
+ File getBundle();
+
+ /**
+ * Returns the location of the unzipped bundle folder.
+ */
+ @NonNull
+ File getFolder();
+
+ /**
+ * Returns the direct dependency of this dependency. The order is important.
+ */
+ @NonNull
+ List<? extends AndroidLibrary> getLibraryDependencies();
+
+ /**
+ * Returns the location of the manifest.
+ */
+ @NonNull
+ File getManifest();
+
+ /**
+ * Returns the location of the jar file to use for packaging.
+ *
+ * @return a File for the jar file. The file may not point to an existing file.
+ */
+ @NonNull
+ File getJarFile();
+
+ /**
+ * Returns the list of local Jar files that are included in the dependency.
+ *
+ * @return a list of File. May be empty but not null.
+ */
+ @NonNull
+ Collection<File> getLocalJars();
+
+ /**
+ * Returns the location of the res folder.
+ *
+ * @return a File for the res folder. The file may not point to an existing folder.
+ */
+ @NonNull
+ File getResFolder();
+
+ /**
+ * Returns the location of the assets folder.
+ *
+ * @return a File for the assets folder. The file may not point to an existing folder.
+ */
+ @NonNull
+ File getAssetsFolder();
+
+ /**
+ * Returns the location of the jni libraries folder.
+ *
+ * @return a File for the folder. The file may not point to an existing folder.
+ */
+ @NonNull
+ File getJniFolder();
+
+ /**
+ * Returns the location of the aidl import folder.
+ *
+ * @return a File for the folder. The file may not point to an existing folder.
+ */
+ @NonNull
+ File getAidlFolder();
+
+ /**
+ * Returns the location of the renderscript import folder.
+ *
+ * @return a File for the folder. The file may not point to an existing folder.
+ */
+ @NonNull
+ File getRenderscriptFolder();
+
+ /**
+ * Returns the location of the proguard files.
+ *
+ * @return a File for the file. The file may not point to an existing file.
+ */
+ @NonNull
+ File getProguardRules();
+
+ /**
+ * Returns the location of the lint jar.
+ *
+ * @return a File for the jar file. The file may not point to an existing file.
+ */
+ @NonNull
+ File getLintJar();
+}
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/AndroidProject.java b/build-system/builder-model/src/main/java/com/android/builder/model/AndroidProject.java
new file mode 100644
index 0000000..9a630d3
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/AndroidProject.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.model;
+
+import com.android.annotations.NonNull;
+
+import java.io.File;
+import java.util.Collection;
+
+/**
+ * Entry point for the model of the Android Projects. This models a single module, whether
+ * the module is an app project or a library project.
+ */
+public interface AndroidProject {
+ String BUILD_MODEL_ONLY_SYSTEM_PROPERTY = "android.build.model.only";
+
+ public static final String ARTIFACT_MAIN = "_main_";
+ public static final String ARTIFACT_INSTRUMENT_TEST = "_instrument_test_";
+
+ /**
+ * Returns the model version. This is a string in the format X.Y.Z
+ *
+ * @return a string containing the model version.
+ */
+ @NonNull
+ String getModelVersion();
+
+ /**
+ * Returns the name of the module.
+ *
+ * @return the name of the module.
+ */
+ @NonNull
+ String getName();
+
+ /**
+ * Returns whether this is a library.
+ * @return true for a library module.
+ */
+ boolean isLibrary();
+
+ /**
+ * Returns the {@link ProductFlavorContainer} for the 'main' default config.
+ *
+ * @return the product flavor.
+ */
+ @NonNull
+ ProductFlavorContainer getDefaultConfig();
+
+ /**
+ * Returns a list of all the {@link BuildType} in their container.
+ *
+ * @return a list of build type containers.
+ */
+ @NonNull
+ Collection<BuildTypeContainer> getBuildTypes();
+
+ /**
+ * Returns a list of all the {@link ProductFlavor} in their container.
+ *
+ * @return a list of product flavor containers.
+ */
+ @NonNull
+ Collection<ProductFlavorContainer> getProductFlavors();
+
+ /**
+ * Returns a list of all the variants.
+ *
+ * This does not include test variant. Test variants are additional artifacts in their
+ * respective variant info.
+ *
+ * @return a list of the variants.
+ */
+ @NonNull
+ Collection<Variant> getVariants();
+
+ /**
+ * Returns a list of extra artifacts meta data. This does not include the main artifact.
+ *
+ * @return a list of extra artifacts
+ */
+ @NonNull
+ Collection<ArtifactMetaData> getExtraArtifacts();
+
+ /**
+ * Returns the compilation target as a string. This is the full extended target hash string.
+ * (see com.android.sdklib.IAndroidTarget#hashString())
+ *
+ * @return the target hash string
+ */
+ @NonNull
+ String getCompileTarget();
+
+ /**
+ * Returns the boot classpath matching the compile target. This is typically android.jar plus
+ * other optional libraries.
+ *
+ * @return a list of jar files.
+ */
+ @NonNull
+ Collection<String> getBootClasspath();
+
+ /**
+ * Returns a list of folders or jar files that contains the framework source code.
+ * @return a list of folders or jar files that contains the framework source code.
+ */
+ @NonNull
+ Collection<File> getFrameworkSources();
+
+ /**
+ * Returns a list of {@link SigningConfig}.
+ *
+ * @return a map of signing config
+ */
+ @NonNull
+ Collection<SigningConfig> getSigningConfigs();
+
+ /**
+ * Returns the aapt options.
+ *
+ * @return the aapt options.
+ */
+ @NonNull
+ AaptOptions getAaptOptions();
+
+ /**
+ * Returns the lint options.
+ *
+ * @return the lint options.
+ */
+ @NonNull
+ LintOptions getLintOptions();
+
+ /**
+ * Returns the dependencies that were not successfully resolved. The returned list gets
+ * populated only if the system property {@link #BUILD_MODEL_ONLY_SYSTEM_PROPERTY} has been
+ * set to {@code true}.
+ * <p>
+ * Each value of the collection has the format group:name:version, for example:
+ * com.google.guava:guava:15.0.2
+ *
+ * @return the dependencies that were not successfully resolved.
+ */
+ @NonNull
+ Collection<String> getUnresolvedDependencies();
+
+ /**
+ * @return the compile options for Java code.
+ */
+ @NonNull
+ JavaCompileOptions getJavaCompileOptions();
+}
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/ArtifactMetaData.java b/build-system/builder-model/src/main/java/com/android/builder/model/ArtifactMetaData.java
new file mode 100644
index 0000000..f1af029
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/ArtifactMetaData.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.model;
+
+import com.android.annotations.NonNull;
+
+/**
+ * Meta Data for an Artifact.
+ */
+public interface ArtifactMetaData {
+
+ public final static int TYPE_ANDROID = 1;
+ public final static int TYPE_JAVA = 2;
+
+ @NonNull
+ String getName();
+
+ boolean isTest();
+
+ int getType();
+}
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/BaseArtifact.java b/build-system/builder-model/src/main/java/com/android/builder/model/BaseArtifact.java
new file mode 100644
index 0000000..fd1a7b7
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/BaseArtifact.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.io.File;
+
+/**
+ * The base information for all generated artifacts
+ */
+public interface BaseArtifact {
+
+ /**
+ * Name of the artifact. This should match {@link ArtifactMetaData#getName()}.
+ */
+ @NonNull
+ String getName();
+
+ /**
+ * @return the name of the task used to compile Java code.
+ */
+ @NonNull
+ String getJavaCompileTaskName();
+
+ /**
+ * Returns the name of the task used to generate the artifact.
+ *
+ * @return the name of the task.
+ */
+ @NonNull
+ String getAssembleTaskName();
+
+ /**
+ * Returns the folder containing the class files. This is the output of the java compilation.
+ *
+ * @return a folder.
+ */
+ @NonNull
+ File getClassesFolder();
+
+ /**
+ * Returns the resolved dependencies for this artifact. This is a composite of all the
+ * dependencies for that artifact: default config + build type + flavor(s).s
+ *
+ * @return The dependencies.
+ */
+ @NonNull
+ Dependencies getDependencies();
+
+ /**
+ * A SourceProvider specific to the variant. This can be null if there is no flavors as
+ * the "variant" is equal to the build type.
+ *
+ * @return the variant specific source provider
+ */
+ @Nullable
+ SourceProvider getVariantSourceProvider();
+
+ /**
+ * A SourceProvider specific to the flavor combination.
+ *
+ * For instance if there are 2 dimensions, then this would be Flavor1Flavor2, and would be
+ * common to all variant using these two flavors and any of the build type.
+ *
+ * This can be null if there is less than 2 flavors.
+ *
+ * @return the multi flavor specific source provider
+ */
+ @Nullable
+ SourceProvider getMultiFlavorSourceProvider();
+}
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/BaseConfig.java b/build-system/builder-model/src/main/java/com/android/builder/model/BaseConfig.java
new file mode 100644
index 0000000..d4e22c1
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/BaseConfig.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.model;
+
+import com.android.annotations.NonNull;
+
+import java.io.File;
+import java.util.Collection;
+
+/**
+ * Base config object for Build Type and Product flavor.
+ */
+public interface BaseConfig {
+
+ /**
+ * List of Build Config Fields
+ * @return a non-null list of class fields (possibly empty)
+ */
+ @NonNull
+ Collection<ClassField> getBuildConfigFields();
+
+ /**
+ * Returns the list of proguard rule files.
+ *
+ * @return a non-null list of files.
+ */
+ @NonNull
+ Collection<File> getProguardFiles();
+
+ /**
+ * Returns the list of proguard rule files for consumers of the library to use.
+ *
+ * @return a non-null list of files.
+ */
+ @NonNull
+ Collection<File> getConsumerProguardFiles();
+}
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/BuildType.java b/build-system/builder-model/src/main/java/com/android/builder/model/BuildType.java
new file mode 100644
index 0000000..c8cf61e
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/BuildType.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+/**
+ * a Build Type. This is only the configuration of the build type.
+ *
+ * It does not include the sources or the dependencies. Those are available on the container
+ * or in the artifact info.
+ *
+ * @see BuildTypeContainer
+ * @see AndroidArtifact#getDependencies()
+ */
+public interface BuildType extends BaseConfig {
+
+ /**
+ * Returns the name of the build type.
+ *
+ * @return the name of the build type.
+ */
+ @NonNull
+ String getName();
+
+ /**
+ * Returns whether the build type is configured to generate a debuggable apk.
+ *
+ * @return true if the apk is debuggable
+ */
+ boolean isDebuggable();
+
+ /**
+ * Returns whether the build type is configured to generate an apk with debuggable native code.
+ *
+ * @return true if the apk is debuggable
+ */
+ boolean isJniDebugBuild();
+
+ /**
+ * Returns whether the build type is configured to generate an apk with debuggable
+ * renderscript code.
+ *
+ * @return true if the apk is debuggable
+ */
+ boolean isRenderscriptDebugBuild();
+
+ /**
+ * Returns the optimization level of the renderscript compilation.
+ *
+ * @return the optimization level.
+ */
+ int getRenderscriptOptimLevel();
+
+ /**
+ * Returns the package name suffix applied to this build type.
+ * To get the final package name, use {@link AndroidArtifact#getPackageName()}.
+ *
+ * @return the package name suffix.
+ */
+ @Nullable
+ String getPackageNameSuffix();
+
+ /**
+ * Returns the version name suffix.
+ *
+ * @return the version name suffix.
+ */
+ @Nullable
+ String getVersionNameSuffix();
+
+ /**
+ * Returns whether proguard is enabled for this build type.
+ *
+ * @return true if proguard is enabled.
+ */
+ boolean isRunProguard();
+
+ /**
+ * Return whether zipalign is enabled for this build type.
+ *
+ * @return true if zipalign is enabled.
+ */
+ boolean isZipAlign();
+
+ /**
+ * Returns the NDK configuration.
+ * @return the ndk config.
+ */
+ @Nullable
+ NdkConfig getNdkConfig();
+}
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/BuildTypeContainer.java b/build-system/builder-model/src/main/java/com/android/builder/model/BuildTypeContainer.java
new file mode 100644
index 0000000..87765f1
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/BuildTypeContainer.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.model;
+
+import com.android.annotations.NonNull;
+
+import java.util.Collection;
+
+/**
+ * A Container of all the data related to {@link BuildType}.
+ */
+public interface BuildTypeContainer {
+
+ /**
+ * The Build Type itself.
+ *
+ * @return the build type
+ */
+ @NonNull
+ BuildType getBuildType();
+
+ /**
+ * The associated sources of the build type.
+ *
+ * @return the build type source provider.
+ */
+ @NonNull
+ SourceProvider getSourceProvider();
+
+ /**
+ * Returns a list of ArtifactMetaData/SourceProvider association.
+ *
+ * @return a list of ArtifactMetaData/SourceProvider association.
+ */
+ @NonNull
+ Collection<SourceProviderContainer> getExtraSourceProviders();
+}
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/ClassField.java b/build-system/builder-model/src/main/java/com/android/builder/model/ClassField.java
new file mode 100644
index 0000000..d6b6a26
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/ClassField.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.model;
+
+import com.android.annotations.NonNull;
+
+/**
+ * A Simple class field with name, type and value, all as strings.
+ */
+public interface ClassField {
+ @NonNull
+ String getType();
+
+ @NonNull
+ String getName();
+
+ @NonNull
+ String getValue();
+}
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/Dependencies.java b/build-system/builder-model/src/main/java/com/android/builder/model/Dependencies.java
new file mode 100644
index 0000000..f28b648
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/Dependencies.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.model;
+
+import com.android.annotations.NonNull;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * A set of dependencies for an {@link AndroidArtifact}.
+ */
+public interface Dependencies {
+
+ /**
+ * The list of Android library dependencies. This includes both modules and external
+ * dependencies.
+ *
+ * @return the list of libraries.
+ */
+ @NonNull
+ List<AndroidLibrary> getLibraries();
+
+ /**
+ * The list of jar dependencies. This only includes external dependencies.
+ *
+ * @return the list of jar files.
+ */
+ @NonNull
+ Collection<File> getJars();
+
+ /**
+ * The list of project dependencies. This is only for non Android module dependencies (which
+ * right now is Java-only modules).
+ *
+ * @return the list of projects.
+ */
+ @NonNull
+ Collection<String> getProjects();
+}
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/JavaArtifact.java b/build-system/builder-model/src/main/java/com/android/builder/model/JavaArtifact.java
new file mode 100644
index 0000000..02cba55
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/JavaArtifact.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.model;
+
+/**
+ * The information for a generated Java artifact.
+ */
+public interface JavaArtifact extends BaseArtifact {
+
+}
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/JavaCompileOptions.java b/build-system/builder-model/src/main/java/com/android/builder/model/JavaCompileOptions.java
new file mode 100644
index 0000000..dafe83a
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/JavaCompileOptions.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.builder.model;
+
+import com.android.annotations.NonNull;
+
+/**
+ * Java compile options.
+ */
+public interface JavaCompileOptions {
+ /**
+ * @return the level of compliance Java source code has.
+ */
+ @NonNull
+ String getSourceCompatibility();
+
+ /**
+ * @return the Java version to be able to run classes on.
+ */
+ @NonNull
+ String getTargetCompatibility();
+}
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/LintOptions.java b/build-system/builder-model/src/main/java/com/android/builder/model/LintOptions.java
new file mode 100644
index 0000000..c2ae6de
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/LintOptions.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.io.File;
+import java.util.Set;
+
+/**
+ * Options for lint.
+ * Example:
+ *
+ * <pre>
+ *
+ * android {
+ * lintOptions {
+ * // set to true to turn off analysis progress reporting by lint
+ * quiet true
+ * // if true, stop the gradle build if errors are found
+ * abortOnError false
+ * // if true, only report errors
+ * ignoreWarnings true
+ * // if true, emit full/absolute paths to files with errors (true by default)
+ * //absolutePaths true
+ * // if true, check all issues, including those that are off by default
+ * checkAllWarnings true
+ * // if true, treat all warnings as errors
+ * warningsAsErrors true
+ * // turn off checking the given issue id's
+ * disable 'TypographyFractions','TypographyQuotes'
+ * // turn on the given issue id's
+ * enable 'RtlHardcoded','RtlCompat', 'RtlEnabled'
+ * // check *only* the given issue id's
+ * check 'NewApi', 'InlinedApi'
+ * // if true, don't include source code lines in the error output
+ * noLines true
+ * // if true, show all locations for an error, do not truncate lists, etc.
+ * showAll true
+ * // Fallback lint configuration (default severities, etc.)
+ * lintConfig file("default-lint.xml")
+ * // if true, generate a text report of issues (false by default)
+ * textReport true
+ * // location to write the output; can be a file or 'stdout'
+ * //textOutput 'stdout'
+ * textOutput file("lint-results.txt")
+ * // if true, generate an XML report for use by for example Jenkins
+ * xmlReport true
+ * // file to write report to (if not specified, defaults to lint-results.xml)
+ * xmlOutput file("lint-report.xml")
+ * // if true, generate an HTML report (with issue explanations, sourcecode, etc)
+ * htmlReport true
+ * // optional path to report (default will be lint-results.html in the builddir)
+ * htmlOutput file("lint-report.html")
+ * }
+ * }
+ * </pre>
+ */
+public interface LintOptions {
+ /**
+ * Returns the set of issue id's to suppress. Callers are allowed to modify this collection.
+ * To suppress a given issue, add the lint issue id to the returned set.
+ */
+ @NonNull
+ public Set<String> getDisable();
+
+ /**
+ * Returns the set of issue id's to enable. Callers are allowed to modify this collection.
+ * To enable a given issue, add the lint issue id to the returned set.
+ */
+ @NonNull
+ public Set<String> getEnable();
+
+ /**
+ * Returns the exact set of issues to check, or null to run the issues that are enabled
+ * by default plus any issues enabled via {@link #getEnable} and without issues disabled
+ * via {@link #getDisable}. If non-null, callers are allowed to modify this collection.
+ */
+ @Nullable
+ public Set<String> getCheck();
+
+ /** Whether lint should abort the build if errors are found */
+ public boolean isAbortOnError();
+
+ /**
+ * Whether lint should display full paths in the error output. By default the paths
+ * are relative to the path lint was invoked from.
+ */
+ public boolean isAbsolutePaths();
+
+ /**
+ * Whether lint should include the source lines in the output where errors occurred
+ * (true by default)
+ */
+ public boolean isNoLines();
+
+ /**
+ * Returns whether lint should be quiet (for example, not show progress dots for each analyzed
+ * file)
+ */
+ public boolean isQuiet();
+
+ /** Returns whether lint should check all warnings, including those off by default */
+ public boolean isCheckAllWarnings();
+
+ /** Returns whether lint will only check for errors (ignoring warnings) */
+ public boolean isIgnoreWarnings();
+
+ /** Returns whether lint should treat all warnings as errors */
+ public boolean isWarningsAsErrors();
+
+ /**
+ * Returns whether lint should include all output (e.g. include all alternate
+ * locations, not truncating long messages, etc.)
+ */
+ public boolean isShowAll();
+
+ /**
+ * Returns an optional path to a lint.xml configuration file
+ */
+ @Nullable
+ public File getLintConfig();
+
+ /** Whether we should write an text report. Default false. The location can be
+ * controlled by {@link #getTextOutput()}. */
+ public boolean getTextReport();
+
+ /**
+ * The optional path to where a text report should be written. The special value
+ * "stdout" can be used to point to standard output.
+ */
+ @Nullable
+ public File getTextOutput();
+
+ /** Whether we should write an HTML report. Default true. The location can be
+ * controlled by {@link #getHtmlOutput()}. */
+ public boolean getHtmlReport();
+
+ /** The optional path to where an HTML report should be written */
+ @Nullable
+ public File getHtmlOutput();
+
+ /** Whether we should write an XML report. Default true. The location can be
+ * controlled by {@link #getXmlOutput()}. */
+ public boolean getXmlReport();
+
+ /** The optional path to where an XML report should be written */
+ @Nullable
+ public File getXmlOutput();
+
+}
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/NdkConfig.java b/build-system/builder-model/src/main/java/com/android/builder/model/NdkConfig.java
new file mode 100644
index 0000000..25056c7
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/NdkConfig.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.model;
+
+import com.android.annotations.Nullable;
+
+import java.util.Collection;
+
+/**
+ * Base class for NDK config file.
+ */
+public interface NdkConfig {
+
+ /**
+ * The module name
+ */
+ @Nullable
+ public String getModuleName();
+
+ /**
+ * The C Flags
+ */
+ @Nullable
+ public String getcFlags();
+
+ /**
+ * The LD Libs
+ */
+ @Nullable
+ public Collection<String> getLdLibs();
+
+ /**
+ * The ABI Filters
+ */
+ @Nullable
+ public Collection<String> getAbiFilters();
+
+ /**
+ * The APP_STL value
+ */
+ @Nullable
+ public String getStl();
+}
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/ProductFlavor.java b/build-system/builder-model/src/main/java/com/android/builder/model/ProductFlavor.java
new file mode 100644
index 0000000..2e5eb81
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/ProductFlavor.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.util.Collection;
+
+/**
+ * a Product Flavor. This is only the configuration of the flavor.
+ *
+ * It does not include the sources or the dependencies. Those are available on the container
+ * or in the artifact info.
+ *
+ * @see ProductFlavorContainer
+ * @see BaseArtifact#getDependencies()
+ */
+public interface ProductFlavor extends BaseConfig {
+
+ /**
+ * Returns the name of the flavor.
+ *
+ * @return the name of the flavor.
+ */
+ @NonNull
+ String getName();
+
+ /**
+ * Returns the name of the product flavor. This is only the value set on this product flavor.
+ * To get the final package name, use {@link AndroidArtifact#getPackageName()}.
+ *
+ * @return the package name.
+ */
+ @Nullable
+ String getPackageName();
+
+ /**
+ * Returns the version code. This is only the value set on this product flavor.
+ * To get the final value, use {@link Variant#getMergedFlavor()}
+ *
+ * @return the version code
+ */
+ int getVersionCode();
+
+ /**
+ * Returns the version name. This is only the value set on this product flavor.
+ * To get the final value, use {@link Variant#getMergedFlavor()} as well as
+ * {@link BuildType#getVersionNameSuffix()}
+ *
+ * @return the version name.
+ */
+ @Nullable
+ String getVersionName();
+
+ /**
+ * Returns the minSdkVersion. This is only the value set on this product flavor.
+ * TODO: make final minSdkVersion available through the model
+ *
+ * @return the minSdkVersion
+ */
+ int getMinSdkVersion();
+
+ /**
+ * Returns the targetSdkVersion. This is only the value set on this product flavor.
+ * TODO: make final targetSdkVersion available through the model
+ *
+ * @return the targetSdkVersion
+ */
+ int getTargetSdkVersion();
+
+ /**
+ * Returns the renderscript target api. This is only the value set on this product flavor.
+ * TODO: make final renderscript target api available through the model
+ *
+ * @return the renderscript target api
+ */
+ int getRenderscriptTargetApi();
+
+ /**
+ * Returns whether the renderscript code should be compiled in support mode to
+ * make it compatible with older versions of Android.
+ *
+ * @return true if support mode is enabled.
+ */
+ boolean getRenderscriptSupportMode();
+
+ /**
+ * Returns whether the renderscript code should be compiled to generate C/C++ bindings.
+ * @return true for C/C++ generation, false for Java
+ */
+ boolean getRenderscriptNdkMode();
+
+ /**
+ * Returns the test package name. This is only the value set on this product flavor.
+ * To get the final value, use {@link Variant#getTestArtifactInfo()} and
+ * {@link AndroidArtifact#getPackageName()}
+ *
+ * @return the test package name.
+ */
+ @Nullable
+ String getTestPackageName();
+
+ /**
+ * Returns the test package name. This is only the value set on this product flavor.
+ * TODO: make test instrumentation runner available through the model.
+ *
+ * @return the test package name.
+ */
+ @Nullable
+ String getTestInstrumentationRunner();
+
+ /**
+ * Returns the handlingProfile value. This is only the value set on this product flavor.
+ *
+ * @return the handlingProfile value.
+ */
+ @Nullable
+ Boolean getTestHandleProfiling();
+
+ /**
+ * Returns the functionalTest value. This is only the value set on this product flavor.
+ *
+ * @return the functionalTest value.
+ */
+ @Nullable
+ Boolean getTestFunctionalTest();
+
+ /**
+ * Returns the NDK configuration.
+ * @return the ndk config.
+ */
+ @Nullable
+ NdkConfig getNdkConfig();
+
+ /**
+ * Returns the resource configuration for this variant.
+ * TODO implement this.
+ *
+ * This is the list of -c parameters for aapt.
+ *
+ * @return the resource configuration options.
+ */
+ @NonNull
+ Collection<String> getResourceConfigurations();
+}
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/ProductFlavorContainer.java b/build-system/builder-model/src/main/java/com/android/builder/model/ProductFlavorContainer.java
new file mode 100644
index 0000000..8643f9d
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/ProductFlavorContainer.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.model;
+
+import com.android.annotations.NonNull;
+
+import java.util.Collection;
+
+/**
+ * A Container of all the data related to {@link ProductFlavor}.
+ */
+public interface ProductFlavorContainer {
+
+ /**
+ * The Product Flavor itself.
+ *
+ * @return the product flavor
+ */
+ @NonNull
+ ProductFlavor getProductFlavor();
+
+ /**
+ * The associated main sources of the product flavor
+ *
+ * @return the main source provider.
+ */
+ @NonNull
+ SourceProvider getSourceProvider();
+
+ /**
+ * Returns a list of ArtifactMetaData/SourceProvider association.
+ *
+ * @return a list of ArtifactMetaData/SourceProvider association.
+ */
+ @NonNull
+ Collection<SourceProviderContainer> getExtraSourceProviders();
+}
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/SigningConfig.java b/build-system/builder-model/src/main/java/com/android/builder/model/SigningConfig.java
new file mode 100644
index 0000000..99aedf3
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/SigningConfig.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.io.File;
+
+/**
+ * A Signing Configuration
+ */
+public interface SigningConfig {
+
+ /**
+ * Returns the name of the Signing config
+ *
+ * @return the name of the config
+ */
+ @NonNull
+ public String getName();
+
+ /**
+ * Returns the keystore file.
+ *
+ * @return the file.
+ */
+ @Nullable
+ File getStoreFile();
+
+ /**
+ * Returns the keystore password.
+ *
+ * @return the password.
+ */
+ @Nullable
+ String getStorePassword();
+
+ /**
+ * Returns the key alias name.
+ *
+ * @return the key alias name.
+ */
+ @Nullable
+ String getKeyAlias();
+
+ /**
+ * return the key password.
+ *
+ * @return the password.
+ */
+ @Nullable
+ String getKeyPassword();
+
+ /**
+ * Returns the store type.
+ *
+ * @return the store type.
+ */
+ @Nullable
+ String getStoreType();
+
+ /**
+ * Returns whether the config is fully configured for signing.
+ *
+ * @return true if all the required information are present.
+ */
+ boolean isSigningReady();
+}
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/SourceProvider.java b/build-system/builder-model/src/main/java/com/android/builder/model/SourceProvider.java
new file mode 100644
index 0000000..6d58e64
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/SourceProvider.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.builder.model;
+
+import com.android.annotations.NonNull;
+
+import java.io.File;
+import java.util.Collection;
+
+/**
+ * Represent a SourceProvider for a given configuration.
+ *
+ * TODO: source filters?
+ */
+public interface SourceProvider {
+
+ /**
+ * Returns the manifest file.
+ *
+ * @return the manifest file. It may not exist.
+ */
+ @NonNull
+ File getManifestFile();
+
+ /**
+ * Returns the java source folders.
+ *
+ * @return a list of folders. They may not all exist.
+ */
+ @NonNull
+ Collection<File> getJavaDirectories();
+
+ /**
+ * Returns the java resources folders.
+ *
+ * @return a list of folders. They may not all exist.
+ */
+ @NonNull
+ Collection<File> getResourcesDirectories();
+
+ /**
+ * Returns the aidl source folders.
+ *
+ * @return a list of folders. They may not all exist.
+ */
+ @NonNull
+ Collection<File> getAidlDirectories();
+
+ /**
+ * Returns the renderscript source folders.
+ *
+ * @return a list of folders. They may not all exist.
+ */
+ @NonNull
+ Collection<File> getRenderscriptDirectories();
+
+ /**
+ * Returns the jni source folders.
+ *
+ * @return a list of folders. They may not all exist.
+ */
+ @NonNull
+ Collection<File> getJniDirectories();
+
+ /**
+ * Returns the android resources folders.
+ *
+ * @return a list of folders. They may not all exist.
+ */
+ @NonNull
+ Collection<File> getResDirectories();
+
+ /**
+ * Returns the android assets folders.
+ *
+ * @return a list of folders. They may not all exist.
+ */
+ @NonNull
+ Collection<File> getAssetsDirectories();
+}
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/SourceProviderContainer.java b/build-system/builder-model/src/main/java/com/android/builder/model/SourceProviderContainer.java
new file mode 100644
index 0000000..0ac8387
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/SourceProviderContainer.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.model;
+
+import com.android.annotations.NonNull;
+
+/**
+ * An association of an {@link ArtifactMetaData}'s name and a {@link SourceProvider}.
+ */
+public interface SourceProviderContainer {
+
+ /**
+ * Returns the name matching {@link ArtifactMetaData#getName()}
+ */
+ @NonNull
+ String getArtifactName();
+
+ /**
+ * Returns the source provider
+ */
+ @NonNull
+ SourceProvider getSourceProvider();
+}
diff --git a/build-system/builder-model/src/main/java/com/android/builder/model/Variant.java b/build-system/builder-model/src/main/java/com/android/builder/model/Variant.java
new file mode 100644
index 0000000..67813ae
--- /dev/null
+++ b/build-system/builder-model/src/main/java/com/android/builder/model/Variant.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.model;
+
+import com.android.annotations.NonNull;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * A build Variant.
+ */
+public interface Variant {
+
+ /**
+ * Returns the name of the variant. It is made up of the build type and flavors (if applicable)
+ *
+ * @return the name of the variant.
+ */
+ @NonNull
+ String getName();
+
+ /**
+ * Returns a display name for the variant. It is made up of the build type and flavors
+ * (if applicable)
+ *
+ * @return the name.
+ */
+ @NonNull
+ String getDisplayName();
+
+ /**
+ * Returns the main artifact for this variant.
+ *
+ * @return the artifact.
+ */
+ @NonNull
+ AndroidArtifact getMainArtifact();
+
+ @NonNull
+ Collection<AndroidArtifact> getExtraAndroidArtifacts();
+
+ @NonNull
+ Collection<JavaArtifact> getExtraJavaArtifacts();
+
+ /**
+ * Returns the build type. All variants have a build type, so this is never null.
+ *
+ * @return the name of the build type.
+ */
+ @NonNull
+ String getBuildType();
+
+ /**
+ * Returns the flavors for this variants. This can be empty if no flavors are configured.
+ *
+ * @return a list of flavors which can be empty.
+ */
+ @NonNull
+ List<String> getProductFlavors();
+
+ /**
+ * The result of the merge of all the flavors and of the main default config. If no flavors
+ * are defined then this is the same as the default config.
+ *
+ * This is directly a ProductFlavor instance of a ProdutFlavorContainer since this a composite
+ * of existing ProductFlavors.
+ *
+ * @return the merged flavors.
+ *
+ * @see AndroidProject#getDefaultConfig()
+ */
+ @NonNull
+ ProductFlavor getMergedFlavor();
+}
diff --git a/build-system/builder-test-api/build.gradle b/build-system/builder-test-api/build.gradle
new file mode 100644
index 0000000..2c4c8ae
--- /dev/null
+++ b/build-system/builder-test-api/build.gradle
@@ -0,0 +1,18 @@
+apply plugin: 'java'
+apply plugin: 'clone-artifacts'
+
+dependencies {
+ compile project(':ddmlib')
+}
+
+group = 'com.android.tools.build'
+archivesBaseName = 'builder-test-api'
+project.ext.pomName = 'Android Builder Test API library'
+project.ext.pomDesc = 'API for the Test extension point in the Builder library.'
+
+apply from: '../../buildVersion.gradle'
+apply from: '../../publish.gradle'
+apply from: '../../javadoc.gradle'
+
+apply plugin: 'distrib'
+shipping.isShipping = false
diff --git a/build-system/builder-test-api/builder-test-api.iml b/build-system/builder-test-api/builder-test-api.iml
new file mode 100644
index 0000000..ee69c52
--- /dev/null
+++ b/build-system/builder-test-api/builder-test-api.iml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module relativePaths="true" type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module" module-name="ddmlib" exported="" />
+ <orderEntry type="library" exported="" name="guava-tools" level="project" />
+ </component>
+</module>
+
diff --git a/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceConnector.java b/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceConnector.java
new file mode 100644
index 0000000..48208e8
--- /dev/null
+++ b/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceConnector.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.testing.api;
+
+import com.android.annotations.NonNull;
+import com.android.ddmlib.IShellEnabledDevice;
+import com.android.ddmlib.TimeoutException;
+import com.android.utils.ILogger;
+import com.google.common.annotations.Beta;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * A connector to a device to install/uninstall APKs, and run shell command.
+ */
+@Beta
+public abstract class DeviceConnector implements IShellEnabledDevice {
+
+ /**
+ * Establishes the connection with the device. Called before any other actions.
+ * @param timeOut the time out.
+ * @throws TimeoutException
+ */
+ public abstract void connect(int timeOut, ILogger logger) throws TimeoutException;
+
+ /**
+ * Disconnects from the device. No other action is called afterwards.
+ * @param timeOut the time out.
+ * @throws TimeoutException
+ */
+ public abstract void disconnect(int timeOut, ILogger logger) throws TimeoutException;
+
+ /**
+ * Installs the given APK on the device.
+ * @param apkFile the APK file to install.
+ * @param timeout the time out.
+ * @throws DeviceException
+ */
+ public abstract void installPackage(@NonNull File apkFile, int timeout, ILogger logger) throws DeviceException;
+
+ /**
+ * Uninstall the given package name from the device
+ * @param packageName the package name
+ * @param timeout the time out
+ * @throws DeviceException
+ */
+ public abstract void uninstallPackage(@NonNull String packageName, int timeout, ILogger logger) throws DeviceException;
+
+ /**
+ * Returns the API level of the device, or 0 if it could not be queried.
+ * @return the api level
+ */
+ public abstract int getApiLevel();
+
+ /**
+ * The device supported ABIs. This is in preferred order.
+ * @return the list of supported ABIs
+ */
+ @NonNull
+ public abstract List<String> getAbis();
+
+ public abstract int getDensity();
+
+ public abstract int getHeight();
+
+ public abstract int getWidth();
+}
diff --git a/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceException.java b/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceException.java
new file mode 100644
index 0000000..4268d50
--- /dev/null
+++ b/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceException.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.testing.api;
+
+import com.android.annotations.NonNull;
+import com.google.common.annotations.Beta;
+
+/**
+ * Exception thrown during device actions.
+ */
+@Beta
+public class DeviceException extends Exception {
+
+ public DeviceException(@NonNull Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceProvider.java b/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceProvider.java
new file mode 100644
index 0000000..6f28ed9
--- /dev/null
+++ b/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/DeviceProvider.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.testing.api;
+
+import com.android.annotations.NonNull;
+import com.google.common.annotations.Beta;
+
+import java.util.List;
+
+/**
+ * Provides a list of remote or local devices.
+ */
+@Beta
+public abstract class DeviceProvider {
+
+ /**
+ * Returns the name of the provider. Must be unique, not contain spaces, and start with a lower
+ * case.
+ *
+ * @return the name of the provider.
+ */
+ @NonNull
+ public abstract String getName();
+
+ /**
+ * Initializes the provider. This is called before any other method (except {@link #getName()}).
+ * @throws DeviceException
+ */
+ public abstract void init() throws DeviceException;
+
+ public abstract void terminate() throws DeviceException;
+
+ /**
+ * Returns a list of DeviceConnector.
+ * @return a non-null list (but could be empty.)
+ */
+ @NonNull
+ public abstract List<? extends DeviceConnector> getDevices();
+
+ /**
+ * Returns the timeout to use.
+ * @return the time out.
+ */
+ public abstract int getTimeout();
+
+ /**
+ * Returns true if the provider is configured and able to run.
+ *
+ * @return if the provider is configured.
+ */
+ public abstract boolean isConfigured();
+
+ public int getMaxThreads() {
+ return 0;
+ }
+}
diff --git a/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/TestException.java b/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/TestException.java
new file mode 100644
index 0000000..5536996
--- /dev/null
+++ b/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/TestException.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.testing.api;
+
+import com.android.annotations.NonNull;
+import com.google.common.annotations.Beta;
+
+/**
+ * Exception thrown during test actions.
+ */
+@Beta
+public class TestException extends Exception {
+
+ public TestException(@NonNull Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/TestServer.java b/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/TestServer.java
new file mode 100644
index 0000000..5a99470
--- /dev/null
+++ b/build-system/builder-test-api/src/main/java/com/android/builder/testing/api/TestServer.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.testing.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.google.common.annotations.Beta;
+
+import java.io.File;
+
+/**
+ * Base interface for Remote CI Servers.
+ */
+@Beta
+public abstract class TestServer {
+
+ /**
+ * Returns the name of the server. Must be unique, not contain spaces, and start with a lower
+ * case.
+ *
+ * @return the name of the provider.
+ */
+ @NonNull
+ public abstract String getName();
+
+ /**
+ * Uploads the APKs to the server.
+ *
+ * @param variantName the name of the variant being tested.
+ * @param testApk the APK containing the tests.
+ * @param testedApk the APK to be tested. This is optional in case the test apk is self-tested.
+ */
+ public abstract void uploadApks(@NonNull String variantName,
+ @NonNull File testApk, @Nullable File testedApk);
+
+ /**
+ * Returns true if the server is configured and able to run.
+ *
+ * @return if the server is configured.
+ */
+ public abstract boolean isConfigured();
+}
diff --git a/build-system/builder/.settings/org.eclipse.jdt.core.prefs b/build-system/builder/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..7a52926
--- /dev/null
+++ b/build-system/builder/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,100 @@
+#
+#Mon Aug 20 17:59:32 PDT 2012
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
+org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning
+org.eclipse.jdt.core.compiler.problem.unclosedCloseable=error
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
+org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
+org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=warning
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
+org.eclipse.jdt.core.compiler.annotation.nonnull=com.android.annotations.NonNull
+org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
+org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
+org.eclipse.jdt.core.compiler.problem.unusedImport=warning
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=com.android.annotations.NonNullByDefault
+org.eclipse.jdt.core.compiler.annotation.nullable=com.android.annotations.Nullable
+org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning
+org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning
+org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=enabled
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
+org.eclipse.jdt.core.compiler.problem.potentialNullSpecViolation=error
+org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
+org.eclipse.jdt.core.compiler.problem.nullReference=error
+org.eclipse.jdt.core.compiler.source=1.6
+org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.fieldHiding=warning
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
+org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning
+org.eclipse.jdt.core.compiler.problem.deadCode=warning
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
+org.eclipse.jdt.core.compiler.problem.nullSpecInsufficientInfo=warning
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning
+org.eclipse.jdt.core.compiler.annotation.nonnullisdefault=disabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
+org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
+org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=disabled
+org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=error
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=ignore
diff --git a/build-system/builder/.settings/org.moreunit.prefs b/build-system/builder/.settings/org.moreunit.prefs
new file mode 100644
index 0000000..c0ed4c1
--- /dev/null
+++ b/build-system/builder/.settings/org.moreunit.prefs
@@ -0,0 +1,5 @@
+#Thu Jan 05 10:46:32 PST 2012
+eclipse.preferences.version=1
+org.moreunit.prefixes=
+org.moreunit.unitsourcefolder=common\:src\:common-tests\:src
+org.moreunit.useprojectsettings=true
diff --git a/build-system/builder/MODULE_LICENSE_APACHE2 b/build-system/builder/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/build-system/builder/MODULE_LICENSE_APACHE2
diff --git a/build-system/builder/NOTICE b/build-system/builder/NOTICE
new file mode 100644
index 0000000..33ff961
--- /dev/null
+++ b/build-system/builder/NOTICE
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2005-2012, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/build-system/builder/build.gradle b/build-system/builder/build.gradle
new file mode 100644
index 0000000..42aa673
--- /dev/null
+++ b/build-system/builder/build.gradle
@@ -0,0 +1,37 @@
+apply plugin: 'java'
+apply plugin: 'clone-artifacts'
+
+evaluationDependsOn(':builder-model')
+evaluationDependsOn(':builder-test-api')
+
+dependencies {
+ compile project(':builder-model')
+ compile project(':builder-test-api')
+
+ compile project(':sdklib')
+ compile project(':sdk-common')
+ compile project(':common')
+ compile project(':manifest-merger')
+ compile project(':ddmlib')
+
+ compile 'com.squareup:javawriter:2.2.1'
+ compile 'org.bouncycastle:bcpkix-jdk15on:1.48'
+
+ testCompile 'junit:junit:3.8.1'
+ testCompile project(':testutils')
+}
+
+group = 'com.android.tools.build'
+archivesBaseName = 'builder'
+project.ext.pomName = 'Android Builder library'
+project.ext.pomDesc = 'Library to build Android applications.'
+
+apply from: '../../buildVersion.gradle'
+apply from: '../../publish.gradle'
+apply from: '../../javadoc.gradle'
+
+jar.manifest.attributes("Builder-Version": version)
+publishLocal.dependsOn ':builder-model:publishLocal', ':builder-test-api:publishLocal'
+
+apply plugin: 'distrib'
+shipping.isShipping = false
diff --git a/build-system/builder/builder.iml b/build-system/builder/builder.iml
new file mode 100644
index 0000000..a9fbd0b
--- /dev/null
+++ b/build-system/builder/builder.iml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module relativePaths="true" type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/main/resources" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+ <sourceFolder url="file://$MODULE_DIR$/src/test/resources" isTestSource="true" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module" module-name="builder-test-api" exported="" />
+ <orderEntry type="module" module-name="builder-model" exported="" />
+ <orderEntry type="library" scope="TEST" name="JUnit3" level="project" />
+ <orderEntry type="module" module-name="testutils" scope="TEST" />
+ <orderEntry type="module" module-name="ddmlib" exported="" />
+ <orderEntry type="module" module-name="manifest-merger" exported="" />
+ <orderEntry type="module" module-name="sdk-common" exported="" />
+ <orderEntry type="library" exported="" name="javawriter" level="project" />
+ <orderEntry type="library" exported="" name="bouncy-castle" level="project" />
+ </component>
+</module>
+
diff --git a/build-system/builder/src/main/java/com/android/builder/AndroidBuilder.java b/build-system/builder/src/main/java/com/android/builder/AndroidBuilder.java
new file mode 100644
index 0000000..0e12242
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/AndroidBuilder.java
@@ -0,0 +1,1191 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.builder.compiling.DependencyFileProcessor;
+import com.android.builder.dependency.ManifestDependency;
+import com.android.builder.dependency.SymbolFileProvider;
+import com.android.builder.internal.ClassFieldImpl;
+import com.android.builder.internal.SymbolLoader;
+import com.android.builder.internal.SymbolWriter;
+import com.android.builder.internal.TestManifestGenerator;
+import com.android.builder.internal.compiler.AidlProcessor;
+import com.android.builder.internal.compiler.LeafFolderGatherer;
+import com.android.builder.internal.compiler.RenderScriptProcessor;
+import com.android.builder.internal.compiler.SourceSearcher;
+import com.android.builder.internal.packaging.JavaResourceProcessor;
+import com.android.builder.internal.packaging.Packager;
+import com.android.builder.model.AaptOptions;
+import com.android.builder.model.ClassField;
+import com.android.builder.model.ProductFlavor;
+import com.android.builder.model.SigningConfig;
+import com.android.builder.packaging.DuplicateFileException;
+import com.android.builder.packaging.PackagerException;
+import com.android.builder.packaging.SealedPackageException;
+import com.android.builder.packaging.SigningException;
+import com.android.builder.signing.CertificateInfo;
+import com.android.builder.signing.KeystoreHelper;
+import com.android.builder.signing.KeytoolException;
+import com.android.ide.common.internal.AaptRunner;
+import com.android.ide.common.internal.CommandLineRunner;
+import com.android.ide.common.internal.LoggedErrorException;
+import com.android.manifmerger.ManifestMerger;
+import com.android.manifmerger.MergerLog;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.repository.FullRevision;
+import com.android.utils.ILogger;
+import com.android.utils.SdkUtils;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * This is the main builder class. It is given all the data to process the build (such as
+ * {@link DefaultProductFlavor}s, {@link DefaultBuildType} and dependencies) and use them when doing specific
+ * build steps.
+ *
+ * To use:
+ * create a builder with {@link #AndroidBuilder(SdkParser, String, ILogger, boolean)}
+ *
+ * then build steps can be done with
+ * {@link #processManifest(java.io.File, java.util.List, java.util.List, String, int, String, int, int, String)}
+ * {@link #processTestManifest(String, int, int, String, String, Boolean, Boolean, java.util.List, String)}
+ * {@link #processResources(java.io.File, java.io.File, java.io.File, java.util.List, String, String, String, String, String, com.android.builder.VariantConfiguration.Type, boolean, com.android.builder.model.AaptOptions)}
+ * {@link #compileAllAidlFiles(java.util.List, java.io.File, java.util.List, com.android.builder.compiling.DependencyFileProcessor)}
+ * {@link #convertByteCode(Iterable, Iterable, File, DexOptions, boolean)}
+ * {@link #packageApk(String, String, java.util.List, String, java.util.Collection, java.util.Set, boolean, com.android.builder.model.SigningConfig, String)}
+ *
+ * Java compilation is not handled but the builder provides the bootclasspath with
+ * {@link #getBootClasspath(SdkParser)}.
+ */
+public class AndroidBuilder {
+
+ private static final FullRevision MIN_BUILD_TOOLS_REV = new FullRevision(16, 0, 0);
+
+ private static final DependencyFileProcessor sNoOpDependencyFileProcessor = new DependencyFileProcessor() {
+ @Override
+ public boolean processFile(@NonNull File dependencyFile) {
+ return true;
+ }
+ };
+
+ private final SdkParser mSdkParser;
+ private final ILogger mLogger;
+ private final CommandLineRunner mCmdLineRunner;
+ private final boolean mVerboseExec;
+ private boolean mLibrary;
+
+ @NonNull
+ private final IAndroidTarget mTarget;
+ @NonNull
+ private final BuildToolInfo mBuildTools;
+ private String mCreatedBy;
+
+ /**
+ * Creates an AndroidBuilder
+ * <p/>
+ * This receives an {@link SdkParser} to provide the build with information about the SDK, as
+ * well as an {@link ILogger} to display output.
+ * <p/>
+ * <var>verboseExec</var> is needed on top of the ILogger due to remote exec tools not being
+ * able to output info and verbose messages separately.
+ *
+ * @param sdkParser the SdkParser
+ * @param logger the Logger
+ * @param verboseExec whether external tools are launched in verbose mode
+ */
+ public AndroidBuilder(
+ @NonNull SdkParser sdkParser,
+ @Nullable String createdBy,
+ @NonNull ILogger logger,
+ boolean verboseExec) {
+ mCreatedBy = createdBy;
+ mSdkParser = checkNotNull(sdkParser);
+ mLogger = checkNotNull(logger);
+ mVerboseExec = verboseExec;
+ mCmdLineRunner = new CommandLineRunner(mLogger);
+
+ BuildToolInfo buildToolInfo = mSdkParser.getBuildTools();
+ FullRevision buildToolsRevision = buildToolInfo.getRevision();
+
+ if (buildToolsRevision.compareTo(MIN_BUILD_TOOLS_REV) < 0) {
+ throw new IllegalArgumentException(String.format(
+ "The SDK Build Tools revision (%1$s) is too low. Minimum required is %2$s",
+ buildToolsRevision, MIN_BUILD_TOOLS_REV));
+ }
+
+ mTarget = mSdkParser.getTarget();
+ mBuildTools = mSdkParser.getBuildTools();
+ }
+
+ @VisibleForTesting
+ AndroidBuilder(
+ @NonNull SdkParser sdkParser,
+ @NonNull CommandLineRunner cmdLineRunner,
+ @NonNull ILogger logger,
+ boolean verboseExec) {
+ mSdkParser = checkNotNull(sdkParser);
+ mCmdLineRunner = checkNotNull(cmdLineRunner);
+ mLogger = checkNotNull(logger);
+ mVerboseExec = verboseExec;
+
+ mTarget = mSdkParser.getTarget();
+ mBuildTools = mSdkParser.getBuildTools();
+ }
+
+ /**
+ * Helper method to get the boot classpath to be used during compilation.
+ */
+ @NonNull
+ public static List<String> getBootClasspath(@NonNull SdkParser sdkParser) {
+
+ List<String> classpath = Lists.newArrayList();
+
+ IAndroidTarget target = sdkParser.getTarget();
+
+ classpath.addAll(target.getBootClasspath());
+
+ // add optional libraries if any
+ IAndroidTarget.IOptionalLibrary[] libs = target.getOptionalLibraries();
+ if (libs != null) {
+ for (IAndroidTarget.IOptionalLibrary lib : libs) {
+ classpath.add(lib.getJarPath());
+ }
+ }
+
+ // add annotations.jar if needed.
+ if (target.getVersion().getApiLevel() <= 15) {
+ classpath.add(sdkParser.getAnnotationsJar());
+ }
+
+ return classpath;
+ }
+
+ /** Sets whether this builder is currently used to build a library. Defaults to false. */
+ public AndroidBuilder setBuildingLibrary(boolean library) {
+ mLibrary = library;
+ return this;
+ }
+
+ /** Sets whether this builder is currently used to build a library */
+ public boolean isBuildingLibrary() {
+ return mLibrary;
+ }
+
+ /**
+ * Returns the compile classpath for this config. If the config tests a library, this
+ * will include the classpath of the tested config
+ *
+ * @return a non null, but possibly empty set.
+ */
+ @NonNull
+ public Set<File> getCompileClasspath(@NonNull VariantConfiguration variantConfiguration) {
+ Set<File> compileClasspath = variantConfiguration.getCompileClasspath();
+
+ ProductFlavor mergedFlavor = variantConfiguration.getMergedFlavor();
+
+ if (mergedFlavor.getRenderscriptSupportMode()) {
+ File renderScriptSupportJar = RenderScriptProcessor.getSupportJar(
+ mBuildTools.getLocation().getAbsolutePath());
+
+ Set<File> fullJars = Sets.newHashSetWithExpectedSize(compileClasspath.size() + 1);
+ fullJars.addAll(compileClasspath);
+ fullJars.add(renderScriptSupportJar);
+ compileClasspath = fullJars;
+ }
+
+ return compileClasspath;
+ }
+
+ /**
+ * Returns the list of packaged jars for this config. If the config tests a library, this
+ * will include the jars of the tested config
+ *
+ * @return a non null, but possibly empty list.
+ */
+ @NonNull
+ public List<File> getPackagedJars(@NonNull VariantConfiguration variantConfiguration) {
+ List<File> packagedJars = variantConfiguration.getPackagedJars();
+
+ ProductFlavor mergedFlavor = variantConfiguration.getMergedFlavor();
+
+ if (mergedFlavor.getRenderscriptSupportMode()) {
+ File renderScriptSupportJar = RenderScriptProcessor.getSupportJar(
+ mBuildTools.getLocation().getAbsolutePath());
+
+ List<File> fullJars = Lists.newArrayListWithCapacity(packagedJars.size() + 1);
+ fullJars.addAll(packagedJars);
+ fullJars.add(renderScriptSupportJar);
+ packagedJars = fullJars;
+ }
+
+ return packagedJars;
+ }
+
+ @NonNull
+ public File getSupportNativeLibFolder() {
+ return RenderScriptProcessor.getSupportNativeLibFolder(
+ mBuildTools.getLocation().getAbsolutePath());
+ }
+
+ /**
+ * Returns an {@link AaptRunner} able to run aapt commands.
+ * @return an AaptRunner object
+ */
+ @NonNull
+ public AaptRunner getAaptRunner() {
+ return new AaptRunner(
+ mBuildTools.getPath(BuildToolInfo.PathId.AAPT),
+ mCmdLineRunner);
+ }
+
+ @NonNull
+ public CommandLineRunner getCommandLineRunner() {
+ return mCmdLineRunner;
+ }
+
+ @NonNull
+ public static ClassField createClassField(@NonNull String type, @NonNull String name, @NonNull String value) {
+ return new ClassFieldImpl(type, name, value);
+ }
+
+ /**
+ * Merges all the manifests into a single manifest
+ *
+ * @param mainManifest The main manifest of the application.
+ * @param manifestOverlays manifest overlays coming from flavors and build types
+ * @param libraries the library dependency graph
+ * @param packageOverride a package name override. Can be null.
+ * @param versionCode a version code to inject in the manifest or -1 to do nothing.
+ * @param versionName a version name to inject in the manifest or null to do nothing.
+ * @param minSdkVersion a minSdkVersion to inject in the manifest or -1 to do nothing.
+ * @param targetSdkVersion a targetSdkVersion to inject in the manifest or -1 to do nothing.
+ * @param outManifestLocation the output location for the merged manifest
+ *
+ * @see com.android.builder.VariantConfiguration#getMainManifest()
+ * @see com.android.builder.VariantConfiguration#getManifestOverlays()
+ * @see com.android.builder.VariantConfiguration#getDirectLibraries()
+ * @see com.android.builder.VariantConfiguration#getMergedFlavor()
+ * @see DefaultProductFlavor#getVersionCode()
+ * @see DefaultProductFlavor#getVersionName()
+ * @see DefaultProductFlavor#getMinSdkVersion()
+ * @see DefaultProductFlavor#getTargetSdkVersion()
+ */
+ public void processManifest(
+ @NonNull File mainManifest,
+ @NonNull List<File> manifestOverlays,
+ @NonNull List<? extends ManifestDependency> libraries,
+ String packageOverride,
+ int versionCode,
+ String versionName,
+ int minSdkVersion,
+ int targetSdkVersion,
+ @NonNull String outManifestLocation) {
+ checkNotNull(mainManifest, "mainManifest cannot be null.");
+ checkNotNull(manifestOverlays, "manifestOverlays cannot be null.");
+ checkNotNull(libraries, "libraries cannot be null.");
+ checkNotNull(outManifestLocation, "outManifestLocation cannot be null.");
+
+ try {
+ Map<String, String> attributeInjection = getAttributeInjectionMap(
+ versionCode, versionName, minSdkVersion, targetSdkVersion);
+
+ if (manifestOverlays.isEmpty() && libraries.isEmpty()) {
+ // if no manifest to merge, just copy to location, unless we have to inject
+ // attributes
+ if (attributeInjection.isEmpty() && packageOverride == null) {
+ SdkUtils.copyXmlWithSourceReference(mainManifest,
+ new File(outManifestLocation));
+ } else {
+ ManifestMerger merger = new ManifestMerger(MergerLog.wrapSdkLog(mLogger), null);
+ merger.setInsertSourceMarkers(isInsertSourceMarkers());
+ doMerge(merger, new File(outManifestLocation), mainManifest,
+ attributeInjection, packageOverride);
+ }
+ } else {
+ File outManifest = new File(outManifestLocation);
+
+ // first merge the app manifest.
+ if (!manifestOverlays.isEmpty()) {
+ File mainManifestOut = outManifest;
+
+ // if there is also libraries, put this in a temp file.
+ if (!libraries.isEmpty()) {
+ // TODO find better way of storing intermediary file?
+ mainManifestOut = File.createTempFile("manifestMerge", ".xml");
+ mainManifestOut.deleteOnExit();
+ }
+
+ ManifestMerger merger = new ManifestMerger(MergerLog.wrapSdkLog(mLogger), null);
+ merger.setInsertSourceMarkers(isInsertSourceMarkers());
+ doMerge(merger, mainManifestOut, mainManifest, manifestOverlays,
+ attributeInjection, packageOverride);
+
+ // now the main manifest is the newly merged one
+ mainManifest = mainManifestOut;
+ // and the attributes have been inject, no need to do it below
+ attributeInjection = null;
+ }
+
+ if (!libraries.isEmpty()) {
+ // recursively merge all manifests starting with the leaves and up toward the
+ // root (the app)
+ mergeLibraryManifests(mainManifest, libraries,
+ new File(outManifestLocation), attributeInjection, packageOverride);
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Creates the manifest for a test variant
+ *
+ * @param testPackageName the package name of the test application
+ * @param minSdkVersion the minSdkVersion of the test application
+ * @param targetSdkVersion the targetSdkVersion of the test application
+ * @param testedPackageName the package name of the tested application
+ * @param instrumentationRunner the name of the instrumentation runner
+ * @param handleProfiling whether or not the Instrumentation object will turn profiling on and off
+ * @param functionalTest whether or not the Instrumentation class should run as a functional test
+ * @param libraries the library dependency graph
+ * @param outManifestLocation the output location for the merged manifest
+ *
+ * @see com.android.builder.VariantConfiguration#getPackageName()
+ * @see com.android.builder.VariantConfiguration#getTestedConfig()
+ * @see com.android.builder.VariantConfiguration#getMinSdkVersion()
+ * @see com.android.builder.VariantConfiguration#getTestedPackageName()
+ * @see com.android.builder.VariantConfiguration#getInstrumentationRunner()
+ * @see com.android.builder.VariantConfiguration#getHandleProfiling()
+ * @see com.android.builder.VariantConfiguration#getFunctionalTest()
+ * @see com.android.builder.VariantConfiguration#getDirectLibraries()
+ */
+ public void processTestManifest(
+ @NonNull String testPackageName,
+ int minSdkVersion,
+ int targetSdkVersion,
+ @NonNull String testedPackageName,
+ @NonNull String instrumentationRunner,
+ @NonNull Boolean handleProfiling,
+ @NonNull Boolean functionalTest,
+ @NonNull List<? extends ManifestDependency> libraries,
+ @NonNull String outManifestLocation) {
+ checkNotNull(testPackageName, "testPackageName cannot be null.");
+ checkNotNull(testedPackageName, "testedPackageName cannot be null.");
+ checkNotNull(instrumentationRunner, "instrumentationRunner cannot be null.");
+ checkNotNull(handleProfiling, "handleProfiling cannot be null.");
+ checkNotNull(functionalTest, "functionalTest cannot be null.");
+ checkNotNull(libraries, "libraries cannot be null.");
+ checkNotNull(outManifestLocation, "outManifestLocation cannot be null.");
+
+ if (!libraries.isEmpty()) {
+ try {
+ // create the test manifest, merge the libraries in it
+ File generatedTestManifest = File.createTempFile("manifestMerge", ".xml");
+
+ generateTestManifest(
+ testPackageName,
+ minSdkVersion,
+ targetSdkVersion,
+ testedPackageName,
+ instrumentationRunner,
+ handleProfiling,
+ functionalTest,
+ generatedTestManifest.getAbsolutePath());
+
+ mergeLibraryManifests(
+ generatedTestManifest,
+ libraries,
+ new File(outManifestLocation),
+ null, null);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ generateTestManifest(
+ testPackageName,
+ minSdkVersion,
+ targetSdkVersion,
+ testedPackageName,
+ instrumentationRunner,
+ handleProfiling,
+ functionalTest,
+ outManifestLocation);
+ }
+ }
+
+ private void generateTestManifest(
+ String testPackageName,
+ int minSdkVersion,
+ int targetSdkVersion,
+ String testedPackageName,
+ String instrumentationRunner,
+ Boolean handleProfiling,
+ Boolean functionalTest,
+ String outManifestLocation) {
+ TestManifestGenerator generator = new TestManifestGenerator(
+ outManifestLocation,
+ testPackageName,
+ minSdkVersion,
+ targetSdkVersion,
+ testedPackageName,
+ instrumentationRunner,
+ handleProfiling,
+ functionalTest);
+ try {
+ generator.generate();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @NonNull
+ private Map<String, String> getAttributeInjectionMap(
+ int versionCode,
+ @Nullable String versionName,
+ int minSdkVersion,
+ int targetSdkVersion) {
+
+ Map<String, String> attributeInjection = Maps.newHashMap();
+
+ if (versionCode != -1) {
+ attributeInjection.put(
+ "/manifest|http://schemas.android.com/apk/res/android versionCode",
+ Integer.toString(versionCode));
+ }
+
+ if (versionName != null) {
+ attributeInjection.put(
+ "/manifest|http://schemas.android.com/apk/res/android versionName",
+ versionName);
+ }
+
+ if (minSdkVersion != -1) {
+ attributeInjection.put(
+ "/manifest/uses-sdk|http://schemas.android.com/apk/res/android minSdkVersion",
+ Integer.toString(minSdkVersion));
+ }
+
+ if (targetSdkVersion != -1) {
+ attributeInjection.put(
+ "/manifest/uses-sdk|http://schemas.android.com/apk/res/android targetSdkVersion",
+ Integer.toString(targetSdkVersion));
+ }
+ return attributeInjection;
+ }
+
+ /**
+ * Merges library manifests into a main manifest.
+ * @param mainManifest the main manifest
+ * @param directLibraries the libraries to merge
+ * @param outManifest the output file
+ * @throws IOException
+ */
+ private void mergeLibraryManifests(
+ File mainManifest,
+ Iterable<? extends ManifestDependency> directLibraries,
+ File outManifest, Map<String, String> attributeInjection, String packageOverride)
+ throws IOException {
+
+ List<File> manifests = Lists.newArrayList();
+ for (ManifestDependency library : directLibraries) {
+ Collection<? extends ManifestDependency> subLibraries = library.getManifestDependencies();
+ if (subLibraries.isEmpty()) {
+ manifests.add(library.getManifest());
+ } else {
+ File mergeLibManifest = File.createTempFile("manifestMerge", ".xml");
+ mergeLibManifest.deleteOnExit();
+
+ // don't insert the attribute injection into libraries
+ mergeLibraryManifests(
+ library.getManifest(), subLibraries, mergeLibManifest, null, null);
+
+ manifests.add(mergeLibManifest);
+ }
+ }
+
+ ManifestMerger merger = new ManifestMerger(MergerLog.wrapSdkLog(mLogger), null);
+ merger.setInsertSourceMarkers(isInsertSourceMarkers());
+ doMerge(merger, outManifest, mainManifest, manifests, attributeInjection, packageOverride);
+ }
+
+ /**
+ * Returns whether we should insert source markers in generated files (such as
+ * XML resources and merged manifest files)
+ *
+ * @return true to generate source comments
+ */
+ public boolean isInsertSourceMarkers() {
+ // In release library builds (generating AAR's) we don't want source comments.
+ // In other scenarios (e.g. during development) we do.
+
+ // TODO: Find out whether we're building in a release build type
+ boolean isRelease = false;
+
+ //noinspection ConstantConditions
+ return !(mLibrary && isRelease);
+ }
+
+ private void doMerge(ManifestMerger merger, File output, File input,
+ Map<String, String> injectionMap, String packageOverride) {
+ List<File> list = Collections.emptyList();
+ doMerge(merger, output, input, list, injectionMap, packageOverride);
+ }
+
+ private void doMerge(ManifestMerger merger, File output, File input, List<File> subManifests,
+ Map<String, String> injectionMap, String packageOverride) {
+ if (!merger.process(output, input,
+ subManifests.toArray(new File[subManifests.size()]),
+ injectionMap, packageOverride)) {
+ throw new RuntimeException("Manifest merging failed. See console for more info.");
+ }
+ }
+
+ /**
+ * Process the resources and generate R.java and/or the packaged resources.
+ *
+ * @param manifestFile the location of the manifest file
+ * @param resFolder the merged res folder
+ * @param assetsDir the merged asset folder
+ * @param libraries the flat list of libraries
+ * @param packageForR Package override to generate the R class in a different package.
+ * @param sourceOutputDir optional source folder to generate R.java
+ * @param resPackageOutput optional filepath for packaged resources
+ * @param proguardOutput optional filepath for proguard file to generate
+ * @param type the type of the variant being built
+ * @param debuggable whether the app is debuggable
+ * @param options the {@link com.android.builder.model.AaptOptions}
+ *
+ *
+ * @throws IOException
+ * @throws InterruptedException
+ * @throws LoggedErrorException
+ */
+ public void processResources(
+ @NonNull File manifestFile,
+ @NonNull File resFolder,
+ @Nullable File assetsDir,
+ @NonNull List<? extends SymbolFileProvider> libraries,
+ @Nullable String packageForR,
+ @Nullable String sourceOutputDir,
+ @Nullable String symbolOutputDir,
+ @Nullable String resPackageOutput,
+ @Nullable String proguardOutput,
+ VariantConfiguration.Type type,
+ boolean debuggable,
+ @NonNull AaptOptions options,
+ @NonNull Collection<String> resourceConfigs)
+ throws IOException, InterruptedException, LoggedErrorException {
+
+ checkNotNull(manifestFile, "manifestFile cannot be null.");
+ checkNotNull(resFolder, "resFolder cannot be null.");
+ checkNotNull(libraries, "libraries cannot be null.");
+ checkNotNull(options, "options cannot be null.");
+ // if both output types are empty, then there's nothing to do and this is an error
+ checkArgument(sourceOutputDir != null || resPackageOutput != null,
+ "No output provided for aapt task");
+
+ // launch aapt: create the command line
+ ArrayList<String> command = Lists.newArrayList();
+
+ String aapt = mBuildTools.getPath(BuildToolInfo.PathId.AAPT);
+ if (aapt == null || !new File(aapt).isFile()) {
+ throw new IllegalStateException("aapt is missing");
+ }
+
+ command.add(aapt);
+ command.add("package");
+
+ if (mVerboseExec) {
+ command.add("-v");
+ }
+
+ command.add("-f");
+
+ command.add("--no-crunch");
+
+ // inputs
+ command.add("-I");
+ command.add(mTarget.getPath(IAndroidTarget.ANDROID_JAR));
+
+ command.add("-M");
+ command.add(manifestFile.getAbsolutePath());
+
+ if (resFolder.isDirectory()) {
+ command.add("-S");
+ command.add(resFolder.getAbsolutePath());
+ }
+
+ if (assetsDir != null && assetsDir.isDirectory()) {
+ command.add("-A");
+ command.add(assetsDir.getAbsolutePath());
+ }
+
+ // outputs
+
+ if (sourceOutputDir != null) {
+ command.add("-m");
+ command.add("-J");
+ command.add(sourceOutputDir);
+ }
+
+ if (resPackageOutput != null) {
+ command.add("-F");
+ command.add(resPackageOutput);
+ }
+
+ if (proguardOutput != null) {
+ command.add("-G");
+ command.add(proguardOutput);
+ }
+
+ // options controlled by build variants
+
+ if (debuggable) {
+ command.add("--debug-mode");
+ }
+
+ if (type == VariantConfiguration.Type.DEFAULT) {
+ if (packageForR != null) {
+ command.add("--custom-package");
+ command.add(packageForR);
+ mLogger.verbose("Custom package for R class: '%s'", packageForR);
+ }
+ }
+
+ // library specific options
+ if (type == VariantConfiguration.Type.LIBRARY) {
+ command.add("--non-constant-id");
+ }
+
+ // AAPT options
+ String ignoreAssets = options.getIgnoreAssets();
+ if (ignoreAssets != null) {
+ command.add("--ignore-assets");
+ command.add(ignoreAssets);
+ }
+
+ Collection<String> noCompressList = options.getNoCompress();
+ if (noCompressList != null) {
+ for (String noCompress : noCompressList) {
+ command.add("-0");
+ command.add(noCompress);
+ }
+ }
+
+ if (!resourceConfigs.isEmpty()) {
+ command.add("-c");
+
+ Joiner joiner = Joiner.on(',');
+ command.add(joiner.join(resourceConfigs));
+ }
+
+ if (symbolOutputDir != null &&
+ (type == VariantConfiguration.Type.LIBRARY || !libraries.isEmpty())) {
+ command.add("--output-text-symbols");
+ command.add(symbolOutputDir);
+ }
+
+ mCmdLineRunner.runCmdLine(command, null);
+
+ // now if the project has libraries, R needs to be created for each libraries,
+ // but only if the current project is not a library.
+ if (type != VariantConfiguration.Type.LIBRARY && !libraries.isEmpty()) {
+ SymbolLoader fullSymbolValues = null;
+
+ // First pass processing the libraries, collecting them by packageName,
+ // and ignoring the ones that have the same package name as the application
+ // (since that R class was already created).
+ String appPackageName = packageForR;
+ if (appPackageName == null) {
+ appPackageName = VariantConfiguration.getManifestPackage(manifestFile);
+ }
+
+ // list of all the symbol loaders per package names.
+ Multimap<String, SymbolLoader> libMap = ArrayListMultimap.create();
+
+ for (SymbolFileProvider lib : libraries) {
+ File rFile = lib.getSymbolFile();
+ // if the library has no resource, this file won't exist.
+ if (rFile.isFile()) {
+
+ String packageName = VariantConfiguration.getManifestPackage(lib.getManifest());
+ if (appPackageName.equals(packageName)) {
+ // ignore libraries that have the same package name as the app
+ continue;
+ }
+
+ // load the full values if that's not already been done.
+ // Doing it lazily allow us to support the case where there's no
+ // resources anywhere.
+ if (fullSymbolValues == null) {
+ fullSymbolValues = new SymbolLoader(new File(symbolOutputDir, "R.txt"),
+ mLogger);
+ fullSymbolValues.load();
+ }
+
+ SymbolLoader libSymbols = new SymbolLoader(rFile, mLogger);
+ libSymbols.load();
+
+
+ // store these symbols by associating them with the package name.
+ libMap.put(packageName, libSymbols);
+ }
+ }
+
+ // now loop on all the package name, merge all the symbols to write, and write them
+ for (String packageName : libMap.keySet()) {
+ Collection<SymbolLoader> symbols = libMap.get(packageName);
+
+ SymbolWriter writer = new SymbolWriter(sourceOutputDir, packageName,
+ fullSymbolValues);
+ for (SymbolLoader symbolLoader : symbols) {
+ writer.addSymbolsToWrite(symbolLoader);
+ }
+ writer.write();
+ }
+ }
+ }
+
+ /**
+ * Compiles all the aidl files found in the given source folders.
+ *
+ * @param sourceFolders all the source folders to find files to compile
+ * @param sourceOutputDir the output dir in which to generate the source code
+ * @param importFolders import folders
+ * @param dependencyFileProcessor the dependencyFileProcessor to record the dependencies
+ * of the compilation.
+ * @throws IOException
+ * @throws InterruptedException
+ * @throws LoggedErrorException
+ */
+ public void compileAllAidlFiles(@NonNull List<File> sourceFolders,
+ @NonNull File sourceOutputDir,
+ @NonNull List<File> importFolders,
+ @Nullable DependencyFileProcessor dependencyFileProcessor)
+ throws IOException, InterruptedException, LoggedErrorException {
+ checkNotNull(sourceFolders, "sourceFolders cannot be null.");
+ checkNotNull(sourceOutputDir, "sourceOutputDir cannot be null.");
+ checkNotNull(importFolders, "importFolders cannot be null.");
+
+ String aidl = mBuildTools.getPath(BuildToolInfo.PathId.AIDL);
+ if (aidl == null || !new File(aidl).isFile()) {
+ throw new IllegalStateException("aidl is missing");
+ }
+
+ List<File> fullImportList = Lists.newArrayListWithCapacity(
+ sourceFolders.size() + importFolders.size());
+ fullImportList.addAll(sourceFolders);
+ fullImportList.addAll(importFolders);
+
+ AidlProcessor processor = new AidlProcessor(
+ aidl,
+ mTarget.getPath(IAndroidTarget.ANDROID_AIDL),
+ fullImportList,
+ sourceOutputDir,
+ dependencyFileProcessor != null ?
+ dependencyFileProcessor : sNoOpDependencyFileProcessor,
+ mCmdLineRunner);
+
+ SourceSearcher searcher = new SourceSearcher(sourceFolders, "aidl");
+ searcher.setUseExecutor(true);
+ searcher.search(processor);
+ }
+
+ /**
+ * Compiles the given aidl file.
+ *
+ * @param aidlFile the AIDL file to compile
+ * @param sourceOutputDir the output dir in which to generate the source code
+ * @param importFolders all the import folders, including the source folders.
+ * @param dependencyFileProcessor the dependencyFileProcessor to record the dependencies
+ * of the compilation.
+ * @throws IOException
+ * @throws InterruptedException
+ * @throws LoggedErrorException
+ */
+ public void compileAidlFile(@NonNull File aidlFile,
+ @NonNull File sourceOutputDir,
+ @NonNull List<File> importFolders,
+ @Nullable DependencyFileProcessor dependencyFileProcessor)
+ throws IOException, InterruptedException, LoggedErrorException {
+ checkNotNull(aidlFile, "aidlFile cannot be null.");
+ checkNotNull(sourceOutputDir, "sourceOutputDir cannot be null.");
+ checkNotNull(importFolders, "importFolders cannot be null.");
+
+ String aidl = mBuildTools.getPath(BuildToolInfo.PathId.AIDL);
+ if (aidl == null || !new File(aidl).isFile()) {
+ throw new IllegalStateException("aidl is missing");
+ }
+
+ AidlProcessor processor = new AidlProcessor(
+ aidl,
+ mTarget.getPath(IAndroidTarget.ANDROID_AIDL),
+ importFolders,
+ sourceOutputDir,
+ dependencyFileProcessor != null ?
+ dependencyFileProcessor : sNoOpDependencyFileProcessor,
+ mCmdLineRunner);
+
+ processor.processFile(aidlFile);
+ }
+
+ /**
+ * Compiles all the renderscript files found in the given source folders.
+ *
+ * Right now this is the only way to compile them as the renderscript compiler requires all
+ * renderscript files to be passed for all compilation.
+ *
+ * Therefore whenever a renderscript file or header changes, all must be recompiled.
+ *
+ * @param sourceFolders all the source folders to find files to compile
+ * @param importFolders all the import folders.
+ * @param sourceOutputDir the output dir in which to generate the source code
+ * @param resOutputDir the output dir in which to generate the bitcode file
+ * @param targetApi the target api
+ * @param debugBuild whether the build is debug
+ * @param optimLevel the optimization level
+ *
+ * @throws IOException
+ * @throws InterruptedException
+ * @throws LoggedErrorException
+ */
+ public void compileAllRenderscriptFiles(@NonNull List<File> sourceFolders,
+ @NonNull List<File> importFolders,
+ @NonNull File sourceOutputDir,
+ @NonNull File resOutputDir,
+ @NonNull File objOutputDir,
+ @NonNull File libOutputDir,
+ int targetApi,
+ boolean debugBuild,
+ int optimLevel,
+ boolean ndkMode,
+ boolean supportMode,
+ @Nullable Set<String> abiFilters)
+ throws IOException, InterruptedException, LoggedErrorException {
+ checkNotNull(sourceFolders, "sourceFolders cannot be null.");
+ checkNotNull(importFolders, "importFolders cannot be null.");
+ checkNotNull(sourceOutputDir, "sourceOutputDir cannot be null.");
+ checkNotNull(resOutputDir, "resOutputDir cannot be null.");
+
+ String renderscript = mBuildTools.getPath(BuildToolInfo.PathId.LLVM_RS_CC);
+ if (renderscript == null || !new File(renderscript).isFile()) {
+ throw new IllegalStateException("llvm-rs-cc is missing");
+ }
+
+ if (supportMode && mBuildTools.getRevision().compareTo(new FullRevision(18,1, 0)) == -1) {
+ throw new IllegalStateException(
+ "RenderScript Support Mode requires buildToolsVersion >= 18.1");
+ }
+
+ RenderScriptProcessor processor = new RenderScriptProcessor(
+ sourceFolders,
+ importFolders,
+ sourceOutputDir,
+ resOutputDir,
+ objOutputDir,
+ libOutputDir,
+ mBuildTools,
+ targetApi,
+ debugBuild,
+ optimLevel,
+ ndkMode,
+ supportMode,
+ abiFilters);
+ processor.build(mCmdLineRunner);
+ }
+
+ /**
+ * Computes and returns the leaf folders based on a given file extension.
+ *
+ * This looks through all the given root import folders, and recursively search for leaf
+ * folders containing files matching the given extensions. All the leaf folders are gathered
+ * and returned in the list.
+ *
+ * @param extension the extension to search for.
+ * @param importFolders an array of list of root folders.
+ * @return a list of leaf folder, never null.
+ */
+ @NonNull
+ public List<File> getLeafFolders(@NonNull String extension, List<File>... importFolders) {
+ List<File> results = Lists.newArrayList();
+
+ if (importFolders != null) {
+ for (List<File> folders : importFolders) {
+ SourceSearcher searcher = new SourceSearcher(folders, extension);
+ searcher.setUseExecutor(false);
+ LeafFolderGatherer processor = new LeafFolderGatherer();
+ try {
+ searcher.search(processor);
+ } catch (InterruptedException e) {
+ // wont happen as we're not using the executor, and our processor
+ // doesn't throw those.
+ } catch (IOException e) {
+ // wont happen as we're not using the executor, and our processor
+ // doesn't throw those.
+ } catch (LoggedErrorException e) {
+ // wont happen as we're not using the executor, and our processor
+ // doesn't throw those.
+ }
+
+ results.addAll(processor.getFolders());
+ }
+ }
+
+ return results;
+ }
+
+ /**
+ * Converts the bytecode to Dalvik format
+ * @param inputs the input files
+ * @param preDexedLibraries the list of pre-dexed libraries
+ * @param outDexFile the location of the output classes.dex file
+ * @param dexOptions dex options
+ * @param incremental true if it should attempt incremental dex if applicable
+ *
+ * @throws IOException
+ * @throws InterruptedException
+ * @throws LoggedErrorException
+ */
+ public void convertByteCode(
+ @NonNull Iterable<File> inputs,
+ @NonNull Iterable<File> preDexedLibraries,
+ @NonNull File outDexFile,
+ @NonNull DexOptions dexOptions,
+ boolean incremental) throws IOException, InterruptedException, LoggedErrorException {
+ checkNotNull(inputs, "inputs cannot be null.");
+ checkNotNull(preDexedLibraries, "preDexedLibraries cannot be null.");
+ checkNotNull(outDexFile, "outDexFile cannot be null.");
+ checkNotNull(dexOptions, "dexOptions cannot be null.");
+
+ // launch dx: create the command line
+ ArrayList<String> command = Lists.newArrayList();
+
+ String dx = mBuildTools.getPath(BuildToolInfo.PathId.DX);
+ if (dx == null || !new File(dx).isFile()) {
+ throw new IllegalStateException("dx is missing");
+ }
+
+ command.add(dx);
+
+ if (dexOptions.getJavaMaxHeapSize() != null) {
+ command.add("-JXmx" + dexOptions.getJavaMaxHeapSize());
+ }
+
+ command.add("--dex");
+
+ if (mVerboseExec) {
+ command.add("--verbose");
+ }
+
+ if (dexOptions.isCoreLibrary()) {
+ command.add("--core-library");
+ }
+
+ if (dexOptions.getJumboMode()) {
+ command.add("--force-jumbo");
+ }
+
+ if (incremental) {
+ command.add("--incremental");
+ command.add("--no-strict");
+ }
+
+ command.add("--output");
+ command.add(outDexFile.getAbsolutePath());
+
+ // clean up input list
+ List<String> inputList = Lists.newArrayList();
+ for (File f : inputs) {
+ if (f != null && f.exists()) {
+ inputList.add(f.getAbsolutePath());
+ }
+ }
+
+ if (!inputList.isEmpty()) {
+ mLogger.verbose("Dex inputs: " + inputList);
+ command.addAll(inputList);
+ }
+
+ // clean up and add library inputs.
+ List<String> libraryList = Lists.newArrayList();
+ for (File f : preDexedLibraries) {
+ if (f != null && f.exists()) {
+ libraryList.add(f.getAbsolutePath());
+ }
+ }
+
+ if (!libraryList.isEmpty()) {
+ mLogger.verbose("Dex pre-dexed inputs: " + libraryList);
+ command.addAll(libraryList);
+ }
+
+ mCmdLineRunner.runCmdLine(command, null);
+ }
+
+ /**
+ * Converts the bytecode to Dalvik format
+ * @param inputFile the input file
+ * @param outFile the location of the output classes.dex file
+ * @param dexOptions dex options
+ *
+ * @throws IOException
+ * @throws InterruptedException
+ * @throws LoggedErrorException
+ */
+ public void preDexLibrary(
+ @NonNull File inputFile,
+ @NonNull File outFile,
+ @NonNull DexOptions dexOptions)
+ throws IOException, InterruptedException, LoggedErrorException {
+ checkNotNull(inputFile, "inputFile cannot be null.");
+ checkNotNull(outFile, "outFile cannot be null.");
+ checkNotNull(dexOptions, "dexOptions cannot be null.");
+
+ // launch dx: create the command line
+ ArrayList<String> command = Lists.newArrayList();
+
+ String dx = mBuildTools.getPath(BuildToolInfo.PathId.DX);
+ if (dx == null || !new File(dx).isFile()) {
+ throw new IllegalStateException("dx is missing");
+ }
+
+ command.add(dx);
+
+ if (dexOptions.getJavaMaxHeapSize() != null) {
+ command.add("-JXmx" + dexOptions.getJavaMaxHeapSize());
+ }
+
+ command.add("--dex");
+
+ if (mVerboseExec) {
+ command.add("--verbose");
+ }
+
+ if (dexOptions.isCoreLibrary()) {
+ command.add("--core-library");
+ }
+
+ if (dexOptions.getJumboMode()) {
+ command.add("--force-jumbo");
+ }
+
+ command.add("--output");
+ command.add(outFile.getAbsolutePath());
+
+ command.add(inputFile.getAbsolutePath());
+
+ mCmdLineRunner.runCmdLine(command, null);
+ }
+
+ /**
+ * Packages the apk.
+ *
+ * @param androidResPkgLocation the location of the packaged resource file
+ * @param classesDexLocation the location of the classes.dex file
+ * @param packagedJars the jars that are packaged (libraries + jar dependencies)
+ * @param javaResourcesLocation the processed Java resource folder
+ * @param jniLibsFolders the folders containing jni shared libraries
+ * @param abiFilters optional ABI filter
+ * @param jniDebugBuild whether the app should include jni debug data
+ * @param signingConfig the signing configuration
+ * @param outApkLocation location of the APK.
+ * @throws DuplicateFileException
+ * @throws FileNotFoundException if the store location was not found
+ * @throws KeytoolException
+ * @throws PackagerException
+ * @throws SigningException when the key cannot be read from the keystore
+ *
+ * @see com.android.builder.VariantConfiguration#getPackagedJars()
+ */
+ public void packageApk(
+ @NonNull String androidResPkgLocation,
+ @NonNull String classesDexLocation,
+ @NonNull List<File> packagedJars,
+ @Nullable String javaResourcesLocation,
+ @Nullable Collection<File> jniLibsFolders,
+ @Nullable Set<String> abiFilters,
+ boolean jniDebugBuild,
+ @Nullable SigningConfig signingConfig,
+ @NonNull String outApkLocation) throws DuplicateFileException, FileNotFoundException,
+ KeytoolException, PackagerException, SigningException {
+ checkNotNull(androidResPkgLocation, "androidResPkgLocation cannot be null.");
+ checkNotNull(classesDexLocation, "classesDexLocation cannot be null.");
+ checkNotNull(outApkLocation, "outApkLocation cannot be null.");
+
+ CertificateInfo certificateInfo = null;
+ if (signingConfig != null && signingConfig.isSigningReady()) {
+ certificateInfo = KeystoreHelper.getCertificateInfo(signingConfig);
+ if (certificateInfo == null) {
+ throw new SigningException("Failed to read key from keystore");
+ }
+ }
+
+ try {
+ Packager packager = new Packager(
+ outApkLocation, androidResPkgLocation, classesDexLocation,
+ certificateInfo, mCreatedBy, mLogger);
+
+ packager.setJniDebugMode(jniDebugBuild);
+
+ // figure out conflicts!
+ JavaResourceProcessor resProcessor = new JavaResourceProcessor(packager);
+
+ if (javaResourcesLocation != null) {
+ resProcessor.addSourceFolder(javaResourcesLocation);
+ }
+
+ // add the resources from the jar files.
+ for (File jar : packagedJars) {
+ packager.addResourcesFromJar(jar);
+ }
+
+ // also add resources from library projects and jars
+ if (jniLibsFolders != null) {
+ for (File jniFolder : jniLibsFolders) {
+ packager.addNativeLibraries(jniFolder, abiFilters);
+ }
+ }
+
+ packager.sealApk();
+ } catch (SealedPackageException e) {
+ // shouldn't happen since we control the package from start to end.
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/BuilderConstants.java b/build-system/builder/src/main/java/com/android/builder/BuilderConstants.java
new file mode 100644
index 0000000..327d797
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/BuilderConstants.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder;
+
+/**
+ * Generic constants.
+ */
+public class BuilderConstants {
+
+ /**
+ * Extension for library packages.
+ */
+ public final static String EXT_LIB_ARCHIVE = "aar";
+
+ /**
+ * The name of the default config.
+ */
+ public static final String MAIN = "main";
+
+ public final static String DEBUG = "debug";
+ public final static String RELEASE = "release";
+
+ public final static String LINT = "lint";
+
+ public final static String FD_REPORTS = "reports";
+
+ public final static String CONNECTED = "connected";
+ public final static String DEVICE = "device";
+
+ public final static String INSTRUMENT_TEST = "instrumentTest";
+ public final static String FD_INSTRUMENT_TESTS = "instrumentTests";
+ public final static String FD_INSTRUMENT_RESULTS = INSTRUMENT_TEST + "-results";
+
+ public final static String UI_TEST = "uiTest";
+ public final static String FD_UI_TESTS = "uiTests";
+ public final static String FD_UI_RESULTS = UI_TEST + "-results";
+
+ public final static String FD_FLAVORS = "flavors";
+ public final static String FD_FLAVORS_ALL = "all";
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/DefaultBuildType.java b/build-system/builder/src/main/java/com/android/builder/DefaultBuildType.java
new file mode 100644
index 0000000..63abf00
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/DefaultBuildType.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.internal.BaseConfigImpl;
+import com.android.builder.model.BuildType;
+import com.android.builder.model.NdkConfig;
+import com.android.builder.model.SigningConfig;
+import com.google.common.base.Objects;
+
+public class DefaultBuildType extends BaseConfigImpl implements BuildType {
+ private static final long serialVersionUID = 1L;
+
+ private final String mName;
+ private boolean mDebuggable = false;
+ private boolean mJniDebugBuild = false;
+ private boolean mRenderscriptDebugBuild = false;
+ private int mRenderscriptOptimLevel = 3;
+ private String mPackageNameSuffix = null;
+ private String mVersionNameSuffix = null;
+ private boolean mRunProguard = false;
+ private SigningConfig mSigningConfig = null;
+
+ private boolean mZipAlign = true;
+
+ public DefaultBuildType(@NonNull String name) {
+ mName = name;
+ }
+
+ public DefaultBuildType initWith(DefaultBuildType that) {
+ _initWith(that);
+
+ setDebuggable(that.isDebuggable());
+ setJniDebugBuild(that.isJniDebugBuild());
+ setRenderscriptDebugBuild(that.isRenderscriptDebugBuild());
+ setRenderscriptOptimLevel(that.getRenderscriptOptimLevel());
+ setPackageNameSuffix(that.getPackageNameSuffix());
+ setVersionNameSuffix(that.getVersionNameSuffix());
+ setRunProguard(that.isRunProguard());
+ setZipAlign(that.isZipAlign());
+ setSigningConfig(that.getSigningConfig());
+
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ @NonNull
+ public BuildType setDebuggable(boolean debuggable) {
+ mDebuggable = debuggable;
+ return this;
+ }
+
+ @Override
+ public boolean isDebuggable() {
+ return mDebuggable;
+ }
+
+ @NonNull
+ public BuildType setJniDebugBuild(boolean jniDebugBuild) {
+ mJniDebugBuild = jniDebugBuild;
+ return this;
+ }
+
+ @Override
+ public boolean isJniDebugBuild() {
+ return mJniDebugBuild;
+ }
+
+ @Override
+ public boolean isRenderscriptDebugBuild() {
+ return mRenderscriptDebugBuild;
+ }
+
+ public void setRenderscriptDebugBuild(boolean renderscriptDebugBuild) {
+ mRenderscriptDebugBuild = renderscriptDebugBuild;
+ }
+
+ @Override
+ public int getRenderscriptOptimLevel() {
+ return mRenderscriptOptimLevel;
+ }
+
+ public void setRenderscriptOptimLevel(int renderscriptOptimLevel) {
+ mRenderscriptOptimLevel = renderscriptOptimLevel;
+ }
+
+ @NonNull
+ public BuildType setPackageNameSuffix(@Nullable String packageNameSuffix) {
+ mPackageNameSuffix = packageNameSuffix;
+ return this;
+ }
+
+ @Override
+ @Nullable
+ public String getPackageNameSuffix() {
+ return mPackageNameSuffix;
+ }
+
+ @NonNull
+ public BuildType setVersionNameSuffix(@Nullable String versionNameSuffix) {
+ mVersionNameSuffix = versionNameSuffix;
+ return this;
+ }
+
+ @Override
+ @Nullable
+ public String getVersionNameSuffix() {
+ return mVersionNameSuffix;
+ }
+
+ @NonNull
+ public BuildType setRunProguard(boolean runProguard) {
+ mRunProguard = runProguard;
+ return this;
+ }
+
+ @Override
+ public boolean isRunProguard() {
+ return mRunProguard;
+ }
+
+ @NonNull
+ public BuildType setZipAlign(boolean zipAlign) {
+ mZipAlign = zipAlign;
+ return this;
+ }
+
+ @Override
+ public boolean isZipAlign() {
+ return mZipAlign;
+ }
+
+ @NonNull
+ public BuildType setSigningConfig(@Nullable SigningConfig signingConfig) {
+ mSigningConfig = signingConfig;
+ return this;
+ }
+
+ @Nullable
+ public SigningConfig getSigningConfig() {
+ return mSigningConfig;
+ }
+
+ @Override
+ @Nullable
+ public NdkConfig getNdkConfig() {
+ return null;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
+
+ DefaultBuildType buildType = (DefaultBuildType) o;
+
+ if (!mName.equals(buildType.mName)) return false;
+ if (mDebuggable != buildType.mDebuggable) return false;
+ if (mJniDebugBuild != buildType.mJniDebugBuild) return false;
+ if (mRenderscriptDebugBuild != buildType.mRenderscriptDebugBuild) return false;
+ if (mRenderscriptOptimLevel != buildType.mRenderscriptOptimLevel) return false;
+ if (mRunProguard != buildType.mRunProguard) return false;
+ if (mZipAlign != buildType.mZipAlign) return false;
+ if (mPackageNameSuffix != null ?
+ !mPackageNameSuffix.equals(buildType.mPackageNameSuffix) :
+ buildType.mPackageNameSuffix != null)
+ return false;
+ if (mVersionNameSuffix != null ?
+ !mVersionNameSuffix.equals(buildType.mVersionNameSuffix) :
+ buildType.mVersionNameSuffix != null)
+ return false;
+ if (mSigningConfig != null ?
+ !mSigningConfig.equals(buildType.mSigningConfig) :
+ buildType.mSigningConfig != null)
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + (mName.hashCode());
+ result = 31 * result + (mDebuggable ? 1 : 0);
+ result = 31 * result + (mJniDebugBuild ? 1 : 0);
+ result = 31 * result + (mRenderscriptDebugBuild ? 1 : 0);
+ result = 31 * result + mRenderscriptOptimLevel;
+ result = 31 * result + (mPackageNameSuffix != null ? mPackageNameSuffix.hashCode() : 0);
+ result = 31 * result + (mVersionNameSuffix != null ? mVersionNameSuffix.hashCode() : 0);
+ result = 31 * result + (mRunProguard ? 1 : 0);
+ result = 31 * result + (mZipAlign ? 1 : 0);
+ result = 31 * result + (mSigningConfig != null ? mSigningConfig.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .add("name", mName)
+ .add("debuggable", mDebuggable)
+ .add("jniDebugBuild", mJniDebugBuild)
+ .add("renderscriptDebugBuild", mRenderscriptDebugBuild)
+ .add("renderscriptOptimLevel", mRenderscriptOptimLevel)
+ .add("packageNameSuffix", mPackageNameSuffix)
+ .add("versionNameSuffix", mVersionNameSuffix)
+ .add("runProguard", mRunProguard)
+ .add("zipAlign", mZipAlign)
+ .add("signingConfig", mSigningConfig)
+ .toString();
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/DefaultManifestParser.java b/build-system/builder/src/main/java/com/android/builder/DefaultManifestParser.java
new file mode 100644
index 0000000..e8d70d6
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/DefaultManifestParser.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.io.FileWrapper;
+import com.android.io.StreamException;
+import com.android.xml.AndroidManifest;
+import com.android.xml.AndroidXPathFactory;
+import org.xml.sax.InputSource;
+
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathExpressionException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+
+public class DefaultManifestParser implements ManifestParser {
+
+ @Nullable
+ @Override
+ public String getPackage(@NonNull File manifestFile) {
+ XPath xpath = AndroidXPathFactory.newXPath();
+
+ try {
+ return xpath.evaluate("/manifest/@package",
+ new InputSource(new FileInputStream(manifestFile)));
+ } catch (XPathExpressionException e) {
+ // won't happen.
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public String getVersionName(@NonNull File manifestFile) {
+ XPath xpath = AndroidXPathFactory.newXPath();
+
+ try {
+ return xpath.evaluate("/manifest/@android:versionName",
+ new InputSource(new FileInputStream(manifestFile)));
+ } catch (XPathExpressionException e) {
+ // won't happen.
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+
+ return null;
+ }
+
+ @Override
+ public int getVersionCode(@NonNull File manifestFile) {
+ XPath xpath = AndroidXPathFactory.newXPath();
+
+ try {
+ String value= xpath.evaluate("/manifest/@android:versionCode",
+ new InputSource(new FileInputStream(manifestFile)));
+ if (value != null) {
+ return Integer.parseInt(value);
+ }
+ } catch (XPathExpressionException e) {
+ // won't happen.
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException(e);
+ } catch (NumberFormatException e) {
+ // return -1 below.
+ }
+
+ return -1;
+ }
+
+ @Override
+ public int getMinSdkVersion(@NonNull File manifestFile) {
+ try {
+ Object value = AndroidManifest.getMinSdkVersion(new FileWrapper(manifestFile));
+ if (value instanceof Integer) {
+ return (Integer) value;
+ } else if (value instanceof String) {
+ // TODO: support codename
+ }
+
+ } catch (XPathExpressionException e) {
+ // won't happen.
+ } catch (StreamException e) {
+ throw new RuntimeException(e);
+ }
+
+ return 1;
+ }
+
+ @Override
+ public int getTargetSdkVersion(@NonNull File manifestFile) {
+ try {
+ Integer value = AndroidManifest.getTargetSdkVersion(new FileWrapper(manifestFile));
+ if (value != null) {
+ return value;
+ } else {
+ return -1;
+ }
+
+ } catch (XPathExpressionException e) {
+ // won't happen.
+ } catch (StreamException e) {
+ throw new RuntimeException(e);
+ }
+
+ return -1;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/DefaultProductFlavor.java b/build-system/builder/src/main/java/com/android/builder/DefaultProductFlavor.java
new file mode 100644
index 0000000..2dbdd81
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/DefaultProductFlavor.java
@@ -0,0 +1,411 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.internal.BaseConfigImpl;
+import com.android.builder.model.NdkConfig;
+import com.android.builder.model.ProductFlavor;
+import com.android.builder.model.SigningConfig;
+import com.google.common.base.Objects;
+import com.google.common.collect.Sets;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * The configuration of a product flavor.
+ *
+ * This is also used to describe the default configuration of all builds, even those that
+ * do not contain any flavors.
+ */
+public class DefaultProductFlavor extends BaseConfigImpl implements ProductFlavor {
+ private static final long serialVersionUID = 1L;
+
+ private final String mName;
+ private int mMinSdkVersion = -1;
+ private int mTargetSdkVersion = -1;
+ private int mRenderscriptTargetApi = -1;
+ private Boolean mRenderscriptSupportMode;
+ private Boolean mRenderscriptNdkMode;
+ private int mVersionCode = -1;
+ private String mVersionName = null;
+ private String mPackageName = null;
+ private String mTestPackageName = null;
+ private String mTestInstrumentationRunner = null;
+ private Boolean mTestHandleProfiling = null;
+ private Boolean mTestFunctionalTest = null;
+ private SigningConfig mSigningConfig = null;
+ private Set<String> mResourceConfiguration = null;
+
+ /**
+ * Creates a ProductFlavor with a given name.
+ *
+ * Names can be important when dealing with flavor groups.
+ * @param name the name of the flavor.
+ *
+ * @see BuilderConstants#MAIN
+ */
+ public DefaultProductFlavor(@NonNull String name) {
+ mName = name;
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Sets the package name.
+ *
+ * @param packageName the package name
+ * @return the flavor object
+ */
+ @NonNull
+ public ProductFlavor setPackageName(String packageName) {
+ mPackageName = packageName;
+ return this;
+ }
+
+ @Override
+ @Nullable
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Sets the version code. If the value is -1, it is considered not set.
+ *
+ * @param versionCode the version code
+ * @return the flavor object
+ */
+ @NonNull
+ public ProductFlavor setVersionCode(int versionCode) {
+ mVersionCode = versionCode;
+ return this;
+ }
+
+ @Override
+ public int getVersionCode() {
+ return mVersionCode;
+ }
+
+ /**
+ * Sets the version name.
+ *
+ * @param versionName the version name
+ * @return the flavor object
+ */
+ @NonNull
+ public ProductFlavor setVersionName(String versionName) {
+ mVersionName = versionName;
+ return this;
+ }
+
+ @Override
+ @Nullable
+ public String getVersionName() {
+ return mVersionName;
+ }
+
+ @NonNull
+ public ProductFlavor setMinSdkVersion(int minSdkVersion) {
+ mMinSdkVersion = minSdkVersion;
+ return this;
+ }
+
+ @Override
+ public int getMinSdkVersion() {
+ return mMinSdkVersion;
+ }
+
+ @NonNull
+ public ProductFlavor setTargetSdkVersion(int targetSdkVersion) {
+ mTargetSdkVersion = targetSdkVersion;
+ return this;
+ }
+
+ @Override
+ public int getTargetSdkVersion() {
+ return mTargetSdkVersion;
+ }
+
+ @Override
+ public int getRenderscriptTargetApi() {
+ return mRenderscriptTargetApi;
+ }
+
+ public void setRenderscriptTargetApi(int renderscriptTargetApi) {
+ mRenderscriptTargetApi = renderscriptTargetApi;
+ }
+
+ @Override
+ public boolean getRenderscriptSupportMode() {
+ // default is false
+ return mRenderscriptSupportMode != null && mRenderscriptSupportMode.booleanValue();
+ }
+
+ public void setRenderscriptSupportMode(boolean renderscriptSupportMode) {
+ mRenderscriptSupportMode = renderscriptSupportMode;
+ }
+
+ @Override
+ public boolean getRenderscriptNdkMode() {
+ // default is false
+ return mRenderscriptNdkMode != null && mRenderscriptNdkMode.booleanValue();
+ }
+
+ public void setRenderscriptNdkMode(boolean renderscriptNdkMode) {
+ mRenderscriptNdkMode = renderscriptNdkMode;
+ }
+
+ @NonNull
+ public ProductFlavor setTestPackageName(String testPackageName) {
+ mTestPackageName = testPackageName;
+ return this;
+ }
+
+ @Override
+ @Nullable
+ public String getTestPackageName() {
+ return mTestPackageName;
+ }
+
+ @NonNull
+ public ProductFlavor setTestInstrumentationRunner(String testInstrumentationRunner) {
+ mTestInstrumentationRunner = testInstrumentationRunner;
+ return this;
+ }
+
+ @Override
+ @Nullable
+ public String getTestInstrumentationRunner() {
+ return mTestInstrumentationRunner;
+ }
+
+ @Override
+ @Nullable
+ public Boolean getTestHandleProfiling() {
+ return mTestHandleProfiling;
+ }
+
+ @NonNull
+ public ProductFlavor setTestHandleProfiling(boolean handleProfiling) {
+ mTestHandleProfiling = handleProfiling;
+ return this;
+ }
+
+ @Override
+ @Nullable
+ public Boolean getTestFunctionalTest() {
+ return mTestFunctionalTest;
+ }
+
+ @NonNull
+ public ProductFlavor setTestFunctionalTest(boolean functionalTest) {
+ mTestFunctionalTest = functionalTest;
+ return this;
+ }
+
+ @Nullable
+ public SigningConfig getSigningConfig() {
+ return mSigningConfig;
+ }
+
+ @NonNull
+ public ProductFlavor setSigningConfig(SigningConfig signingConfig) {
+ mSigningConfig = signingConfig;
+ return this;
+ }
+
+ @Override
+ @Nullable
+ public NdkConfig getNdkConfig() {
+ return null;
+ }
+
+ public void addResourceConfiguration(@NonNull String configuration) {
+ if (mResourceConfiguration == null) {
+ mResourceConfiguration = Sets.newHashSet();
+ }
+
+ mResourceConfiguration.add(configuration);
+ }
+
+ public void addResourceConfigurations(@NonNull String... configurations) {
+ if (mResourceConfiguration == null) {
+ mResourceConfiguration = Sets.newHashSet();
+ }
+
+ mResourceConfiguration.addAll(Arrays.asList(configurations));
+ }
+
+ public void addResourceConfigurations(@NonNull Collection<String> configurations) {
+ if (mResourceConfiguration == null) {
+ mResourceConfiguration = Sets.newHashSet();
+ }
+
+ mResourceConfiguration.addAll(configurations);
+ }
+
+ @NonNull
+ @Override
+ public Collection<String> getResourceConfigurations() {
+ if (mResourceConfiguration == null) {
+ mResourceConfiguration = Sets.newHashSet();
+ }
+
+ return mResourceConfiguration;
+ }
+
+ /**
+ * Merges the flavor on top of a base platform and returns a new object with the result.
+ * @param base the flavor to merge on top of
+ * @return a new merged product flavor
+ */
+ @NonNull
+ DefaultProductFlavor mergeOver(@NonNull DefaultProductFlavor base) {
+ DefaultProductFlavor flavor = new DefaultProductFlavor("");
+
+ flavor.mMinSdkVersion = chooseInt(mMinSdkVersion, base.mMinSdkVersion);
+ flavor.mTargetSdkVersion = chooseInt(mTargetSdkVersion, base.mTargetSdkVersion);
+ flavor.mRenderscriptTargetApi = chooseInt(mRenderscriptTargetApi,
+ base.mRenderscriptTargetApi);
+ flavor.mRenderscriptSupportMode = chooseBoolean(mRenderscriptSupportMode,
+ base.mRenderscriptSupportMode);
+ flavor.mRenderscriptNdkMode = chooseBoolean(mRenderscriptNdkMode,
+ base.mRenderscriptNdkMode);
+
+ flavor.mVersionCode = chooseInt(mVersionCode, base.mVersionCode);
+ flavor.mVersionName = chooseString(mVersionName, base.mVersionName);
+
+ flavor.mPackageName = chooseString(mPackageName, base.mPackageName);
+
+ flavor.mTestPackageName = chooseString(mTestPackageName, base.mTestPackageName);
+ flavor.mTestInstrumentationRunner = chooseString(mTestInstrumentationRunner,
+ base.mTestInstrumentationRunner);
+
+ flavor.mTestHandleProfiling = chooseBoolean(mTestHandleProfiling,
+ base.mTestHandleProfiling);
+
+ flavor.mTestFunctionalTest = chooseBoolean(mTestFunctionalTest,
+ base.mTestFunctionalTest);
+
+ flavor.mSigningConfig =
+ mSigningConfig != null ? mSigningConfig : base.mSigningConfig;
+
+ flavor.addResourceConfigurations(base.getResourceConfigurations());
+
+ return flavor;
+ }
+
+ private int chooseInt(int overlay, int base) {
+ return overlay != -1 ? overlay : base;
+ }
+
+ @Nullable
+ private String chooseString(String overlay, String base) {
+ return overlay != null ? overlay : base;
+ }
+
+ private Boolean chooseBoolean(Boolean overlay, Boolean base) {
+ return overlay != null ? overlay : base;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
+
+ DefaultProductFlavor that = (DefaultProductFlavor) o;
+
+ if (mMinSdkVersion != that.mMinSdkVersion) return false;
+ if (mRenderscriptTargetApi != that.mRenderscriptTargetApi) return false;
+ if (mTargetSdkVersion != that.mTargetSdkVersion) return false;
+ if (mVersionCode != that.mVersionCode) return false;
+ if (!mName.equals(that.mName)) return false;
+ if (mPackageName != null ? !mPackageName.equals(that.mPackageName) : that.mPackageName != null)
+ return false;
+ if (mRenderscriptNdkMode != null ? !mRenderscriptNdkMode.equals(that.mRenderscriptNdkMode) : that.mRenderscriptNdkMode != null)
+ return false;
+ if (mRenderscriptSupportMode != null ? !mRenderscriptSupportMode.equals(that.mRenderscriptSupportMode) : that.mRenderscriptSupportMode != null)
+ return false;
+ if (mResourceConfiguration != null ? !mResourceConfiguration.equals(that.mResourceConfiguration) : that.mResourceConfiguration != null)
+ return false;
+ if (mSigningConfig != null ? !mSigningConfig.equals(that.mSigningConfig) : that.mSigningConfig != null)
+ return false;
+ if (mTestFunctionalTest != null ? !mTestFunctionalTest.equals(that.mTestFunctionalTest) : that.mTestFunctionalTest != null)
+ return false;
+ if (mTestHandleProfiling != null ? !mTestHandleProfiling.equals(that.mTestHandleProfiling) : that.mTestHandleProfiling != null)
+ return false;
+ if (mTestInstrumentationRunner != null ? !mTestInstrumentationRunner.equals(that.mTestInstrumentationRunner) : that.mTestInstrumentationRunner != null)
+ return false;
+ if (mTestPackageName != null ? !mTestPackageName.equals(that.mTestPackageName) : that.mTestPackageName != null)
+ return false;
+ if (mVersionName != null ? !mVersionName.equals(that.mVersionName) : that.mVersionName != null)
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + mName.hashCode();
+ result = 31 * result + mMinSdkVersion;
+ result = 31 * result + mTargetSdkVersion;
+ result = 31 * result + mRenderscriptTargetApi;
+ result = 31 * result + (mRenderscriptSupportMode != null ? mRenderscriptSupportMode.hashCode() : 0);
+ result = 31 * result + (mRenderscriptNdkMode != null ? mRenderscriptNdkMode.hashCode() : 0);
+ result = 31 * result + mVersionCode;
+ result = 31 * result + (mVersionName != null ? mVersionName.hashCode() : 0);
+ result = 31 * result + (mPackageName != null ? mPackageName.hashCode() : 0);
+ result = 31 * result + (mTestPackageName != null ? mTestPackageName.hashCode() : 0);
+ result = 31 * result + (mTestInstrumentationRunner != null ? mTestInstrumentationRunner.hashCode() : 0);
+ result = 31 * result + (mTestHandleProfiling != null ? mTestHandleProfiling.hashCode() : 0);
+ result = 31 * result + (mTestFunctionalTest != null ? mTestFunctionalTest.hashCode() : 0);
+ result = 31 * result + (mSigningConfig != null ? mSigningConfig.hashCode() : 0);
+ result = 31 * result + (mResourceConfiguration != null ? mResourceConfiguration.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .add("name", mName)
+ .add("minSdkVersion", mMinSdkVersion)
+ .add("targetSdkVersion", mTargetSdkVersion)
+ .add("renderscriptTargetApi", mRenderscriptTargetApi)
+ .add("renderscriptSupportMode", mRenderscriptSupportMode)
+ .add("renderscriptNdkMode", mRenderscriptNdkMode)
+ .add("versionCode", mVersionCode)
+ .add("versionName", mVersionName)
+ .add("packageName", mPackageName)
+ .add("testPackageName", mTestPackageName)
+ .add("testInstrumentationRunner", mTestInstrumentationRunner)
+ .add("testHandleProfiling", mTestHandleProfiling)
+ .add("testFunctionalTest", mTestFunctionalTest)
+ .add("signingConfig", mSigningConfig)
+ .add("resConfig", mResourceConfiguration)
+ .toString();
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/DefaultSdkParser.java b/build-system/builder/src/main/java/com/android/builder/DefaultSdkParser.java
new file mode 100644
index 0000000..6244fb9
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/DefaultSdkParser.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.SdkManager;
+import com.android.sdklib.repository.FullRevision;
+import com.android.sdklib.repository.PkgProps;
+import com.android.utils.ILogger;
+import com.google.common.base.Charsets;
+import com.google.common.collect.Lists;
+import com.google.common.io.Closeables;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.List;
+import java.util.Properties;
+
+import static com.android.SdkConstants.FD_PLATFORM_TOOLS;
+import static com.android.SdkConstants.FD_SUPPORT;
+import static com.android.SdkConstants.FD_TOOLS;
+import static com.android.SdkConstants.FN_ANNOTATIONS_JAR;
+import static com.android.SdkConstants.FN_SOURCE_PROP;
+
+/**
+ * Default implementation of {@link SdkParser} for a normal Android SDK distribution.
+ */
+public class DefaultSdkParser implements SdkParser {
+
+ private final String mSdkLocation;
+ private final File mNdkLocation;
+ private SdkManager mManager;
+
+ private IAndroidTarget mTarget;
+ private BuildToolInfo mBuildToolInfo;
+
+ private File mTools;
+ private File mPlatformTools;
+ private File mAdb;
+ private File mZipAlign;
+
+ public DefaultSdkParser(@NonNull String sdkLocation, @Nullable File ndkLocation) {
+ if (!sdkLocation.endsWith(File.separator)) {
+ mSdkLocation = sdkLocation + File.separator;
+ } else {
+ mSdkLocation = sdkLocation;
+ }
+ mNdkLocation = ndkLocation;
+ }
+
+ @Override
+ public void initParser(@NonNull String target,
+ @NonNull FullRevision buildToolRevision,
+ @NonNull ILogger logger) {
+ if (mManager == null) {
+ mManager = SdkManager.createManager(mSdkLocation, logger);
+ if (mManager == null) {
+ throw new IllegalStateException("failed to parse SDK!");
+ }
+
+ mTarget = mManager.getTargetFromHashString(target);
+ if (mTarget == null) {
+ throw new IllegalStateException("failed to find target " + target);
+ }
+
+ mBuildToolInfo = mManager.getBuildTool(buildToolRevision);
+ if (mBuildToolInfo == null) {
+ throw new IllegalStateException("failed to find Build Tools revision "
+ + buildToolRevision.toString());
+ }
+ }
+ }
+
+ @NonNull
+ @Override
+ public IAndroidTarget getTarget() {
+ if (mManager == null) {
+ throw new IllegalStateException("SdkParser was not initialized.");
+ }
+ return mTarget;
+ }
+
+ @NonNull
+ @Override
+ public BuildToolInfo getBuildTools() {
+ if (mManager == null) {
+ throw new IllegalStateException("SdkParser was not initialized.");
+ }
+ return mBuildToolInfo;
+ }
+
+ @Override
+ @NonNull
+ public String getAnnotationsJar() {
+ return mSdkLocation + FD_TOOLS +
+ '/' + FD_SUPPORT +
+ '/' + FN_ANNOTATIONS_JAR;
+ }
+
+ @Override
+ @Nullable
+ public FullRevision getPlatformToolsRevision() {
+ File platformTools = getPlatformToolsFolder();
+ if (!platformTools.isDirectory()) {
+ return null;
+ }
+
+ Reader reader = null;
+ try {
+ reader = new InputStreamReader(
+ new FileInputStream(new File(platformTools, FN_SOURCE_PROP)),
+ Charsets.UTF_8);
+ Properties props = new Properties();
+ props.load(reader);
+
+ String value = props.getProperty(PkgProps.PKG_REVISION);
+
+ return FullRevision.parseRevision(value);
+
+ } catch (FileNotFoundException ignore) {
+ // return null below.
+ } catch (IOException ignore) {
+ // return null below.
+ } catch (NumberFormatException ignore) {
+ // return null below.
+ } finally {
+ Closeables.closeQuietly(reader);
+ }
+
+ return null;
+ }
+
+ @Override
+ @NonNull
+ public File getZipAlign() {
+ if (mZipAlign == null) {
+ mZipAlign = new File(getToolsFolder(), SdkConstants.FN_ZIPALIGN);
+ }
+ return mZipAlign;
+ }
+
+ @Override
+ @NonNull
+ public File getAdb() {
+ if (mAdb == null) {
+ mAdb = new File(getPlatformToolsFolder(), SdkConstants.FN_ADB);
+ }
+ return mAdb;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getRepositories() {
+ List<File> repositories = Lists.newArrayList();
+
+ File androidRepo = new File(mSdkLocation + "/extras/android/m2repository");
+ if (androidRepo.isDirectory()) {
+ repositories.add(androidRepo);
+ }
+
+ File googleRepo = new File(mSdkLocation + "/extras/google/m2repository");
+ if (googleRepo.isDirectory()) {
+ repositories.add(googleRepo);
+ }
+
+ return repositories;
+ }
+
+ @NonNull
+ private File getPlatformToolsFolder() {
+ if (mPlatformTools == null) {
+ mPlatformTools = new File(mSdkLocation, FD_PLATFORM_TOOLS);
+ if (!mPlatformTools.isDirectory()) {
+ throw new IllegalStateException("Platform-tools folder missing: " +
+ mPlatformTools.getAbsolutePath());
+ }
+ }
+
+ return mPlatformTools;
+ }
+
+ @NonNull
+ private File getToolsFolder() {
+ if (mTools == null) {
+ mTools = new File(mSdkLocation, FD_TOOLS);
+ if (!mTools.isDirectory()) {
+ throw new IllegalStateException("Platform-tools folder missing: " +
+ mTools.getAbsolutePath());
+ }
+ }
+
+ return mTools;
+ }
+
+ @Nullable
+ @Override
+ public File getNdkLocation() {
+ return mNdkLocation;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/DexOptions.java b/build-system/builder/src/main/java/com/android/builder/DexOptions.java
new file mode 100644
index 0000000..1db13be
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/DexOptions.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder;
+
+public interface DexOptions {
+
+ boolean isCoreLibrary();
+ boolean getIncremental();
+ boolean getPreDexLibraries();
+ boolean getJumboMode();
+ String getJavaMaxHeapSize();
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/ManifestParser.java b/build-system/builder/src/main/java/com/android/builder/ManifestParser.java
new file mode 100644
index 0000000..3b8e4a3
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/ManifestParser.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.io.File;
+
+/**
+ * A Manifest parser
+ */
+public interface ManifestParser {
+
+ /**
+ * Returns the package name parsed from the given manifest file.
+ *
+ * @param manifestFile the manifest file to parse
+ *
+ * @return the package name or null if not found.
+ */
+ @Nullable
+ String getPackage(@NonNull File manifestFile);
+
+ /**
+ * Returns the minSdkVersion parsed from the given manifest file.
+ *
+ * @param manifestFile the manifest file to parse
+ *
+ * @return the minSdkVersion or 1 if not found.
+ */
+ int getMinSdkVersion(@NonNull File manifestFile);
+
+ /**
+ * Returns the targetSdkVersion parsed from the given manifest file.
+ *
+ * @param manifestFile the manifest file to parse
+ *
+ * @return the targetSdkVersion or -1 if not found.
+ */
+ int getTargetSdkVersion(@NonNull File manifestFile);
+
+ /**
+ * Returns the version name parsed from the given manifest file.
+ *
+ * @param manifestFile the manifest file to parse
+ *
+ * @return the version name or null if not found.
+ */
+ @Nullable
+ String getVersionName(@NonNull File manifestFile);
+
+ /**
+ * Returns the version code parsed from the given manifest file.
+ *
+ * @param manifestFile the manifest file to parse
+ *
+ * @return the version code or -1 if not found.
+ */
+ int getVersionCode(@NonNull File manifestFile);
+
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/PlatformSdkParser.java b/build-system/builder/src/main/java/com/android/builder/PlatformSdkParser.java
new file mode 100644
index 0000000..d0e1828
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/PlatformSdkParser.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.internal.FakeAndroidTarget;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.repository.FullRevision;
+import com.android.utils.ILogger;
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Implementation of {@link SdkParser} for the SDK prebuilds in the Android source tree.
+ */
+public class PlatformSdkParser implements SdkParser {
+ private final String mPlatformRootFolder;
+
+ private boolean mInitialized = false;
+ private IAndroidTarget mTarget;
+ private BuildToolInfo mBuildToolInfo;
+
+ private File mHostTools;
+ private File mZipAlign;
+ private File mAdb;
+
+ public PlatformSdkParser(@NonNull String sdkLocation) {
+ mPlatformRootFolder = sdkLocation;
+ }
+
+ @Override
+ public void initParser(@NonNull String target,
+ @NonNull FullRevision buildToolRevision,
+ @NonNull ILogger logger) {
+ if (!mInitialized) {
+ mTarget = new FakeAndroidTarget(mPlatformRootFolder, target);
+
+ mBuildToolInfo = new BuildToolInfo(buildToolRevision, new File(mPlatformRootFolder),
+ new File(getHostToolsFolder(), SdkConstants.FN_AAPT),
+ new File(getHostToolsFolder(), SdkConstants.FN_AIDL),
+ new File(mPlatformRootFolder, "prebuilts/sdk/tools/dx"),
+ new File(mPlatformRootFolder, "prebuilts/sdk/tools/lib/dx.jar"),
+ new File(getHostToolsFolder(), SdkConstants.FN_RENDERSCRIPT),
+ new File(mPlatformRootFolder, "prebuilts/sdk/renderscript/include"),
+ new File(mPlatformRootFolder, "prebuilts/sdk/renderscript/clang-include"),
+ new File(getHostToolsFolder(), SdkConstants.FN_BCC_COMPAT),
+ new File(getHostToolsFolder(), "arm-linux-androideabi-ld"),
+ new File(getHostToolsFolder(), "i686-linux-android-ld"),
+ new File(getHostToolsFolder(), "mipsel-linux-android-ld"));
+ mInitialized = true;
+ }
+ }
+
+ @NonNull
+ @Override
+ public IAndroidTarget getTarget() {
+ if (!mInitialized) {
+ throw new IllegalStateException("SdkParser was not initialized.");
+ }
+ return mTarget;
+ }
+
+ @NonNull
+ @Override
+ public BuildToolInfo getBuildTools() {
+ if (!mInitialized) {
+ throw new IllegalStateException("SdkParser was not initialized.");
+ }
+ return mBuildToolInfo;
+ }
+
+ @Override
+ @NonNull
+ public String getAnnotationsJar() {
+ String host;
+ if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) {
+ host = "darwin-x86";
+ } else if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_LINUX) {
+ host = "linux";
+ } else {
+ throw new IllegalStateException("Windows is not supported for platform development");
+ }
+
+ return mPlatformRootFolder + "/out/host/" + host + "/framework/annotations.jar";
+ }
+
+ @Override
+ @Nullable
+ public FullRevision getPlatformToolsRevision() {
+ return new FullRevision(99);
+ }
+
+ @Override
+ @NonNull
+ public File getZipAlign() {
+ if (mZipAlign == null) {
+ mZipAlign = new File(getHostToolsFolder(), SdkConstants.FN_ZIPALIGN);
+ }
+
+ return mZipAlign;
+ }
+
+ @Override
+ @NonNull
+ public File getAdb() {
+ if (mAdb == null) {
+
+ if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) {
+ mAdb = new File(mPlatformRootFolder, "out/host/darwin-x86/bin/adb");
+ } else if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_LINUX) {
+ mAdb = new File(mPlatformRootFolder, "out/host/linux-x86/bin/adb");
+ } else {
+ throw new IllegalStateException(
+ "Windows is not supported for platform development");
+ }
+ }
+
+ return mAdb;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getRepositories() {
+ List<File> repositories = Lists.newArrayList();
+ repositories.add(new File(mPlatformRootFolder + "/prebuilts/sdk/m2repository"));
+
+ return repositories;
+ }
+
+ private File getHostToolsFolder() {
+ if (mHostTools == null) {
+ File tools = new File(mPlatformRootFolder, "prebuilts/sdk/tools");
+ if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) {
+ mHostTools = new File(tools, "darwin");
+ } else if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_LINUX) {
+ mHostTools = new File(tools, "linux");
+ } else {
+ throw new IllegalStateException(
+ "Windows is not supported for platform development");
+ }
+
+ if (!mHostTools.isDirectory()) {
+ throw new IllegalStateException("Host tools folder missing: " +
+ mHostTools.getAbsolutePath());
+ }
+ }
+ return mHostTools;
+ }
+
+ @Nullable
+ @Override
+ public File getNdkLocation() {
+ return null;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/SdkParser.java b/build-system/builder/src/main/java/com/android/builder/SdkParser.java
new file mode 100644
index 0000000..c247694
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/SdkParser.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.repository.FullRevision;
+import com.android.utils.ILogger;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * A parser able to parse the SDK and return valuable information to the build system.
+ *
+ */
+public interface SdkParser {
+
+ /**
+ * Inits the parser with a target hash string and a build tools FullRevision.
+ *
+ * Note that this may be called several times on the same object, though it will always
+ * be with the same values. Extra calls can be ignored.
+ *
+ * @param target the target hash string.
+ * @param buildToolRevision the build tools revision
+ * @param logger a logger object.
+ *
+ * @throws IllegalStateException if the SDK cannot parsed.
+ *
+ * @see IAndroidTarget#hashString()
+ */
+ public void initParser(@NonNull String target,
+ @NonNull FullRevision buildToolRevision,
+ @NonNull ILogger logger);
+
+ /**
+ * Returns the compilation target
+ * @return the target.
+ *
+ * @throws IllegalStateException if the sdk was not initialized.
+ */
+ @NonNull
+ IAndroidTarget getTarget();
+
+ /**
+ * Returns the BuildToolInfo
+ * @return the build tool info
+ *
+ * @throws IllegalStateException if the sdk was not initialized.
+ */
+ @NonNull
+ BuildToolInfo getBuildTools();
+
+ /**
+ * Returns the location of the annotations jar for compilation targets that are <= 15.
+ */
+ @NonNull
+ String getAnnotationsJar();
+
+ /**
+ * Returns the revision of the installed platform tools component.
+ *
+ * @return the FullRevision or null if the revision couldn't not be found
+ */
+ @Nullable
+ FullRevision getPlatformToolsRevision();
+
+ /**
+ * Returns the location of the zip align tool.
+ */
+ @NonNull
+ File getZipAlign();
+
+ /**
+ * Returns the location of the adb tool.
+ */
+ @NonNull
+ File getAdb();
+
+ /**
+ * Returns the location of artifact repositories built-in the SDK.
+ * @return a non null list of repository folders.
+ */
+ @NonNull
+ List<File> getRepositories();
+
+ @Nullable
+ File getNdkLocation();
+}
\ No newline at end of file
diff --git a/build-system/builder/src/main/java/com/android/builder/VariantConfiguration.java b/build-system/builder/src/main/java/com/android/builder/VariantConfiguration.java
new file mode 100644
index 0000000..80df454
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/VariantConfiguration.java
@@ -0,0 +1,1378 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.builder.dependency.DependencyContainer;
+import com.android.builder.dependency.JarDependency;
+import com.android.builder.dependency.LibraryDependency;
+import com.android.builder.internal.MergedNdkConfig;
+import com.android.builder.internal.StringHelper;
+import com.android.builder.model.ClassField;
+import com.android.builder.model.NdkConfig;
+import com.android.builder.model.ProductFlavor;
+import com.android.builder.model.SigningConfig;
+import com.android.builder.model.SourceProvider;
+import com.android.builder.testing.TestData;
+import com.android.ide.common.res2.AssetSet;
+import com.android.ide.common.res2.ResourceSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * A Variant configuration.
+ */
+public class VariantConfiguration implements TestData {
+
+ private static final ManifestParser sManifestParser = new DefaultManifestParser();
+
+ /**
+ * Full, unique name of the variant in camel case, including BuildType and Flavors (and Test)
+ */
+ private String mFullName;
+ /**
+ * Flavor Name of the variant, including all flavors in camel case (starting with a lower
+ * case).
+ */
+ private String mFlavorName;
+ /**
+ * Full, unique name of the variant, including BuildType, flavors and test, dash separated.
+ * (similar to full name but with dashes)
+ */
+ private String mBaseName;
+ /**
+ * Unique directory name (can include multiple folders) for the variant, based on build type,
+ * flavor and test.
+ * This always uses forward slashes ('/') as separator on all platform.
+ *
+ */
+ private String mDirName;
+
+ @NonNull
+ private final DefaultProductFlavor mDefaultConfig;
+ @NonNull
+ private final SourceProvider mDefaultSourceProvider;
+
+ @NonNull
+ private final DefaultBuildType mBuildType;
+ /** SourceProvider for the BuildType. Can be null */
+ @Nullable
+ private final SourceProvider mBuildTypeSourceProvider;
+
+ private final List<String> mFlavorDimensionNames = Lists.newArrayList();
+ private final List<DefaultProductFlavor> mFlavorConfigs = Lists.newArrayList();
+ private final List<SourceProvider> mFlavorSourceProviders = Lists.newArrayList();
+
+ /** Variant specific source provider, may be null */
+ @Nullable
+ private SourceProvider mVariantSourceProvider;
+
+ /** MultiFlavors specific source provider, may be null */
+ @Nullable
+ private SourceProvider mMultiFlavorSourceProvider;
+
+ @NonNull
+ private final Type mType;
+ /** Optional tested config in case type is Type#TEST */
+ private final VariantConfiguration mTestedConfig;
+ /** An optional output that is only valid if the type is Type#LIBRARY so that the test
+ * for the library can use the library as if it was a normal dependency. */
+ private LibraryDependency mOutput;
+
+ private DefaultProductFlavor mMergedFlavor;
+ private final MergedNdkConfig mMergedNdkConfig = new MergedNdkConfig();
+
+ private final Set<JarDependency> mJars = Sets.newHashSet();
+
+ /** List of direct library dependencies. Each object defines its own dependencies. */
+ private final List<LibraryDependency> mDirectLibraries = Lists.newArrayList();
+
+ /** list of all library dependencies in a flat list.
+ * The order is based on the order needed to call aapt: earlier libraries override resources
+ * of latter ones. */
+ private final List<LibraryDependency> mFlatLibraries = Lists.newArrayList();
+
+ public static enum Type {
+ DEFAULT, LIBRARY, TEST
+ }
+
+ /**
+ * Parses the manifest file and return the package name.
+ * @param manifestFile the manifest file
+ * @return the package name found or null
+ */
+ @Nullable
+ public static String getManifestPackage(@NonNull File manifestFile) {
+ return sManifestParser.getPackage(manifestFile);
+ }
+
+ /**
+ * Creates the configuration with the base source sets.
+ *
+ * This creates a config with a {@link Type#DEFAULT} type.
+ *
+ * @param defaultConfig the default configuration. Required.
+ * @param defaultSourceProvider the default source provider. Required
+ * @param buildType the build type for this variant. Required.
+ * @param buildTypeSourceProvider the source provider for the build type. Required.
+ */
+ public VariantConfiguration(
+ @NonNull DefaultProductFlavor defaultConfig,
+ @NonNull SourceProvider defaultSourceProvider,
+ @NonNull DefaultBuildType buildType,
+ @Nullable SourceProvider buildTypeSourceProvider) {
+ this(
+ defaultConfig, defaultSourceProvider,
+ buildType, buildTypeSourceProvider,
+ Type.DEFAULT, null /*testedConfig*/);
+ }
+
+ /**
+ * Creates the configuration with the base source sets for a given {@link Type}.
+ *
+ * @param defaultConfig the default configuration. Required.
+ * @param defaultSourceProvider the default source provider. Required
+ * @param buildType the build type for this variant. Required.
+ * @param buildTypeSourceProvider the source provider for the build type.
+ * @param type the type of the project.
+ */
+ public VariantConfiguration(
+ @NonNull DefaultProductFlavor defaultConfig,
+ @NonNull SourceProvider defaultSourceProvider,
+ @NonNull DefaultBuildType buildType,
+ @Nullable SourceProvider buildTypeSourceProvider,
+ @NonNull Type type) {
+ this(
+ defaultConfig, defaultSourceProvider,
+ buildType, buildTypeSourceProvider,
+ type, null /*testedConfig*/);
+ }
+
+ /**
+ * Creates the configuration with the base source sets, and an optional tested variant.
+ *
+ * @param defaultConfig the default configuration. Required.
+ * @param defaultSourceProvider the default source provider. Required
+ * @param buildType the build type for this variant. Required.
+ * @param buildTypeSourceProvider the source provider for the build type.
+ * @param type the type of the project.
+ * @param testedConfig the reference to the tested project. Required if type is Type.TEST
+ */
+ public VariantConfiguration(
+ @NonNull DefaultProductFlavor defaultConfig,
+ @NonNull SourceProvider defaultSourceProvider,
+ @NonNull DefaultBuildType buildType,
+ @Nullable SourceProvider buildTypeSourceProvider,
+ @NonNull Type type,
+ @Nullable VariantConfiguration testedConfig) {
+ mDefaultConfig = checkNotNull(defaultConfig);
+ mDefaultSourceProvider = checkNotNull(defaultSourceProvider);
+ mBuildType = checkNotNull(buildType);
+ mBuildTypeSourceProvider = buildTypeSourceProvider;
+ mType = checkNotNull(type);
+ mTestedConfig = testedConfig;
+ checkState(mType != Type.TEST || mTestedConfig != null);
+
+ mMergedFlavor = mDefaultConfig;
+ computeNdkConfig();
+
+ if (testedConfig != null &&
+ testedConfig.mType == Type.LIBRARY &&
+ testedConfig.mOutput != null) {
+ mDirectLibraries.add(testedConfig.mOutput);
+ }
+
+ validate();
+ }
+
+ /**
+ * Returns the full, unique name of the variant in camel case (starting with a lower case),
+ * including BuildType, Flavors and Test (if applicable).
+ *
+ * @return the name of the variant
+ */
+ @NonNull
+ public String getFullName() {
+ if (mFullName == null) {
+ StringBuilder sb = new StringBuilder();
+ String flavorName = getFlavorName();
+ if (!flavorName.isEmpty()) {
+ sb.append(flavorName);
+ sb.append(StringHelper.capitalize(mBuildType.getName()));
+ } else {
+ sb.append(mBuildType.getName());
+ }
+
+ if (mType == Type.TEST) {
+ sb.append("Test");
+ }
+
+ mFullName = sb.toString();
+ }
+
+ return mFullName;
+ }
+
+
+ /**
+ * Returns the flavor name of the variant, including all flavors in camel case (starting
+ * with a lower case). If the variant has no flavor, then an empty string is returned.
+ *
+ * @return the flavor name or an empty string.
+ */
+ @NonNull
+ public String getFlavorName() {
+ if (mFlavorName == null) {
+ if (mFlavorConfigs.isEmpty()) {
+ mFlavorName = "";
+ } else {
+ StringBuilder sb = new StringBuilder();
+ boolean first = true;
+ for (DefaultProductFlavor flavor : mFlavorConfigs) {
+ sb.append(first ? flavor.getName() : StringHelper.capitalize(flavor.getName()));
+ first = false;
+ }
+
+ mFlavorName = sb.toString();
+ }
+ }
+
+ return mFlavorName;
+ }
+
+ /**
+ * Returns the full, unique name of the variant, including BuildType, flavors and test,
+ * dash separated. (similar to full name but with dashes)
+ *
+ * @return the name of the variant
+ */
+ @NonNull
+ public String getBaseName() {
+ if (mBaseName == null) {
+ StringBuilder sb = new StringBuilder();
+
+ if (!mFlavorConfigs.isEmpty()) {
+ for (ProductFlavor pf : mFlavorConfigs) {
+ sb.append(pf.getName()).append('-');
+ }
+ }
+
+ sb.append(mBuildType.getName());
+
+ if (mType == Type.TEST) {
+ sb.append('-').append("test");
+ }
+
+ mBaseName = sb.toString();
+ }
+
+ return mBaseName;
+ }
+
+ /**
+ * Returns a unique directory name (can include multiple folders) for the variant,
+ * based on build type, flavor and test.
+ * This always uses forward slashes ('/') as separator on all platform.
+ *
+ * @return the directory name for the variant
+ */
+ @NonNull
+ public String getDirName() {
+ if (mDirName == null) {
+ StringBuilder sb = new StringBuilder();
+
+ if (mType == Type.TEST) {
+ sb.append("test/");
+ }
+
+ if (!mFlavorConfigs.isEmpty()) {
+ for (DefaultProductFlavor flavor : mFlavorConfigs) {
+ sb.append(flavor.getName());
+ }
+
+ sb.append('/').append(mBuildType.getName());
+
+ } else {
+ sb.append(mBuildType.getName());
+ }
+
+ mDirName = sb.toString();
+
+ }
+
+ return mDirName;
+ }
+
+ /**
+ * Return the names of the applied flavors.
+ *
+ * The list contains the dimension names as well.
+ *
+ * @return the list, possibly empty if there are no flavors.
+ */
+ @NonNull
+ public List<String> getFlavorNamesWithDimensionNames() {
+ if (mFlavorConfigs.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ List<String> names;
+ int count = mFlavorConfigs.size();
+
+ if (count > 1) {
+ names = Lists.newArrayListWithCapacity(count * 2);
+
+ for (int i = 0 ; i < count ; i++) {
+ names.add(mFlavorConfigs.get(i).getName());
+ names.add(mFlavorDimensionNames.get(i));
+ }
+
+ } else {
+ names = Collections.singletonList(mFlavorConfigs.get(0).getName());
+ }
+
+ return names;
+ }
+
+
+ /**
+ * Add a new configured ProductFlavor.
+ *
+ * If multiple flavors are added, the priority follows the order they are added when it
+ * comes to resolving Android resources overlays (ie earlier added flavors supersedes
+ * latter added ones).
+ *
+ * @param productFlavor the configured product flavor
+ * @param sourceProvider the source provider for the product flavor
+ * @param dimensionName the name of the dimension associated with the flavor
+ *
+ * @return the config object
+ */
+ @NonNull
+ public VariantConfiguration addProductFlavor(
+ @NonNull DefaultProductFlavor productFlavor,
+ @NonNull SourceProvider sourceProvider,
+ @NonNull String dimensionName) {
+
+ mFlavorConfigs.add(productFlavor);
+ mFlavorSourceProviders.add(sourceProvider);
+ mFlavorDimensionNames.add(dimensionName);
+
+ mMergedFlavor = productFlavor.mergeOver(mMergedFlavor);
+ computeNdkConfig();
+
+ return this;
+ }
+
+ /**
+ * Sets the variant-specific source provider.
+ * @param sourceProvider the source provider for the product flavor
+ *
+ * @return the config object
+ */
+ public VariantConfiguration setVariantSourceProvider(@Nullable SourceProvider sourceProvider) {
+ mVariantSourceProvider = sourceProvider;
+ return this;
+ }
+
+ /**
+ * Sets the variant-specific source provider.
+ * @param sourceProvider the source provider for the product flavor
+ *
+ * @return the config object
+ */
+ public VariantConfiguration setMultiFlavorSourceProvider(@Nullable SourceProvider sourceProvider) {
+ mMultiFlavorSourceProvider = sourceProvider;
+ return this;
+ }
+
+ /**
+ * Returns the variant specific source provider
+ * @return the source provider or null if none has been provided.
+ */
+ @Nullable
+ public SourceProvider getVariantSourceProvider() {
+ return mVariantSourceProvider;
+ }
+
+ @Nullable
+ public SourceProvider getMultiFlavorSourceProvider() {
+ return mMultiFlavorSourceProvider;
+ }
+
+ private void computeNdkConfig() {
+ mMergedNdkConfig.reset();
+
+ if (mDefaultConfig.getNdkConfig() != null) {
+ mMergedNdkConfig.append(mDefaultConfig.getNdkConfig());
+ }
+
+ for (int i = mFlavorConfigs.size() - 1 ; i >= 0 ; i--) {
+ NdkConfig ndkConfig = mFlavorConfigs.get(i).getNdkConfig();
+ if (ndkConfig != null) {
+ mMergedNdkConfig.append(ndkConfig);
+ }
+ }
+
+ if (mBuildType.getNdkConfig() != null && mType != Type.TEST) {
+ mMergedNdkConfig.append(mBuildType.getNdkConfig());
+ }
+ }
+
+ /**
+ * Sets the dependencies
+ *
+ * @param container a DependencyContainer.
+ * @return the config object
+ */
+ @NonNull
+ public VariantConfiguration setDependencies(@NonNull DependencyContainer container) {
+
+ mDirectLibraries.addAll(container.getAndroidDependencies());
+ mJars.addAll(container.getJarDependencies());
+ mJars.addAll(container.getLocalDependencies());
+
+ resolveIndirectLibraryDependencies(mDirectLibraries, mFlatLibraries);
+
+ for (LibraryDependency libraryDependency : mFlatLibraries) {
+ mJars.addAll(libraryDependency.getLocalDependencies());
+ }
+ return this;
+ }
+
+ /**
+ * Returns the list of jar dependencies
+ * @return a non null collection of Jar dependencies.
+ */
+ @NonNull
+ public Collection<JarDependency> getJars() {
+ return mJars;
+ }
+
+ /**
+ * Sets the output of this variant. This is required when the variant is a library so that
+ * the variant that tests this library can properly include the tested library in its own
+ * package.
+ *
+ * @param output the output of the library as an LibraryDependency that will provides the
+ * location of all the created items.
+ * @return the config object
+ */
+ @NonNull
+ public VariantConfiguration setOutput(LibraryDependency output) {
+ mOutput = output;
+ return this;
+ }
+
+ @NonNull
+ public DefaultProductFlavor getDefaultConfig() {
+ return mDefaultConfig;
+ }
+
+ @NonNull
+ public SourceProvider getDefaultSourceSet() {
+ return mDefaultSourceProvider;
+ }
+
+ @NonNull
+ public DefaultProductFlavor getMergedFlavor() {
+ return mMergedFlavor;
+ }
+
+ @NonNull
+ public DefaultBuildType getBuildType() {
+ return mBuildType;
+ }
+
+ /**
+ * The SourceProvider for the BuildType. Can be null.
+ */
+ @Nullable
+ public SourceProvider getBuildTypeSourceSet() {
+ return mBuildTypeSourceProvider;
+ }
+
+ public boolean hasFlavors() {
+ return !mFlavorConfigs.isEmpty();
+ }
+
+ @NonNull
+ public List<DefaultProductFlavor> getFlavorConfigs() {
+ return mFlavorConfigs;
+ }
+
+ /**
+ * Returns the list of SourceProviders for the flavors.
+ *
+ * The list is ordered from higher priority to lower priority.
+ *
+ * @return the list of Source Providers for the flavors. Never null.
+ */
+ @NonNull
+ public List<SourceProvider> getFlavorSourceProviders() {
+ return mFlavorSourceProviders;
+ }
+
+ public boolean hasLibraries() {
+ return !mDirectLibraries.isEmpty();
+ }
+
+ /**
+ * Returns the direct library dependencies
+ */
+ @NonNull
+ public List<LibraryDependency> getDirectLibraries() {
+ return mDirectLibraries;
+ }
+
+ /**
+ * Returns all the library dependencies, direct and transitive.
+ */
+ @NonNull
+ public List<LibraryDependency> getAllLibraries() {
+ return mFlatLibraries;
+ }
+
+ @NonNull
+ public Type getType() {
+ return mType;
+ }
+
+ @Nullable
+ public VariantConfiguration getTestedConfig() {
+ return mTestedConfig;
+ }
+
+ /**
+ * Resolves a given list of libraries, finds out if they depend on other libraries, and
+ * returns a flat list of all the direct and indirect dependencies in the proper order (first
+ * is higher priority when calling aapt).
+ * @param directDependencies the libraries to resolve
+ * @param outFlatDependencies where to store all the libraries.
+ */
+ @VisibleForTesting
+ void resolveIndirectLibraryDependencies(List<LibraryDependency> directDependencies,
+ List<LibraryDependency> outFlatDependencies) {
+ if (directDependencies == null) {
+ return;
+ }
+ // loop in the inverse order to resolve dependencies on the libraries, so that if a library
+ // is required by two higher level libraries it can be inserted in the correct place
+ for (int i = directDependencies.size() - 1 ; i >= 0 ; i--) {
+ LibraryDependency library = directDependencies.get(i);
+
+ // get its libraries
+ Collection<LibraryDependency> dependencies = library.getDependencies();
+ List<LibraryDependency> depList = Lists.newArrayList(dependencies);
+
+ // resolve the dependencies for those libraries
+ resolveIndirectLibraryDependencies(depList, outFlatDependencies);
+
+ // and add the current one (if needed) in front (higher priority)
+ if (!outFlatDependencies.contains(library)) {
+ outFlatDependencies.add(0, library);
+ }
+ }
+ }
+
+ /**
+ * Returns the original package name before any overrides from flavors.
+ * If the variant is a test variant, then the package name is the one coming from the
+ * configuration of the tested variant, and this call is similar to #getPackageName()
+ * @return the package name
+ */
+ @Nullable
+ public String getOriginalPackageName() {
+ if (mType == VariantConfiguration.Type.TEST) {
+ return getPackageName();
+ }
+
+ return getPackageFromManifest();
+ }
+
+ /**
+ * Returns the package name for this variant. This could be coming from the manifest or
+ * could be overridden through the product flavors and/or the build Type.
+ * @return the package
+ */
+ @Override
+ @NonNull
+ public String getPackageName() {
+ String packageName;
+
+ if (mType == Type.TEST) {
+ assert mTestedConfig != null;
+
+ packageName = mMergedFlavor.getTestPackageName();
+ if (packageName == null) {
+ String testedPackage = mTestedConfig.getPackageName();
+ packageName = testedPackage + ".test";
+ }
+ } else {
+ // first get package override.
+ packageName = getPackageOverride();
+ // if it's null, this means we just need the default package
+ // from the manifest since both flavor and build type do nothing.
+ if (packageName == null) {
+ packageName = getPackageFromManifest();
+ }
+ }
+
+ if (packageName == null) {
+ throw new RuntimeException("Failed get query package name for " + getFullName());
+ }
+
+ return packageName;
+ }
+
+ @Override
+ @Nullable
+ public String getTestedPackageName() {
+ if (mType == Type.TEST) {
+ assert mTestedConfig != null;
+ if (mTestedConfig.mType == Type.LIBRARY) {
+ return getPackageName();
+ } else {
+ return mTestedConfig.getPackageName();
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the package override values coming from the Product Flavor and/or the Build Type.
+ * If the package is not overridden then this returns null.
+ *
+ * @return the package override or null
+ */
+ @Nullable
+ public String getPackageOverride() {
+ String packageName = mMergedFlavor.getPackageName();
+ String packageSuffix = mBuildType.getPackageNameSuffix();
+
+ if (packageSuffix != null && packageSuffix.length() > 0) {
+ if (packageName == null) {
+ packageName = getPackageFromManifest();
+ }
+
+ if (packageSuffix.charAt(0) == '.') {
+ packageName = packageName + packageSuffix;
+ } else {
+ packageName = packageName + '.' + packageSuffix;
+ }
+ }
+
+ return packageName;
+ }
+
+ /**
+ * Returns the version name for this variant. This could be coming from the manifest or
+ * could be overridden through the product flavors, and can have a suffix specified by
+ * the build type.
+ *
+ * @return the version name
+ */
+ @Nullable
+ public String getVersionName() {
+ String versionName = mMergedFlavor.getVersionName();
+ String versionSuffix = mBuildType.getVersionNameSuffix();
+
+ if (versionSuffix != null && versionSuffix.length() > 0) {
+ if (versionName == null) {
+ if (mType != Type.TEST) {
+ versionName = getVersionNameFromManifest();
+ } else {
+ versionName = "";
+ }
+ }
+
+ versionName = versionName + versionSuffix;
+ }
+
+ return versionName;
+ }
+
+ /**
+ * Returns the version code for this variant. This could be coming from the manifest or
+ * could be overridden through the product flavors, and can have a suffix specified by
+ * the build type.
+ *
+ * @return the version code or -1 if there was non defined.
+ */
+ public int getVersionCode() {
+ int versionCode = mMergedFlavor.getVersionCode();
+
+ if (versionCode == -1 && mType != Type.TEST) {
+
+ versionCode = getVersionCodeFromManifest();
+ }
+
+ return versionCode;
+ }
+
+ private final static String DEFAULT_TEST_RUNNER = "android.test.InstrumentationTestRunner";
+ private final static Boolean DEFAULT_HANDLE_PROFILING = false;
+ private final static Boolean DEFAULT_FUNCTIONAL_TEST = false;
+
+ /**
+ * Returns the instrumentationRunner to use to test this variant, or if the
+ * variant is a test, the one to use to test the tested variant.
+ * @return the instrumentation test runner name
+ */
+ @Override
+ @NonNull
+ public String getInstrumentationRunner() {
+ VariantConfiguration config = this;
+ if (mType == Type.TEST) {
+ config = getTestedConfig();
+ }
+ String runner = config.mMergedFlavor.getTestInstrumentationRunner();
+ return runner != null ? runner : DEFAULT_TEST_RUNNER;
+ }
+
+ /**
+ * Returns handleProfiling value to use to test this variant, or if the
+ * variant is a test, the one to use to test the tested variant.
+ * @return the handleProfiling value
+ */
+ @Override
+ @NonNull
+ public Boolean getHandleProfiling() {
+ VariantConfiguration config = this;
+ if (mType == Type.TEST) {
+ config = getTestedConfig();
+ }
+ Boolean handleProfiling = config.mMergedFlavor.getTestHandleProfiling();
+ return handleProfiling != null ? handleProfiling : DEFAULT_HANDLE_PROFILING;
+ }
+
+ /**
+ * Returns functionalTest value to use to test this variant, or if the
+ * variant is a test, the one to use to test the tested variant.
+ * @return the functionalTest value
+ */
+ @Override
+ @NonNull
+ public Boolean getFunctionalTest() {
+ VariantConfiguration config = this;
+ if (mType == Type.TEST) {
+ config = getTestedConfig();
+ }
+ Boolean functionalTest = config.mMergedFlavor.getTestFunctionalTest();
+ return functionalTest != null ? functionalTest : DEFAULT_FUNCTIONAL_TEST;
+ }
+
+ /**
+ * Reads the package name from the manifest. This is unmodified by the build type.
+ */
+ @Nullable
+ public String getPackageFromManifest() {
+ assert mType != Type.TEST;
+ File manifestLocation = mDefaultSourceProvider.getManifestFile();
+ return sManifestParser.getPackage(manifestLocation);
+ }
+
+ /**
+ * Reads the version name from the manifest.
+ */
+ @Nullable
+ public String getVersionNameFromManifest() {
+ File manifestLocation = mDefaultSourceProvider.getManifestFile();
+ return sManifestParser.getVersionName(manifestLocation);
+ }
+
+ /**
+ * Reads the version code from the manifest.
+ */
+ public int getVersionCodeFromManifest() {
+ File manifestLocation = mDefaultSourceProvider.getManifestFile();
+ return sManifestParser.getVersionCode(manifestLocation);
+ }
+
+ /**
+ * Return the minSdkVersion for this variant.
+ *
+ * This uses both the value from the manifest (if present), and the override coming
+ * from the flavor(s) (if present).
+ * @return the minSdkVersion
+ */
+ @Override
+ public int getMinSdkVersion() {
+ if (mTestedConfig != null) {
+ return mTestedConfig.getMinSdkVersion();
+ }
+ int minSdkVersion = mMergedFlavor.getMinSdkVersion();
+ if (minSdkVersion == -1) {
+ // read it from the main manifest
+ File manifestLocation = mDefaultSourceProvider.getManifestFile();
+ minSdkVersion = sManifestParser.getMinSdkVersion(manifestLocation);
+ }
+
+ return minSdkVersion;
+ }
+
+ /**
+ * Return the targetSdkVersion for this variant.
+ *
+ * This uses both the value from the manifest (if present), and the override coming
+ * from the flavor(s) (if present).
+ * @return the targetSdkVersion
+ */
+ public int getTargetSdkVersion() {
+ if (mTestedConfig != null) {
+ return mTestedConfig.getTargetSdkVersion();
+ }
+ int targetSdkVersion = mMergedFlavor.getTargetSdkVersion();
+ if (targetSdkVersion == -1) {
+ // read it from the main manifest
+ File manifestLocation = mDefaultSourceProvider.getManifestFile();
+ targetSdkVersion = sManifestParser.getTargetSdkVersion(manifestLocation);
+ }
+
+ return targetSdkVersion;
+ }
+
+ @Nullable
+ public File getMainManifest() {
+ File defaultManifest = mDefaultSourceProvider.getManifestFile();
+
+ // this could not exist in a test project.
+ if (defaultManifest.isFile()) {
+ return defaultManifest;
+ }
+
+ return null;
+ }
+
+ @NonNull
+ public List<File> getManifestOverlays() {
+ List<File> inputs = Lists.newArrayList();
+
+ if (mVariantSourceProvider != null) {
+ File variantLocation = mVariantSourceProvider.getManifestFile();
+ if (variantLocation.isFile()) {
+ inputs.add(variantLocation);
+ }
+ }
+
+ if (mBuildTypeSourceProvider != null) {
+ File typeLocation = mBuildTypeSourceProvider.getManifestFile();
+ if (typeLocation.isFile()) {
+ inputs.add(typeLocation);
+ }
+ }
+
+ if (mMultiFlavorSourceProvider != null) {
+ File variantLocation = mMultiFlavorSourceProvider.getManifestFile();
+ if (variantLocation.isFile()) {
+ inputs.add(variantLocation);
+ }
+ }
+
+ for (SourceProvider sourceProvider : mFlavorSourceProviders) {
+ File f = sourceProvider.getManifestFile();
+ if (f.isFile()) {
+ inputs.add(f);
+ }
+ }
+
+ return inputs;
+ }
+
+ /**
+ * Returns the dynamic list of {@link ResourceSet} based on the configuration, its dependencies,
+ * as well as tested config if applicable (test of a library).
+ *
+ * The list is ordered in ascending order of importance, meaning the first set is meant to be
+ * overridden by the 2nd one and so on. This is meant to facilitate usage of the list in a
+ * {@link com.android.ide.common.res2.ResourceMerger}.
+ *
+ * @param generatedResFolder the generated res folder typically the output of the renderscript
+ * compilation
+ * @param includeDependencies whether to include in the result the resources of the dependencies
+ *
+ * @return a list ResourceSet.
+ */
+ @NonNull
+ public List<ResourceSet> getResourceSets(@Nullable File generatedResFolder,
+ boolean includeDependencies) {
+ List<ResourceSet> resourceSets = Lists.newArrayList();
+
+ // the list of dependency must be reversed to use the right overlay order.
+ if (includeDependencies) {
+ for (int n = mFlatLibraries.size() - 1 ; n >= 0 ; n--) {
+ LibraryDependency dependency = mFlatLibraries.get(n);
+ File resFolder = dependency.getResFolder();
+ if (resFolder.isDirectory()) {
+ ResourceSet resourceSet = new ResourceSet(dependency.getFolder().getName());
+ resourceSet.addSource(resFolder);
+ resourceSets.add(resourceSet);
+ }
+ }
+ }
+
+ Collection<File> mainResDirs = mDefaultSourceProvider.getResDirectories();
+
+ ResourceSet resourceSet = new ResourceSet(BuilderConstants.MAIN);
+ resourceSet.addSources(mainResDirs);
+ if (generatedResFolder != null) {
+ resourceSet.addSource(generatedResFolder);
+ }
+ resourceSets.add(resourceSet);
+
+ // the list of flavor must be reversed to use the right overlay order.
+ for (int n = mFlavorSourceProviders.size() - 1; n >= 0 ; n--) {
+ SourceProvider sourceProvider = mFlavorSourceProviders.get(n);
+
+ Collection<File> flavorResDirs = sourceProvider.getResDirectories();
+ // we need the same of the flavor config, but it's in a different list.
+ // This is fine as both list are parallel collections with the same number of items.
+ resourceSet = new ResourceSet(mFlavorConfigs.get(n).getName());
+ resourceSet.addSources(flavorResDirs);
+ resourceSets.add(resourceSet);
+ }
+
+ // multiflavor specific overrides flavor
+ if (mMultiFlavorSourceProvider != null) {
+ Collection<File> variantResDirs = mMultiFlavorSourceProvider.getResDirectories();
+ resourceSet = new ResourceSet(getFlavorName());
+ resourceSet.addSources(variantResDirs);
+ resourceSets.add(resourceSet);
+ }
+
+ // build type overrides the flavors
+ if (mBuildTypeSourceProvider != null) {
+ Collection<File> typeResDirs = mBuildTypeSourceProvider.getResDirectories();
+ resourceSet = new ResourceSet(mBuildType.getName());
+ resourceSet.addSources(typeResDirs);
+ resourceSets.add(resourceSet);
+ }
+
+ // variant specific overrides all
+ if (mVariantSourceProvider != null) {
+ Collection<File> variantResDirs = mVariantSourceProvider.getResDirectories();
+ resourceSet = new ResourceSet(getFullName());
+ resourceSet.addSources(variantResDirs);
+ resourceSets.add(resourceSet);
+ }
+
+ return resourceSets;
+ }
+
+ /**
+ * Returns the dynamic list of {@link AssetSet} based on the configuration, its dependencies,
+ * as well as tested config if applicable (test of a library).
+ *
+ * The list is ordered in ascending order of importance, meaning the first set is meant to be
+ * overridden by the 2nd one and so on. This is meant to facilitate usage of the list in a
+ * {@link com.android.ide.common.res2.AssetMerger}.
+ *
+ * @return a list ResourceSet.
+ */
+ @NonNull
+ public List<AssetSet> getAssetSets(boolean includeDependencies) {
+ List<AssetSet> assetSets = Lists.newArrayList();
+
+ if (includeDependencies) {
+ // the list of dependency must be reversed to use the right overlay order.
+ for (int n = mFlatLibraries.size() - 1 ; n >= 0 ; n--) {
+ LibraryDependency dependency = mFlatLibraries.get(n);
+ File assetFolder = dependency.getAssetsFolder();
+ if (assetFolder.isDirectory()) {
+ AssetSet assetSet = new AssetSet(dependency.getFolder().getName());
+ assetSet.addSource(assetFolder);
+ assetSets.add(assetSet);
+ }
+ }
+ }
+
+ Collection<File> mainResDirs = mDefaultSourceProvider.getAssetsDirectories();
+
+ AssetSet assetSet = new AssetSet(BuilderConstants.MAIN);
+ assetSet.addSources(mainResDirs);
+ assetSets.add(assetSet);
+
+ // the list of flavor must be reversed to use the right overlay order.
+ for (int n = mFlavorSourceProviders.size() - 1; n >= 0 ; n--) {
+ SourceProvider sourceProvider = mFlavorSourceProviders.get(n);
+
+ Collection<File> flavorResDirs = sourceProvider.getAssetsDirectories();
+ // we need the same of the flavor config, but it's in a different list.
+ // This is fine as both list are parallel collections with the same number of items.
+ assetSet = new AssetSet(mFlavorConfigs.get(n).getName());
+ assetSet.addSources(flavorResDirs);
+ assetSets.add(assetSet);
+ }
+
+ // multiflavor specific overrides flavor
+ if (mMultiFlavorSourceProvider != null) {
+ Collection<File> variantResDirs = mMultiFlavorSourceProvider.getAssetsDirectories();
+ assetSet = new AssetSet(getFlavorName());
+ assetSet.addSources(variantResDirs);
+ assetSets.add(assetSet);
+ }
+
+ // build type overrides flavors
+ if (mBuildTypeSourceProvider != null) {
+ Collection<File> typeResDirs = mBuildTypeSourceProvider.getAssetsDirectories();
+ assetSet = new AssetSet(mBuildType.getName());
+ assetSet.addSources(typeResDirs);
+ assetSets.add(assetSet);
+ }
+
+ // variant specific overrides all
+ if (mVariantSourceProvider != null) {
+ Collection<File> variantResDirs = mVariantSourceProvider.getAssetsDirectories();
+ assetSet = new AssetSet(getFullName());
+ assetSet.addSources(variantResDirs);
+ assetSets.add(assetSet);
+ }
+
+ return assetSets;
+ }
+
+ @NonNull
+ public List<File> getLibraryJniFolders() {
+ List<File> list = Lists.newArrayListWithExpectedSize(mFlatLibraries.size());
+
+ for (int n = mFlatLibraries.size() - 1 ; n >= 0 ; n--) {
+ LibraryDependency dependency = mFlatLibraries.get(n);
+ File jniFolder = dependency.getJniFolder();
+ if (jniFolder.isDirectory()) {
+ list.add(jniFolder);
+ }
+ }
+
+ return list;
+ }
+
+ /**
+ * Returns all the renderscript import folder that are outside of the current project.
+ */
+ @NonNull
+ public List<File> getRenderscriptImports() {
+ List<File> list = Lists.newArrayList();
+
+ for (LibraryDependency lib : mFlatLibraries) {
+ File rsLib = lib.getRenderscriptFolder();
+ if (rsLib.isDirectory()) {
+ list.add(rsLib);
+ }
+ }
+
+ return list;
+ }
+
+ /**
+ * Returns all the renderscript source folder from the main config, the flavors and the
+ * build type.
+ *
+ * @return a list of folders.
+ */
+ @NonNull
+ public List<File> getRenderscriptSourceList() {
+ List<File> sourceList = Lists.newArrayList();
+ sourceList.addAll(mDefaultSourceProvider.getRenderscriptDirectories());
+ if (mType != Type.TEST && mBuildTypeSourceProvider != null) {
+ sourceList.addAll(mBuildTypeSourceProvider.getRenderscriptDirectories());
+ }
+
+ if (hasFlavors()) {
+ for (SourceProvider flavorSourceSet : mFlavorSourceProviders) {
+ sourceList.addAll(flavorSourceSet.getRenderscriptDirectories());
+ }
+ }
+
+ if (mMultiFlavorSourceProvider != null) {
+ sourceList.addAll(mMultiFlavorSourceProvider.getRenderscriptDirectories());
+ }
+
+ if (mVariantSourceProvider != null) {
+ sourceList.addAll(mVariantSourceProvider.getRenderscriptDirectories());
+ }
+
+ return sourceList;
+ }
+
+ /**
+ * Returns all the aidl import folder that are outside of the current project.
+ */
+ @NonNull
+ public List<File> getAidlImports() {
+ List<File> list = Lists.newArrayList();
+
+ for (LibraryDependency lib : mFlatLibraries) {
+ File aidlLib = lib.getAidlFolder();
+ if (aidlLib.isDirectory()) {
+ list.add(aidlLib);
+ }
+ }
+
+ return list;
+ }
+
+ @NonNull
+ public List<File> getAidlSourceList() {
+ List<File> sourceList = Lists.newArrayList();
+ sourceList.addAll(mDefaultSourceProvider.getAidlDirectories());
+ if (mType != Type.TEST && mBuildTypeSourceProvider != null) {
+ sourceList.addAll(mBuildTypeSourceProvider.getAidlDirectories());
+ }
+
+ if (hasFlavors()) {
+ for (SourceProvider flavorSourceSet : mFlavorSourceProviders) {
+ sourceList.addAll(flavorSourceSet.getAidlDirectories());
+ }
+ }
+
+ if (mMultiFlavorSourceProvider != null) {
+ sourceList.addAll(mMultiFlavorSourceProvider.getAidlDirectories());
+ }
+
+ if (mVariantSourceProvider != null) {
+ sourceList.addAll(mVariantSourceProvider.getAidlDirectories());
+ }
+
+ return sourceList;
+ }
+
+ @NonNull
+ public List<File> getJniSourceList() {
+ List<File> sourceList = Lists.newArrayList();
+ sourceList.addAll(mDefaultSourceProvider.getJniDirectories());
+ if (mType != Type.TEST && mBuildTypeSourceProvider != null) {
+ sourceList.addAll(mBuildTypeSourceProvider.getJniDirectories());
+ }
+
+ if (hasFlavors()) {
+ for (SourceProvider flavorSourceSet : mFlavorSourceProviders) {
+ sourceList.addAll(flavorSourceSet.getJniDirectories());
+ }
+ }
+
+ if (mMultiFlavorSourceProvider != null) {
+ sourceList.addAll(mMultiFlavorSourceProvider.getJniDirectories());
+ }
+
+ if (mVariantSourceProvider != null) {
+ sourceList.addAll(mVariantSourceProvider.getJniDirectories());
+ }
+
+ return sourceList;
+ }
+
+ /**
+ * Returns the compile classpath for this config. If the config tests a library, this
+ * will include the classpath of the tested config
+ *
+ * @return a non null, but possibly empty set.
+ */
+ @NonNull
+ public Set<File> getCompileClasspath() {
+ Set<File> classpath = Sets.newHashSet();
+
+ for (LibraryDependency lib : mFlatLibraries) {
+ classpath.add(lib.getJarFile());
+ for (File jarFile : lib.getLocalJars()) {
+ classpath.add(jarFile);
+ }
+ }
+
+ for (JarDependency jar : mJars) {
+ if (jar.isCompiled()) {
+ classpath.add(jar.getJarFile());
+ }
+ }
+
+ return classpath;
+ }
+
+ /**
+ * Returns the list of packaged jars for this config. If the config tests a library, this
+ * will include the jars of the tested config
+ *
+ * @return a non null, but possibly empty list.
+ */
+ @NonNull
+ public List<File> getPackagedJars() {
+ Set<File> jars = Sets.newHashSetWithExpectedSize(mJars.size() + mFlatLibraries.size());
+
+ for (JarDependency jar : mJars) {
+ File jarFile = jar.getJarFile();
+ if (jar.isPackaged() && jarFile.exists()) {
+ jars.add(jarFile);
+ }
+ }
+
+ for (LibraryDependency libraryDependency : mFlatLibraries) {
+ File libJar = libraryDependency.getJarFile();
+ if (libJar.exists()) {
+ jars.add(libJar);
+ }
+ for (File jarFile : libraryDependency.getLocalJars()) {
+ if (jarFile.isFile()) {
+ jars.add(jarFile);
+ }
+ }
+ }
+
+ return Lists.newArrayList(jars);
+ }
+
+ /**
+ * Returns a list of items for the BuildConfig class.
+ *
+ * Items can be either fields (instance of {@link com.android.builder.model.ClassField})
+ * or comments (instance of String).
+ *
+ * @return a list of items.
+ */
+ @NonNull
+ public List<Object> getBuildConfigItems() {
+ List<Object> fullList = Lists.newArrayList();
+
+ Set<String> usedFieldNames = Sets.newHashSet();
+
+ List<ClassField> list = mBuildType.getBuildConfigFields();
+ if (!list.isEmpty()) {
+ fullList.add("Fields from build type: " + mBuildType.getName());
+ for (ClassField f : list) {
+ usedFieldNames.add(f.getName());
+ fullList.add(f);
+ }
+ }
+
+ for (DefaultProductFlavor flavor : mFlavorConfigs) {
+ list = flavor.getBuildConfigFields();
+ if (!list.isEmpty()) {
+ fullList.add("Fields from product flavor: " + flavor.getName());
+ for (ClassField f : list) {
+ String name = f.getName();
+ if (!usedFieldNames.contains(name)) {
+ usedFieldNames.add(f.getName());
+ fullList.add(f);
+ }
+ }
+ }
+ }
+
+ list = mDefaultConfig.getBuildConfigFields();
+ if (!list.isEmpty()) {
+ fullList.add("Fields from default config.");
+ for (ClassField f : list) {
+ String name = f.getName();
+ if (!usedFieldNames.contains(name)) {
+ usedFieldNames.add(f.getName());
+ fullList.add(f);
+ }
+ }
+ }
+
+ return fullList;
+ }
+
+ @Nullable
+ public SigningConfig getSigningConfig() {
+ SigningConfig signingConfig = mBuildType.getSigningConfig();
+ if (signingConfig != null) {
+ return signingConfig;
+ }
+ return mMergedFlavor.getSigningConfig();
+ }
+
+ public boolean isSigningReady() {
+ SigningConfig signingConfig = getSigningConfig();
+ return signingConfig != null && signingConfig.isSigningReady();
+ }
+
+ @NonNull
+ public List<Object> getProguardFiles(boolean includeLibraries) {
+ List<Object> fullList = Lists.newArrayList();
+
+ // add the config files from the build type, main config and flavors
+ fullList.addAll(mDefaultConfig.getProguardFiles());
+ fullList.addAll(mBuildType.getProguardFiles());
+
+ for (DefaultProductFlavor flavor : mFlavorConfigs) {
+ fullList.addAll(flavor.getProguardFiles());
+ }
+
+ // now add the one coming from the library dependencies
+ if (includeLibraries) {
+ for (LibraryDependency libraryDependency : mFlatLibraries) {
+ File proguardRules = libraryDependency.getProguardRules();
+ if (proguardRules.exists()) {
+ fullList.add(proguardRules);
+ }
+ }
+ }
+
+ return fullList;
+ }
+
+ @NonNull
+ public List<Object> getConsumerProguardFiles() {
+ List<Object> fullList = Lists.newArrayList();
+
+ // add the config files from the build type, main config and flavors
+ fullList.addAll(mDefaultConfig.getConsumerProguardFiles());
+ fullList.addAll(mBuildType.getConsumerProguardFiles());
+
+ for (DefaultProductFlavor flavor : mFlavorConfigs) {
+ fullList.addAll(flavor.getConsumerProguardFiles());
+ }
+
+ return fullList;
+ }
+
+ protected void validate() {
+ if (mType != Type.TEST) {
+ File manifest = mDefaultSourceProvider.getManifestFile();
+ if (!manifest.isFile()) {
+ throw new IllegalArgumentException(
+ "Main Manifest missing from " + manifest.getAbsolutePath());
+ }
+ }
+ }
+
+ @NonNull
+ public NdkConfig getNdkConfig() {
+ return mMergedNdkConfig;
+ }
+
+ @Nullable
+ @Override
+ public Set<String> getSupportedAbis() {
+ if (mMergedNdkConfig != null) {
+ return mMergedNdkConfig.getAbiFilters();
+ }
+
+ return null;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/compiling/BuildConfigGenerator.java b/build-system/builder/src/main/java/com/android/builder/compiling/BuildConfigGenerator.java
new file mode 100644
index 0000000..1e298b4
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/compiling/BuildConfigGenerator.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.builder.compiling;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.AndroidBuilder;
+import com.android.builder.model.ClassField;
+import com.google.common.collect.Lists;
+import com.squareup.javawriter.JavaWriter;
+
+import javax.lang.model.element.Modifier;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Class able to generate a BuildConfig class in Android project.
+ * The BuildConfig class contains constants related to the build target.
+ */
+public class BuildConfigGenerator {
+
+ public final static String BUILD_CONFIG_NAME = "BuildConfig.java";
+
+ private final String mGenFolder;
+ private final String mBuildConfigPackageName;
+
+ private final List<ClassField> mFields = Lists.newArrayList();
+ private List<Object> mItems = Lists.newArrayList();
+
+ /**
+ * Creates a generator
+ * @param genFolder the gen folder of the project
+ * @param buildConfigPackageName the package in which to create the class.
+ */
+ public BuildConfigGenerator(@NonNull String genFolder, @NonNull String buildConfigPackageName) {
+ mGenFolder = checkNotNull(genFolder);
+ mBuildConfigPackageName = checkNotNull(buildConfigPackageName);
+ }
+
+ public BuildConfigGenerator addField(
+ @NonNull String type, @NonNull String name, @NonNull String value) {
+ mFields.add(AndroidBuilder.createClassField(type, name, value));
+ return this;
+ }
+
+ public BuildConfigGenerator addItems(@Nullable Collection<Object> items) {
+ if (items != null) {
+ mItems.addAll(items);
+ }
+ return this;
+ }
+
+ /**
+ * Returns a File representing where the BuildConfig class will be.
+ */
+ public File getFolderPath() {
+ File genFolder = new File(mGenFolder);
+ return new File(genFolder, mBuildConfigPackageName.replace('.', File.separatorChar));
+ }
+
+ public File getBuildConfigFile() {
+ File folder = getFolderPath();
+ return new File(folder, BUILD_CONFIG_NAME);
+ }
+
+ /**
+ * Generates the BuildConfig class.
+ */
+ public void generate() throws IOException {
+ File pkgFolder = getFolderPath();
+ if (!pkgFolder.isDirectory()) {
+ if (!pkgFolder.mkdirs()) {
+ throw new RuntimeException("Failed to create " + pkgFolder.getAbsolutePath());
+ }
+ }
+
+ File buildConfigJava = new File(pkgFolder, BUILD_CONFIG_NAME);
+ FileWriter out = new FileWriter(buildConfigJava);
+
+ JavaWriter writer = new JavaWriter(out);
+
+ Set<Modifier> publicFinal = EnumSet.of(Modifier.PUBLIC, Modifier.FINAL);
+ Set<Modifier> publicFinalStatic = EnumSet.of(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC);
+
+ writer.emitJavadoc("Automatically generated file. DO NOT MODIFY")
+ .emitPackage(mBuildConfigPackageName)
+ .beginType("BuildConfig", "class", publicFinal);
+
+ for (ClassField field : mFields) {
+ writer.emitField(
+ field.getType(),
+ field.getName(),
+ publicFinalStatic,
+ field.getValue());
+ }
+
+ for (Object item : mItems) {
+ if (item instanceof ClassField) {
+ ClassField field = (ClassField)item;
+ writer.emitField(
+ field.getType(),
+ field.getName(),
+ publicFinalStatic,
+ field.getValue());
+
+ } else if (item instanceof String) {
+ writer.emitSingleLineComment((String) item);
+ }
+ }
+
+ writer.endType();
+
+ out.close();
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/compiling/DependencyFileProcessor.java b/build-system/builder/src/main/java/com/android/builder/compiling/DependencyFileProcessor.java
new file mode 100644
index 0000000..cc2fd35
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/compiling/DependencyFileProcessor.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.compiling;
+
+import com.android.annotations.NonNull;
+
+import java.io.File;
+
+/**
+ * A Class that processes a dependency file after a compilation.
+ *
+ * During compilation of aidl and renderscript, it is possible to provide an instance of
+ * DependencyFileProcessor to process the dependency files generated by the compilers.
+ *
+ * It can be useful to store the dependency in a better format than a per-file dependency file.
+ *
+ * The instance will be called for each dependency file that is created during compilation, and
+ * if the file can be processed will notify the compiler that the original dependency file is not
+ * needed anymore.
+ *
+ * @see com.android.builder.AndroidBuilder#compileAllAidlFiles(java.util.List, java.io.File, java.util.List, DependencyFileProcessor)
+ * @see com.android.builder.AndroidBuilder#compileAidlFile(java.io.File, java.io.File, java.util.List, DependencyFileProcessor)
+ * @see com.android.builder.AndroidBuilder#compileAllRenderscriptFiles(java.util.List, java.util.List, java.io.File, java.io.File, int, boolean, int)
+ */
+public interface DependencyFileProcessor {
+
+ /**
+ * Processes the dependency file.
+ * @param dependencyFile the dependency file.
+ * @return true if the dependency file can be deleted by the caller.
+ */
+ boolean processFile(@NonNull File dependencyFile);
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/dependency/DependencyContainer.java b/build-system/builder/src/main/java/com/android/builder/dependency/DependencyContainer.java
new file mode 100644
index 0000000..6622435
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/dependency/DependencyContainer.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.dependency;
+
+import com.android.annotations.NonNull;
+
+import java.util.List;
+
+/**
+ * An object able to provide the three types of dependencies an Android project can have:
+ * - local jar dependencies
+ * - artifact jar dependencies
+ * - android library dependencies
+ */
+public interface DependencyContainer {
+
+ /**
+ * Returns a list top level dependency. Each library object should contain
+ * its own dependencies. This is actually a dependency graph.
+ *
+ * @return a non null (but possibly empty) list.
+ */
+ @NonNull
+ List<? extends LibraryDependency> getAndroidDependencies();
+
+ @NonNull
+ List<JarDependency> getJarDependencies();
+
+ @NonNull
+ List<JarDependency> getLocalDependencies();
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/dependency/JarDependency.java b/build-system/builder/src/main/java/com/android/builder/dependency/JarDependency.java
new file mode 100644
index 0000000..d2831a0
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/dependency/JarDependency.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.dependency;
+
+import com.android.annotations.NonNull;
+
+import java.io.File;
+
+/**
+ * Represents a Jar dependency. This could be the output of a Java project.
+ */
+public class JarDependency {
+
+ private final File mJarFile;
+ private final boolean mCompiled;
+ private final boolean mPackaged;
+ private final boolean mProguarded;
+
+ public JarDependency(@NonNull File jarFile, boolean compiled, boolean packaged,
+ boolean proguarded) {
+ mJarFile = jarFile;
+ mCompiled = compiled;
+ mPackaged = packaged;
+ mProguarded = proguarded;
+ }
+
+ public JarDependency(@NonNull File jarFile) {
+ this(jarFile, true, true, true);
+ }
+
+ public JarDependency(@NonNull File jarFile, boolean compiled, boolean packaged) {
+ this(jarFile, compiled, packaged, true);
+ }
+
+ @NonNull
+ public File getJarFile() {
+ return mJarFile;
+ }
+
+ public boolean isCompiled() {
+ return mCompiled;
+ }
+
+ public boolean isPackaged() {
+ return mPackaged;
+ }
+
+ public boolean isProguarded() {
+ return mProguarded;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/dependency/LibraryBundle.java b/build-system/builder/src/main/java/com/android/builder/dependency/LibraryBundle.java
new file mode 100644
index 0000000..f6c8ed4
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/dependency/LibraryBundle.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.dependency;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.google.common.base.Objects;
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Default implementation of the LibraryDependency interface that handles a default bundle project
+ * structure.
+ */
+public abstract class LibraryBundle implements LibraryDependency {
+
+ public static final String FN_PROGUARD_TXT = "proguard.txt";
+
+ private final String mName;
+ private final File mBundle;
+ private final File mBundleFolder;
+
+ /**
+ * Creates the bundle dependency with an optional name
+ *
+ * @param bundle the library's aar bundle file
+ * @param bundleFolder the folder containing the unarchived library content
+ * @param name an optional name
+ */
+ protected LibraryBundle(@NonNull File bundle,
+ @NonNull File bundleFolder,
+ @Nullable String name) {
+ mBundle = bundle;
+ mBundleFolder = bundleFolder;
+ mName = name;
+ }
+
+ protected LibraryBundle(@NonNull File bundle, @NonNull File bundleFolder) {
+ this(bundle, bundleFolder, null);
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ @Override
+ public String toString() {
+ return mName;
+ }
+
+ @Nullable
+ @Override
+ public String getProject() {
+ return null;
+ }
+
+ @Override
+ @NonNull
+ public File getManifest() {
+ return new File(mBundleFolder, SdkConstants.FN_ANDROID_MANIFEST_XML);
+ }
+
+ @Override
+ @NonNull
+ public File getSymbolFile() {
+ return new File(mBundleFolder, "R.txt");
+ }
+
+ @Override
+ @NonNull
+ public File getBundle() {
+ return mBundle;
+ }
+
+ @Override
+ @NonNull
+ public File getFolder() {
+ return mBundleFolder;
+ }
+
+ @Override
+ @NonNull
+ public File getJarFile() {
+ return new File(mBundleFolder, SdkConstants.FN_CLASSES_JAR);
+ }
+
+ @Override
+ @NonNull
+ public List<JarDependency> getLocalDependencies() {
+ List<File> jars = getLocalJars();
+ List<JarDependency> localDependencies = Lists.newArrayListWithCapacity(jars.size());
+ for (File jar : jars) {
+ localDependencies.add(new JarDependency(jar));
+ }
+
+ return localDependencies;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getLocalJars() {
+ List<File> localJars = Lists.newArrayList();
+ File[] jarList = new File(mBundleFolder, SdkConstants.LIBS_FOLDER).listFiles();
+ if (jarList != null) {
+ for (File jars : jarList) {
+ if (jars.isFile() && jars.getName().endsWith(".jar")) {
+ localJars.add(jars);
+ }
+ }
+ }
+
+ return localJars;
+ }
+
+ @Override
+ @NonNull
+ public File getResFolder() {
+ return new File(mBundleFolder, SdkConstants.FD_RES);
+ }
+
+ @Override
+ @NonNull
+ public File getAssetsFolder() {
+ return new File(mBundleFolder, SdkConstants.FD_ASSETS);
+ }
+
+ @Override
+ @NonNull
+ public File getJniFolder() {
+ return new File(mBundleFolder, "jni");
+ }
+
+ @Override
+ @NonNull
+ public File getAidlFolder() {
+ return new File(mBundleFolder, SdkConstants.FD_AIDL);
+ }
+
+ @Override
+ @NonNull
+ public File getRenderscriptFolder() {
+ return new File(mBundleFolder, SdkConstants.FD_RENDERSCRIPT);
+ }
+
+ @Override
+ @NonNull
+ public File getProguardRules() {
+ return new File(mBundleFolder, FN_PROGUARD_TXT);
+ }
+
+ @Override
+ @NonNull
+ public File getLintJar() {
+ return new File(mBundleFolder, "lint.jar");
+ }
+
+ @NonNull
+ public File getBundleFolder() {
+ return mBundleFolder;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ LibraryBundle that = (LibraryBundle) o;
+
+ return Objects.equal(mName, that.mName);
+ }
+
+ @Override
+ public int hashCode() {
+ return mName != null ? mName.hashCode() : 0;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/dependency/LibraryDependency.java b/build-system/builder/src/main/java/com/android/builder/dependency/LibraryDependency.java
new file mode 100644
index 0000000..3649588
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/dependency/LibraryDependency.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.dependency;
+
+import com.android.annotations.NonNull;
+import com.android.builder.model.AndroidLibrary;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Represents a dependency on a Library Project.
+ */
+public interface LibraryDependency extends AndroidLibrary, ManifestDependency, SymbolFileProvider {
+
+ /**
+ * Returns the direct dependency of this dependency. The order is important
+ */
+ @NonNull
+ List<LibraryDependency> getDependencies();
+
+ /**
+ * Returns the collection of local Jar files that are included in the dependency.
+ * @return a list of JarDependency. May be empty but not null.
+ */
+ @NonNull
+ Collection<JarDependency> getLocalDependencies();
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/dependency/ManifestDependency.java b/build-system/builder/src/main/java/com/android/builder/dependency/ManifestDependency.java
new file mode 100644
index 0000000..4618a5a
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/dependency/ManifestDependency.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.dependency;
+
+import com.android.annotations.NonNull;
+
+import java.util.List;
+
+/**
+ * Represents the manifest of a dependency as well as the dependencies
+ */
+public interface ManifestDependency extends ManifestProvider {
+
+ /**
+ * Returns the direct dependency of this dependency.
+ */
+ @NonNull
+ List<? extends ManifestDependency> getManifestDependencies();
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/dependency/ManifestProvider.java b/build-system/builder/src/main/java/com/android/builder/dependency/ManifestProvider.java
new file mode 100644
index 0000000..80e1676
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/dependency/ManifestProvider.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.dependency;
+
+import com.android.annotations.NonNull;
+
+import java.io.File;
+
+/**
+ * Provides a path to the Android Manifest
+ */
+public interface ManifestProvider {
+
+ /**
+ * Returns the location of the manifest.
+ */
+ @NonNull
+ File getManifest();
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/dependency/SymbolFileProvider.java b/build-system/builder/src/main/java/com/android/builder/dependency/SymbolFileProvider.java
new file mode 100644
index 0000000..bf77caf
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/dependency/SymbolFileProvider.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.dependency;
+
+import com.android.annotations.NonNull;
+
+import java.io.File;
+
+/**
+ * Provides a path to the Text Symbol file and to the Android Manifest
+ */
+public interface SymbolFileProvider extends ManifestProvider {
+
+ /**
+ * Returns the location of the text symbol file
+ */
+ @NonNull
+ File getSymbolFile();
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/BaseConfigImpl.java b/build-system/builder/src/main/java/com/android/builder/internal/BaseConfigImpl.java
new file mode 100644
index 0000000..62653d3
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/BaseConfigImpl.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.internal;
+
+import com.android.annotations.NonNull;
+import com.android.builder.model.BaseConfig;
+import com.android.builder.model.ClassField;
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * An object that contain a BuildConfig configuration
+ */
+public class BaseConfigImpl implements Serializable, BaseConfig {
+ private static final long serialVersionUID = 1L;
+
+ private final List<ClassField> mBuildConfigFields = Lists.newArrayList();
+ private final List<File> mProguardFiles = Lists.newArrayList();
+ private final List<File> mConsumerProguardFiles = Lists.newArrayList();
+
+ public void setBuildConfigFields(@NonNull ClassField... fields) {
+ mBuildConfigFields.clear();
+ mBuildConfigFields.addAll(Arrays.asList(fields));
+ }
+
+ public void setBuildConfigFields(@NonNull Collection<ClassField> fields) {
+ mBuildConfigFields.clear();
+ mBuildConfigFields.addAll(fields);
+ }
+
+ public void addBuildConfigField(@NonNull ClassField field) {
+ mBuildConfigFields.add(field);
+ }
+
+ @Override
+ @NonNull
+ public List<ClassField> getBuildConfigFields() {
+ return mBuildConfigFields;
+ }
+
+ @Override
+ @NonNull
+ public List<File> getProguardFiles() {
+ return mProguardFiles;
+ }
+
+ @Override
+ @NonNull
+ public List<File> getConsumerProguardFiles() {
+ return mConsumerProguardFiles;
+ }
+
+ protected void _initWith(BaseConfig that) {
+ setBuildConfigFields(that.getBuildConfigFields());
+
+ mProguardFiles.clear();
+ mProguardFiles.addAll(that.getProguardFiles());
+
+ mConsumerProguardFiles.clear();
+ mConsumerProguardFiles.addAll(that.getConsumerProguardFiles());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ BaseConfigImpl that = (BaseConfigImpl) o;
+
+ if (!mBuildConfigFields.equals(that.mBuildConfigFields)) return false;
+ if (!mProguardFiles.equals(that.mProguardFiles)) return false;
+ if (!mConsumerProguardFiles.equals(that.mConsumerProguardFiles)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mBuildConfigFields.hashCode();
+ result = 31 * result + mProguardFiles.hashCode();
+ result = 31 * result + mConsumerProguardFiles.hashCode();
+ return result;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/ClassFieldImpl.java b/build-system/builder/src/main/java/com/android/builder/internal/ClassFieldImpl.java
new file mode 100644
index 0000000..44824b4
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/ClassFieldImpl.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.internal;
+
+import com.android.annotations.NonNull;
+import com.android.builder.model.ClassField;
+
+import java.io.Serializable;
+
+/**
+ */
+public final class ClassFieldImpl implements ClassField, Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @NonNull
+ private final String type;
+ @NonNull
+ private final String name;
+ @NonNull
+ private final String value;
+
+ public ClassFieldImpl(@NonNull String type, @NonNull String name, @NonNull String value) {
+ //noinspection ConstantConditions
+ if (type == null || name == null || value == null) {
+ throw new NullPointerException("Build Config field cannot have a null parameter");
+ }
+ this.type = type;
+ this.name = name;
+ this.value = value;
+ }
+
+ @Override
+ @NonNull
+ public String getType() {
+ return type;
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ @NonNull
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ ClassFieldImpl that = (ClassFieldImpl) o;
+
+ if (!name.equals(that.name)) return false;
+ if (!type.equals(that.type)) return false;
+ if (!value.equals(that.value)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = type.hashCode();
+ result = 31 * result + name.hashCode();
+ result = 31 * result + value.hashCode();
+ return result;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/FakeAndroidTarget.java b/build-system/builder/src/main/java/com/android/builder/internal/FakeAndroidTarget.java
new file mode 100644
index 0000000..434b8d2
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/FakeAndroidTarget.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.internal;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.ISystemImage;
+import com.android.utils.SparseArray;
+import com.google.common.collect.Lists;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Fake IAndroidTarget used for SDK prebuilts in the Android source tree.
+ */
+public class FakeAndroidTarget implements IAndroidTarget {
+ private final String mSdkLocation;
+ private final SparseArray<String> mPaths = new SparseArray<String>();
+ private final List<String> mBootClasspath = Lists.newArrayListWithExpectedSize(2);
+ private final int mApiLevel;
+
+ public FakeAndroidTarget(String sdkLocation, String target) {
+ mSdkLocation = sdkLocation;
+ mApiLevel = getApiLevel(target);
+
+ if ("unstubbed".equals(target)) {
+ mBootClasspath.add(mSdkLocation +
+ "/out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar");
+ mBootClasspath.add(mSdkLocation +
+ "/out/target/common/obj/JAVA_LIBRARIES/core_intermediates/classes.jar");
+
+ // pre-build the path to the platform components
+ mPaths.put(ANDROID_JAR, mSdkLocation + "/prebuilts/sdk/current/" +
+ SdkConstants.FN_FRAMEWORK_LIBRARY);
+ mPaths.put(ANDROID_AIDL, mSdkLocation + "/prebuilts/sdk/renderscript/" +
+ SdkConstants.FN_FRAMEWORK_AIDL);
+ } else {
+ String apiPrebuilts;
+
+ if ("current".equals(target)) {
+ apiPrebuilts = mSdkLocation + "/prebuilts/sdk/current/";
+ } else {
+ apiPrebuilts = mSdkLocation + "/prebuilts/sdk/" + Integer.toString(mApiLevel) + "/";
+ }
+
+ // pre-build the path to the platform components
+ mBootClasspath.add(apiPrebuilts + SdkConstants.FN_FRAMEWORK_LIBRARY);
+ mPaths.put(ANDROID_JAR, apiPrebuilts + SdkConstants.FN_FRAMEWORK_LIBRARY);
+ mPaths.put(ANDROID_AIDL, apiPrebuilts + SdkConstants.FN_FRAMEWORK_AIDL);
+ }
+ }
+
+ private int getApiLevel(String target) {
+ if (target.startsWith("android-")) {
+ return Integer.parseInt(target.substring("android-".length()));
+ }
+
+ // We don't actually know the API level at this point since the mode is "current"
+ // or "unstubbed". This API is only called to check if annotations.jar needs to be
+ // added to the classpath, so by putting a large value we make sure annotations.jar
+ // isn't used.
+ return 99;
+ }
+
+ @Override
+ public String getPath(int pathId) {
+ return mPaths.get(pathId);
+ }
+
+ @Override
+ public BuildToolInfo getBuildToolInfo() {
+ // this is not used internally since we properly query for the right Build Tools from
+ // the SdkManager.
+ return null;
+ }
+
+ @Override @NonNull
+ public List<String> getBootClasspath() {
+ return mBootClasspath;
+ }
+
+ @Override
+ public String getLocation() {
+ return mSdkLocation;
+ }
+
+ @Override
+ public String getVendor() {
+ return "android";
+ }
+
+ @Override
+ public String getName() {
+ return "android";
+ }
+
+ @Override
+ public String getFullName() {
+ return "android";
+ }
+
+ @Override
+ public String getClasspathName() {
+ return "android";
+ }
+
+ @Override
+ public String getShortClasspathName() {
+ return "android";
+ }
+
+ @Override
+ public String getDescription() {
+ return "android";
+ }
+
+ @Override
+ public AndroidVersion getVersion() {
+ return new AndroidVersion(mApiLevel, null);
+ }
+
+ @Override
+ public String getVersionName() {
+ return "Android API level " + mApiLevel;
+ }
+
+ @Override
+ public int getRevision() {
+ return 1;
+ }
+
+ @Override
+ public boolean isPlatform() {
+ return true;
+ }
+
+ @Override
+ public IAndroidTarget getParent() {
+ return null;
+ }
+
+ @Override
+ public boolean hasRenderingLibrary() {
+ return false;
+ }
+
+ @Override
+ public String[] getSkins() {
+ return new String[0];
+ }
+
+ @Override
+ public String getDefaultSkin() {
+ return null;
+ }
+
+ @Override
+ public IOptionalLibrary[] getOptionalLibraries() {
+ return new IOptionalLibrary[0];
+ }
+
+ @Override
+ public String[] getPlatformLibraries() {
+ return new String[0];
+ }
+
+ @Override
+ public String getProperty(String name) {
+ return null;
+ }
+
+ @Override
+ public Integer getProperty(String name, Integer defaultValue) {
+ return null;
+ }
+
+ @Override
+ public Boolean getProperty(String name, Boolean defaultValue) {
+ return null;
+ }
+
+ @Override
+ public Map<String, String> getProperties() {
+ return null;
+ }
+
+ @Override
+ public int getUsbVendorId() {
+ return 0;
+ }
+
+ @Override
+ public ISystemImage[] getSystemImages() {
+ return new ISystemImage[0];
+ }
+
+ @Override
+ public ISystemImage getSystemImage(String abiType) {
+ return null;
+ }
+
+ @Override
+ public boolean canRunOn(IAndroidTarget target) {
+ return false;
+ }
+
+ @Override
+ public String hashString() {
+ return "android-" + mApiLevel;
+ }
+
+ @Override
+ public int compareTo(IAndroidTarget iAndroidTarget) {
+ FakeAndroidTarget that = (FakeAndroidTarget) iAndroidTarget;
+ return mSdkLocation.compareTo(that.mSdkLocation);
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/MergedNdkConfig.java b/build-system/builder/src/main/java/com/android/builder/internal/MergedNdkConfig.java
new file mode 100644
index 0000000..bd83e99
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/MergedNdkConfig.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.internal;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.NdkConfig;
+import com.google.common.collect.Sets;
+
+import java.util.Set;
+
+/**
+ * Implementation of NdkConfig used to merge multiple configs together.
+ */
+public class MergedNdkConfig implements NdkConfig {
+
+ private String moduleName;
+ private String cFlags;
+ private Set<String> ldLibs;
+ private Set<String> abiFilters;
+ private String stl;
+
+ public void reset() {
+ moduleName = null;
+ cFlags = null;
+ ldLibs = null;
+ abiFilters = null;
+ }
+
+ @Override
+ @Nullable
+ public String getModuleName() {
+ return moduleName;
+ }
+
+ @Override
+ @Nullable
+ public String getcFlags() {
+ return cFlags;
+ }
+
+ @Override
+ @Nullable
+ public Set<String> getLdLibs() {
+ return ldLibs;
+ }
+
+ @Override
+ @Nullable
+ public Set<String> getAbiFilters() {
+ return abiFilters;
+ }
+
+ @Override
+ @Nullable
+ public String getStl() {
+ return stl;
+ }
+
+ public void append(@NonNull NdkConfig ndkConfig) {
+ // override
+ if (ndkConfig.getModuleName() != null) {
+ moduleName = ndkConfig.getModuleName();
+ }
+
+ if (ndkConfig.getStl() != null) {
+ stl = ndkConfig.getStl();
+ }
+
+ // append
+ if (ndkConfig.getAbiFilters() != null) {
+ if (abiFilters == null) {
+ abiFilters = Sets.newHashSetWithExpectedSize(ndkConfig.getAbiFilters().size());
+ } else {
+ abiFilters.clear();
+ }
+ abiFilters.addAll(ndkConfig.getAbiFilters());
+ }
+
+ if (cFlags == null) {
+ cFlags = ndkConfig.getcFlags();
+ } else if (ndkConfig.getcFlags() != null) {
+ cFlags = cFlags + " " + ndkConfig.getcFlags();
+ }
+
+ if (ndkConfig.getLdLibs() != null) {
+ if (ldLibs == null) {
+ ldLibs = Sets.newHashSetWithExpectedSize(ndkConfig.getLdLibs().size());
+ } else {
+ ldLibs.clear();
+ }
+ ldLibs.addAll(ndkConfig.getLdLibs());
+ }
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/StringHelper.java b/build-system/builder/src/main/java/com/android/builder/internal/StringHelper.java
new file mode 100644
index 0000000..4a59a82
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/StringHelper.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.internal;
+
+import com.android.annotations.NonNull;
+
+import java.util.Locale;
+
+/**
+ */
+public class StringHelper {
+
+ @NonNull
+ public static String capitalize(@NonNull String string) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(string.substring(0, 1).toUpperCase(Locale.US)).append(string.substring(1));
+
+ return sb.toString();
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/SymbolLoader.java b/build-system/builder/src/main/java/com/android/builder/internal/SymbolLoader.java
new file mode 100644
index 0000000..a9396c8
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/SymbolLoader.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.internal;
+
+import com.android.utils.ILogger;
+import com.google.common.base.Charsets;
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.Table;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * A class to load the text symbol file generated by aapt with the
+ * --output-text-symbols option.
+ */
+public class SymbolLoader {
+
+ private final File mSymbolFile;
+ private Table<String, String, SymbolEntry> mSymbols;
+ private final ILogger mLogger;
+
+ public static class SymbolEntry {
+ private final String mName;
+ private final String mType;
+ private final String mValue;
+
+ public SymbolEntry(String name, String type, String value) {
+ mName = name;
+ mType = type;
+ mValue = value;
+ }
+
+ public String getValue() {
+ return mValue;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public String getType() {
+ return mType;
+ }
+ }
+
+ public SymbolLoader(File symbolFile, ILogger logger) {
+ mSymbolFile = symbolFile;
+ mLogger = logger;
+ }
+
+ public void load() throws IOException {
+ List<String> lines = Files.readLines(mSymbolFile, Charsets.UTF_8);
+
+ mSymbols = HashBasedTable.create();
+
+ int lineIndex = 1;
+ String line = null;
+ try {
+ final int count = lines.size();
+ for (; lineIndex <= count ; lineIndex++) {
+ line = lines.get(lineIndex-1);
+
+ // format is "<type> <class> <name> <value>"
+ // don't want to split on space as value could contain spaces.
+ int pos = line.indexOf(' ');
+ String type = line.substring(0, pos);
+ int pos2 = line.indexOf(' ', pos + 1);
+ String className = line.substring(pos + 1, pos2);
+ int pos3 = line.indexOf(' ', pos2 + 1);
+ String name = line.substring(pos2 + 1, pos3);
+ String value = line.substring(pos3 + 1);
+
+ mSymbols.put(className, name, new SymbolEntry(name, type, value));
+ }
+ } catch (IndexOutOfBoundsException e) {
+ String s = String.format("File format error reading %s\tline %d: '%s'",
+ mSymbolFile.getAbsolutePath(), lineIndex, line);
+ mLogger.error(null, s);
+ throw new IOException(s, e);
+ }
+ }
+
+ Table<String, String, SymbolEntry> getSymbols() {
+ return mSymbols;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/SymbolWriter.java b/build-system/builder/src/main/java/com/android/builder/internal/SymbolWriter.java
new file mode 100644
index 0000000..a45f650
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/SymbolWriter.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.internal;
+
+import com.android.SdkConstants;
+import com.android.builder.internal.SymbolLoader.SymbolEntry;
+import com.google.common.base.Charsets;
+import com.google.common.base.Splitter;
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Table;
+import com.google.common.io.Closeables;
+import com.google.common.io.Files;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A class to write R.java classes based on data read from text symbol files generated by
+ * aapt with the --output-text-symbols option.
+ */
+public class SymbolWriter {
+
+ private final String mOutFolder;
+ private final String mPackageName;
+ private final List<SymbolLoader> mSymbols = Lists.newArrayList();
+ private final SymbolLoader mValues;
+
+ public SymbolWriter(String outFolder, String packageName, SymbolLoader values) {
+ mOutFolder = outFolder;
+ mPackageName = packageName;
+ mValues = values;
+ }
+
+ public void addSymbolsToWrite(SymbolLoader symbols) {
+ mSymbols.add(symbols);
+ }
+
+ private Table<String, String, SymbolEntry> getAllSymbols() {
+ Table<String, String, SymbolEntry> symbols = HashBasedTable.create();
+
+ for (SymbolLoader symbolLoader : mSymbols) {
+ symbols.putAll(symbolLoader.getSymbols());
+ }
+
+ return symbols;
+ }
+
+ public void write() throws IOException {
+ Splitter splitter = Splitter.on('.');
+ Iterable<String> folders = splitter.split(mPackageName);
+ File file = new File(mOutFolder);
+ for (String folder : folders) {
+ file = new File(file, folder);
+ }
+ file.mkdirs();
+ file = new File(file, SdkConstants.FN_RESOURCE_CLASS);
+
+ BufferedWriter writer = null;
+ try {
+ writer = Files.newWriter(file, Charsets.UTF_8);
+
+ writer.write("/* AUTO-GENERATED FILE. DO NOT MODIFY.\n");
+ writer.write(" *\n");
+ writer.write(" * This class was automatically generated by the\n");
+ writer.write(" * aapt tool from the resource data it found. It\n");
+ writer.write(" * should not be modified by hand.\n");
+ writer.write(" */\n");
+
+ writer.write("package ");
+ writer.write(mPackageName);
+ writer.write(";\n\npublic final class R {\n");
+
+ Table<String, String, SymbolEntry> symbols = getAllSymbols();
+ Table<String, String, SymbolEntry> values = mValues.getSymbols();
+
+ Set<String> rowSet = symbols.rowKeySet();
+ List<String> rowList = Lists.newArrayList(rowSet);
+ Collections.sort(rowList);
+
+ for (String row : rowList) {
+ writer.write("\tpublic static final class ");
+ writer.write(row);
+ writer.write(" {\n");
+
+ Map<String, SymbolEntry> rowMap = symbols.row(row);
+ Set<String> symbolSet = rowMap.keySet();
+ ArrayList<String> symbolList = Lists.newArrayList(symbolSet);
+ Collections.sort(symbolList);
+
+ for (String symbolName : symbolList) {
+ // get the matching SymbolEntry from the values Table.
+ SymbolEntry value = values.get(row, symbolName);
+ if (value != null) {
+ writer.write("\t\tpublic static final ");
+ writer.write(value.getType());
+ writer.write(" ");
+ writer.write(value.getName());
+ writer.write(" = ");
+ writer.write(value.getValue());
+ writer.write(";\n");
+ }
+ }
+
+ writer.write("\t}\n");
+ }
+
+ writer.write("}\n");
+ } finally {
+ Closeables.closeQuietly(writer);
+ }
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/TemplateProcessor.java b/build-system/builder/src/main/java/com/android/builder/internal/TemplateProcessor.java
new file mode 100644
index 0000000..d489652
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/TemplateProcessor.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.internal;
+
+import com.android.annotations.NonNull;
+import com.google.common.base.Charsets;
+import com.google.common.io.CharStreams;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Processes a template to generate a file somewhere.
+ */
+class TemplateProcessor {
+
+ private final InputStream mTemplateStream;
+ private final Map<String, String> mPlaceHolderMap;
+
+ /**
+ * Creates a processor
+ * @param templateStream the stream to read the template file from
+ * @param placeHolderMap
+ */
+ public TemplateProcessor(@NonNull InputStream templateStream,
+ @NonNull Map<String, String> placeHolderMap) {
+ mTemplateStream = checkNotNull(templateStream);
+ mPlaceHolderMap = checkNotNull(placeHolderMap);
+ }
+
+ /**
+ * Generates the file from the template.
+ * @param outputFile the file to create
+ */
+ public void generate(File outputFile) throws IOException {
+ String template = readEmbeddedTextFile(mTemplateStream);
+
+ String content = replaceParameters(template, mPlaceHolderMap);
+
+ writeFile(outputFile, content);
+ }
+
+ /**
+ * Reads and returns the content of a text file embedded in the jar file.
+ * @param templateStream the stream to read the template file from
+ * @return null if the file could not be read
+ * @throws java.io.IOException
+ */
+ private String readEmbeddedTextFile(InputStream templateStream) throws IOException {
+ InputStreamReader reader = new InputStreamReader(templateStream, Charsets.UTF_8);
+
+ try {
+ return CharStreams.toString(reader);
+ } finally {
+ reader.close();
+ }
+ }
+
+ private void writeFile(File file, String content) throws IOException {
+ Files.write(content, file, Charsets.UTF_8);
+ }
+
+ /**
+ * Replaces placeholders found in a string with values.
+ *
+ * @param str the string to search for placeholders.
+ * @param parameters a map of <placeholder, Value> to search for in the string
+ * @return A new String object with the placeholder replaced by the values.
+ */
+ private String replaceParameters(String str, Map<String, String> parameters) {
+
+ for (Entry<String, String> entry : parameters.entrySet()) {
+ String value = entry.getValue();
+ if (value != null) {
+ str = str.replaceAll(entry.getKey(), value);
+ }
+ }
+
+ return str;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/TestManifestGenerator.java b/build-system/builder/src/main/java/com/android/builder/internal/TestManifestGenerator.java
new file mode 100644
index 0000000..9e443a7
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/TestManifestGenerator.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.builder.internal;
+
+import com.android.annotations.NonNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Generate an AndroidManifest.xml file for test projects.
+ */
+public class TestManifestGenerator {
+
+ private final static String TEMPLATE = "AndroidManifest.template";
+ private final static String PH_PACKAGE = "#PACKAGE#";
+ private final static String PH_MIN_SDK_VERSION = "#MINSDKVERSION#";
+ private final static String PH_TARGET_SDK_VERSION = "#TARGETSDKVERSION#";
+ private final static String PH_TESTED_PACKAGE = "#TESTEDPACKAGE#";
+ private final static String PH_TEST_RUNNER = "#TESTRUNNER#";
+ private final static String PH_HANDLE_PROFILING = "#HANDLEPROFILING#";
+ private final static String PH_FUNCTIONAL_TEST = "#FUNCTIONALTEST#";
+
+ private final String mOutputFile;
+ private final String mPackageName;
+ private final int mMinSdkVersion;
+ private final int mTargetSdkVersion;
+ private final String mTestedPackageName;
+ private final String mTestRunnerName;
+ private final boolean mHandleProfiling;
+ private final boolean mFunctionalTest;
+
+ public TestManifestGenerator(@NonNull String outputFile,
+ @NonNull String packageName,
+ int minSdkVersion,
+ int targetSdkVersion,
+ @NonNull String testedPackageName,
+ @NonNull String testRunnerName,
+ @NonNull Boolean handleProfiling,
+ @NonNull Boolean functionalTest) {
+ mOutputFile = outputFile;
+ mPackageName = packageName;
+ mMinSdkVersion = minSdkVersion;
+ mTargetSdkVersion = targetSdkVersion != -1 ? targetSdkVersion : minSdkVersion;
+ mTestedPackageName = testedPackageName;
+ mTestRunnerName = testRunnerName;
+ mHandleProfiling = handleProfiling;
+ mFunctionalTest = functionalTest;
+ }
+
+ public void generate() throws IOException {
+ Map<String, String> map = new HashMap<String, String>();
+ map.put(PH_PACKAGE, mPackageName);
+ map.put(PH_MIN_SDK_VERSION, Integer.toString(mMinSdkVersion));
+ map.put(PH_TARGET_SDK_VERSION, Integer.toString(mTargetSdkVersion));
+ map.put(PH_TESTED_PACKAGE, mTestedPackageName);
+ map.put(PH_TEST_RUNNER, mTestRunnerName);
+ map.put(PH_HANDLE_PROFILING, Boolean.toString(mHandleProfiling));
+ map.put(PH_FUNCTIONAL_TEST, Boolean.toString(mFunctionalTest));
+
+ TemplateProcessor processor = new TemplateProcessor(
+ TestManifestGenerator.class.getResourceAsStream(TEMPLATE),
+ map);
+
+ processor.generate(new File(mOutputFile));
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/compiler/AidlProcessor.java b/build-system/builder/src/main/java/com/android/builder/internal/compiler/AidlProcessor.java
new file mode 100644
index 0000000..a50029e
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/compiler/AidlProcessor.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.internal.compiler;
+
+import com.android.annotations.NonNull;
+import com.android.builder.compiling.DependencyFileProcessor;
+import com.android.ide.common.internal.CommandLineRunner;
+import com.android.ide.common.internal.LoggedErrorException;
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A Source File processor for AIDL files. This compiles each aidl file found by the SourceSearcher.
+ */
+public class AidlProcessor implements SourceSearcher.SourceFileProcessor {
+
+ @NonNull
+ private final String mAidlExecutable;
+ @NonNull
+ private final String mFrameworkLocation;
+ @NonNull
+ private final List<File> mImportFolders;
+ @NonNull
+ private final File mSourceOutputDir;
+ @NonNull
+ private final DependencyFileProcessor mDependencyFileProcessor;
+ @NonNull
+ private final CommandLineRunner mRunner;
+
+ public AidlProcessor(@NonNull String aidlExecutable,
+ @NonNull String frameworkLocation,
+ @NonNull List<File> importFolders,
+ @NonNull File sourceOutputDir,
+ @NonNull DependencyFileProcessor dependencyFileProcessor,
+ @NonNull CommandLineRunner runner) {
+ mAidlExecutable = aidlExecutable;
+ mFrameworkLocation = frameworkLocation;
+ mImportFolders = importFolders;
+ mSourceOutputDir = sourceOutputDir;
+ mDependencyFileProcessor = dependencyFileProcessor;
+ mRunner = runner;
+ }
+
+ @Override
+ public void processFile(File sourceFile) throws IOException, InterruptedException, LoggedErrorException {
+ ArrayList<String> command = Lists.newArrayList();
+
+ command.add(mAidlExecutable);
+
+ command.add("-p" + mFrameworkLocation);
+ command.add("-o" + mSourceOutputDir.getAbsolutePath());
+
+ // add all the library aidl folders to access parcelables that are in libraries
+ for (File f : mImportFolders) {
+ command.add("-I" + f.getAbsolutePath());
+ }
+
+ // create a temp file for the dependency
+ File depFile = File.createTempFile("aidl", ".d");
+ command.add("-d" + depFile.getAbsolutePath());
+
+ command.add(sourceFile.getAbsolutePath());
+
+ mRunner.runCmdLine(command, null);
+
+ // send the dependency file to the processor.
+ if (mDependencyFileProcessor.processFile(depFile)) {
+ depFile.delete();
+ }
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/compiler/FileGatherer.java b/build-system/builder/src/main/java/com/android/builder/internal/compiler/FileGatherer.java
new file mode 100644
index 0000000..1028617
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/compiler/FileGatherer.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.internal.compiler;
+
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Source Searcher processor, gathering a list of all the files found by the SourceSearcher.
+ */
+public class FileGatherer implements SourceSearcher.SourceFileProcessor {
+ private final List<File> mFiles = Lists.newArrayList();
+
+ @Override
+ public void processFile(File sourceFile) throws IOException, InterruptedException {
+ mFiles.add(sourceFile);
+ }
+
+ public List<File> getFiles() {
+ return mFiles;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/compiler/LeafFolderGatherer.java b/build-system/builder/src/main/java/com/android/builder/internal/compiler/LeafFolderGatherer.java
new file mode 100644
index 0000000..42d2bf3
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/compiler/LeafFolderGatherer.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.internal.compiler;
+
+import com.android.ide.common.internal.LoggedErrorException;
+import com.google.common.collect.Sets;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Set;
+
+/**
+ * Source Searcher processor, gathering a list of folders containing processed source files.
+ */
+public class LeafFolderGatherer implements SourceSearcher.SourceFileProcessor {
+
+ private final Set<File> mFolders = Sets.newHashSet();
+
+ @Override
+ public void processFile(File sourceFile) throws IOException, InterruptedException, LoggedErrorException {
+ mFolders.add(sourceFile.getParentFile());
+ }
+
+ public Set<File> getFolders() {
+ return mFolders;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/compiler/RenderScriptProcessor.java b/build-system/builder/src/main/java/com/android/builder/internal/compiler/RenderScriptProcessor.java
new file mode 100644
index 0000000..9b28743
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/compiler/RenderScriptProcessor.java
@@ -0,0 +1,397 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.builder.internal.compiler;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.internal.CommandLineRunner;
+import com.android.ide.common.internal.LoggedErrorException;
+import com.android.ide.common.internal.WaitableExecutor;
+import com.android.sdklib.BuildToolInfo;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+import static com.android.SdkConstants.EXT_BC;
+import static com.android.SdkConstants.FN_RENDERSCRIPT_V8_JAR;
+
+/**
+ * Compiles Renderscript files.
+ */
+public class RenderScriptProcessor {
+
+ // ABI list, as pairs of (android-ABI, toolchain-ABI)
+ private static final class Abi {
+
+ @NonNull
+ private final String mDevice;
+ @NonNull
+ private final String mToolchain;
+ @NonNull
+ private final BuildToolInfo.PathId mLinker;
+ @NonNull
+ private final String[] mLinkerArgs;
+
+ Abi(@NonNull String device,
+ @NonNull String toolchain,
+ @NonNull BuildToolInfo.PathId linker,
+ @NonNull String... linkerArgs) {
+
+ mDevice = device;
+ mToolchain = toolchain;
+ mLinker = linker;
+ mLinkerArgs = linkerArgs;
+ }
+ }
+
+ private static final Abi[] ABIS = {
+ new Abi("armeabi-v7a", "armv7-none-linux-gnueabi", BuildToolInfo.PathId.LD_ARM,
+ "-dynamic-linker", "/system/bin/linker", "-X", "-m", "armelf_linux_eabi"),
+ new Abi("mips", "mipsel-unknown-linux", BuildToolInfo.PathId.LD_MIPS, "-EL"),
+ new Abi("x86", "i686-unknown-linux", BuildToolInfo.PathId.LD_X86, "-m", "elf_i386") };
+
+ public static final String RS_DEPS = "rsDeps";
+
+ @NonNull
+ private final List<File> mSourceFolders;
+
+ @NonNull
+ private final List<File> mImportFolders;
+
+ @NonNull
+ private final File mSourceOutputDir;
+
+ @NonNull
+ private final File mResOutputDir;
+
+ @NonNull
+ private final File mObjOutputDir;
+
+ @NonNull
+ private final File mLibOutputDir;
+
+ @NonNull
+ private final BuildToolInfo mBuildToolInfo;
+
+ private final int mTargetApi;
+
+ private final boolean mDebugBuild;
+
+ private final int mOptimLevel;
+
+ private final boolean mNdkMode;
+
+ private final boolean mSupportMode;
+ private final Set<String> mAbiFilters;
+
+ private final File mRsLib;
+ private final File mLibClCore;
+
+ public RenderScriptProcessor(
+ @NonNull List<File> sourceFolders,
+ @NonNull List<File> importFolders,
+ @NonNull File sourceOutputDir,
+ @NonNull File resOutputDir,
+ @NonNull File objOutputDir,
+ @NonNull File libOutputDir,
+ @NonNull BuildToolInfo buildToolInfo,
+ int targetApi,
+ boolean debugBuild,
+ int optimLevel,
+ boolean ndkMode,
+ boolean supportMode,
+ @Nullable Set<String> abiFilters) {
+ mSourceFolders = sourceFolders;
+ mImportFolders = importFolders;
+ mSourceOutputDir = sourceOutputDir;
+ mResOutputDir = resOutputDir;
+ mObjOutputDir = objOutputDir;
+ mLibOutputDir = libOutputDir;
+ mBuildToolInfo = buildToolInfo;
+ mTargetApi = targetApi;
+ mDebugBuild = debugBuild;
+ mOptimLevel = optimLevel;
+ mNdkMode = ndkMode;
+ mSupportMode = supportMode;
+ mAbiFilters = abiFilters;
+
+ if (supportMode) {
+ File rs = new File(mBuildToolInfo.getLocation(), "renderscript");
+ mRsLib = new File(rs, "lib");
+ mLibClCore = new File(mRsLib, "libclcore.bc");
+ } else {
+ mLibClCore = null;
+ mRsLib = null;
+ }
+ }
+
+ public static File getSupportJar(String buildToolsFolder) {
+ return new File(buildToolsFolder, "renderscript/lib/" + FN_RENDERSCRIPT_V8_JAR);
+ }
+
+ public static File getSupportNativeLibFolder(String buildToolsFolder) {
+ File rs = new File(buildToolsFolder, "renderscript");
+ File lib = new File(rs, "lib");
+ return new File(lib, "packaged");
+ }
+
+ public void build(@NonNull CommandLineRunner launcher)
+ throws IOException, InterruptedException, LoggedErrorException {
+
+ // gather the files to compile
+ FileGatherer fileGatherer = new FileGatherer();
+ SourceSearcher searcher = new SourceSearcher(mSourceFolders, "rs", "fs");
+ searcher.setUseExecutor(false);
+ searcher.search(fileGatherer);
+
+ List<File> renderscriptFiles = fileGatherer.getFiles();
+
+ if (renderscriptFiles.isEmpty()) {
+ return;
+ }
+
+ // get the env var
+ Map<String, String> env = Maps.newHashMap();
+ if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) {
+ env.put("DYLD_LIBRARY_PATH", mBuildToolInfo.getLocation().getAbsolutePath());
+ } else if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_LINUX) {
+ env.put("LD_LIBRARY_PATH", mBuildToolInfo.getLocation().getAbsolutePath());
+ }
+
+ doMainCompilation(renderscriptFiles, launcher, env);
+
+ if (mSupportMode) {
+ createSupportFiles(launcher, env);
+ }
+ }
+
+ private void doMainCompilation(
+ @NonNull List<File> inputFiles,
+ @NonNull CommandLineRunner launcher,
+ @NonNull Map<String, String> env)
+ throws IOException, InterruptedException, LoggedErrorException {
+
+ String renderscript = mBuildToolInfo.getPath(BuildToolInfo.PathId.LLVM_RS_CC);
+ if (renderscript == null || !new File(renderscript).isFile()) {
+ throw new IllegalStateException(BuildToolInfo.PathId.LLVM_RS_CC + " is missing");
+ }
+
+ String rsPath = mBuildToolInfo.getPath(BuildToolInfo.PathId.ANDROID_RS);
+ String rsClangPath = mBuildToolInfo.getPath(BuildToolInfo.PathId.ANDROID_RS_CLANG);
+
+ // the renderscript compiler doesn't expect the top res folder,
+ // but the raw folder directly.
+ File rawFolder = new File(mResOutputDir, SdkConstants.FD_RES_RAW);
+
+ // compile all the files in a single pass
+ ArrayList<String> command = Lists.newArrayListWithExpectedSize(26);
+
+ command.add(renderscript);
+
+ // Due to a device side bug, let's not enable this at this time.
+// if (mDebugBuild) {
+// command.add("-g");
+// }
+
+ command.add("-O");
+ command.add(Integer.toString(mOptimLevel));
+
+ // add all import paths
+ command.add("-I");
+ command.add(rsPath);
+ command.add("-I");
+ command.add(rsClangPath);
+
+ for (File importPath : mImportFolders) {
+ if (importPath.isDirectory()) {
+ command.add("-I");
+ command.add(importPath.getAbsolutePath());
+ }
+ }
+
+ if (mSupportMode) {
+ command.add("-rs-package-name=android.support.v8.renderscript");
+ }
+
+ // source output
+ command.add("-p");
+ command.add(mSourceOutputDir.getAbsolutePath());
+
+ if (mNdkMode) {
+ command.add("-reflect-c++");
+ }
+
+ // res output
+ command.add("-o");
+ command.add(rawFolder.getAbsolutePath());
+
+ command.add("-target-api");
+ int targetApi = mTargetApi < 11 ? 11 : mTargetApi;
+ targetApi = (mSupportMode && targetApi < 18) ? 18 : targetApi;
+ command.add(Integer.toString(targetApi));
+
+ // input files
+ for (File sourceFile : inputFiles) {
+ command.add(sourceFile.getAbsolutePath());
+ }
+
+ launcher.runCmdLine(command, env);
+ }
+
+ private void createSupportFiles(@NonNull final CommandLineRunner launcher,
+ @NonNull final Map<String, String> env)
+ throws IOException, InterruptedException, LoggedErrorException {
+ // get the generated BC files.
+ File rawFolder = new File(mResOutputDir, SdkConstants.FD_RES_RAW);
+
+ SourceSearcher searcher = new SourceSearcher(
+ Collections.singletonList(rawFolder), EXT_BC);
+ FileGatherer fileGatherer = new FileGatherer();
+ searcher.search(fileGatherer);
+
+ WaitableExecutor<Void> mExecutor = new WaitableExecutor<Void>();
+
+ for (final File bcFile : fileGatherer.getFiles()) {
+ String name = bcFile.getName();
+ final String objName = name.replaceAll("\\.bc", ".o");
+ final String soName = "librs." + name.replaceAll("\\.bc", ".so");
+
+ for (final Abi abi : ABIS) {
+ if (mAbiFilters != null && !mAbiFilters.contains(abi.mDevice)) {
+ continue;
+ }
+
+ // make sure the dest folders exist
+ final File objAbiFolder = new File(mObjOutputDir, abi.mDevice);
+ if (!objAbiFolder.isDirectory() && !objAbiFolder.mkdirs()) {
+ throw new IOException("Unable to create dir " + objAbiFolder.getAbsolutePath());
+ }
+
+ final File libAbiFolder = new File(mLibOutputDir, abi.mDevice);
+ if (!libAbiFolder.isDirectory() && !libAbiFolder.mkdirs()) {
+ throw new IOException("Unable to create dir " + libAbiFolder.getAbsolutePath());
+ }
+
+ mExecutor.execute(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ File objFile = createSupportObjFile(bcFile, abi, objName, objAbiFolder,
+ launcher, env);
+ createSupportLibFile(objFile, abi, soName, libAbiFolder, launcher, env);
+ return null;
+ }
+ });
+ }
+ }
+
+ mExecutor.waitForTasksWithQuickFail(true /*cancelRemaining*/);
+ }
+
+ private File createSupportObjFile(
+ @NonNull File bcFile,
+ @NonNull Abi abi,
+ @NonNull String objName,
+ @NonNull File objAbiFolder,
+ @NonNull CommandLineRunner launcher,
+ @NonNull Map<String, String> env)
+ throws IOException, InterruptedException, LoggedErrorException {
+
+ List<String> args = Lists.newArrayListWithExpectedSize(10);
+
+ args.add(mBuildToolInfo.getPath(BuildToolInfo.PathId.BCC_COMPAT));
+
+ args.add("-O" + Integer.toString(mOptimLevel));
+
+ File outFile = new File(objAbiFolder, objName);
+ args.add("-o");
+ args.add(outFile.getAbsolutePath());
+
+ args.add("-fPIC");
+ args.add("-shared");
+
+ args.add("-rt-path");
+ args.add(mLibClCore.getAbsolutePath());
+
+ args.add("-mtriple");
+ args.add(abi.mToolchain);
+
+ args.add(bcFile.getAbsolutePath());
+
+ launcher.runCmdLine(args, env);
+
+ return outFile;
+ }
+
+ private void createSupportLibFile(
+ @NonNull File objFile,
+ @NonNull Abi abi,
+ @NonNull String soName,
+ @NonNull File libAbiFolder,
+ @NonNull CommandLineRunner launcher,
+ @NonNull Map<String, String> env)
+ throws IOException, InterruptedException, LoggedErrorException {
+
+ File intermediatesFolder = new File(mRsLib, "intermediates");
+ File intermediatesAbiFolder = new File(intermediatesFolder, abi.mDevice);
+ File packagedFolder = new File(mRsLib, "packaged");
+ File packagedAbiFolder = new File(packagedFolder, abi.mDevice);
+
+ List<String> args = Lists.newArrayListWithExpectedSize(26);
+
+ args.add(mBuildToolInfo.getPath(abi.mLinker));
+
+ args.add("--eh-frame-hdr");
+ Collections.addAll(args, abi.mLinkerArgs);
+ args.add("-shared");
+ args.add("-Bsymbolic");
+ args.add("-z");
+ args.add("noexecstack");
+ args.add("-z");
+ args.add("relro");
+ args.add("-z");
+ args.add("now");
+
+ File outFile = new File(libAbiFolder, soName);
+ args.add("-o");
+ args.add(outFile.getAbsolutePath());
+
+ args.add("-L" + intermediatesAbiFolder.getAbsolutePath());
+ args.add("-L" + packagedAbiFolder.getAbsolutePath());
+
+ args.add("-soname");
+ args.add(soName);
+
+ args.add(objFile.getAbsolutePath());
+ args.add(new File(intermediatesAbiFolder, "libcompiler_rt.a").getAbsolutePath());
+
+ args.add("-lRSSupport");
+ args.add("-lm");
+ args.add("-lc");
+
+ launcher.runCmdLine(args, env);
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/compiler/SourceSearcher.java b/build-system/builder/src/main/java/com/android/builder/internal/compiler/SourceSearcher.java
new file mode 100644
index 0000000..fe031e9
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/compiler/SourceSearcher.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.internal.compiler;
+
+import com.android.annotations.Nullable;
+import com.android.ide.common.internal.LoggedErrorException;
+import com.android.ide.common.internal.WaitableExecutor;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+/**
+ * Class to search for source files (by extension) in a set of source folders.
+ */
+public class SourceSearcher {
+
+ private final List<File> mSourceFolders;
+ private final String[] mExtensions;
+ @Nullable
+ private WaitableExecutor<Void> mExecutor;
+
+ public interface SourceFileProcessor {
+ void processFile(File sourceFile) throws IOException, InterruptedException, LoggedErrorException;
+ }
+
+ public SourceSearcher(List<File> sourceFolders, String... extensions) {
+ mSourceFolders = sourceFolders;
+ mExtensions = extensions;
+ }
+
+ public void setUseExecutor(boolean useExecutor) {
+ if (useExecutor) {
+ mExecutor = new WaitableExecutor<Void>();
+ } else {
+ mExecutor = null;
+ }
+ }
+
+ public void search(SourceFileProcessor processor)
+ throws IOException, InterruptedException, LoggedErrorException {
+ for (File file : mSourceFolders) {
+ processFile(file, processor);
+ }
+
+ if (mExecutor != null) {
+ mExecutor.waitForTasksWithQuickFail(true /*cancelRemaining*/);
+ }
+ }
+
+ private void processFile(final File file, final SourceFileProcessor processor)
+ throws IOException, InterruptedException, LoggedErrorException {
+ if (file.isFile()) {
+ // get the extension of the file.
+ if (checkExtension(file)) {
+ if (mExecutor != null) {
+ mExecutor.execute(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ processor.processFile(file);
+ return null;
+ }
+ });
+ } else {
+ processor.processFile(file);
+ }
+ }
+ } else if (file.isDirectory()) {
+ File[] children = file.listFiles();
+ if (children != null) {
+ for (File child : children) {
+ processFile(child, processor);
+ }
+ }
+ }
+ }
+
+ private boolean checkExtension(File file) {
+ if (mExtensions.length == 0) {
+ return true;
+ }
+
+ String filename = file.getName();
+ int pos = filename.indexOf('.');
+ if (pos != -1) {
+ String extension = filename.substring(pos + 1);
+ for (String ext : mExtensions) {
+ if (ext.equalsIgnoreCase(extension)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/incremental/DependencyData.java b/build-system/builder/src/main/java/com/android/builder/internal/incremental/DependencyData.java
new file mode 100644
index 0000000..575e50c
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/incremental/DependencyData.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.internal.incremental;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.google.common.base.Charsets;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Holds dependency information, including the main compiled file, secondary input files
+ * (usually headers), and output files.
+ */
+public class DependencyData {
+
+ @NonNull
+ private String mMainFile;
+ @NonNull
+ private List<String> mSecondaryFiles = Lists.newArrayList();
+ @NonNull
+ private List<String> mOutputFiles = Lists.newArrayList();
+
+ DependencyData() {
+ }
+
+ @NonNull
+ public String getMainFile() {
+ return mMainFile;
+ }
+
+ void setMainFile(String path) {
+ mMainFile = path;
+ }
+
+ @NonNull
+ public List<String> getSecondaryFiles() {
+ return mSecondaryFiles;
+ }
+
+ void addSecondaryFile(String path) {
+ mSecondaryFiles.add(path);
+ }
+
+ @NonNull
+ public List<String> getOutputFiles() {
+ return mOutputFiles;
+ }
+
+ void addOutputFile(String path) {
+ mOutputFiles.add(path);
+ }
+
+ /**
+ * Parses the given dependency file and returns the parsed data
+ *
+ * @param dependencyFile the dependency file
+ */
+ @Nullable
+ public static DependencyData parseDependencyFile(@NonNull File dependencyFile)
+ throws IOException {
+ // first check if the dependency file is here.
+ if (!dependencyFile.isFile()) {
+ return null;
+ }
+
+ // Read in our dependency file
+ List<String> content = Files.readLines(dependencyFile, Charsets.UTF_8);
+ return processDependencyData(content);
+ }
+
+ private static enum ParseMode {
+ OUTPUT, MAIN, SECONDARY
+ }
+
+ @VisibleForTesting
+ @Nullable
+ static DependencyData processDependencyData(@NonNull List<String> content) {
+ // The format is technically:
+ // output1 output2 [...]: dep1 dep2 [...]
+ // However, the current tools generating those files guarantee that each file path
+ // is on its own line, making it simpler to handle windows paths as well as path
+ // with spaces in them.
+
+ DependencyData data = new DependencyData();
+
+ ParseMode parseMode = ParseMode.OUTPUT;
+
+ for (String line : content) {
+ line = line.trim();
+
+ // check for separator at the beginning
+ if (line.startsWith(":")) {
+ parseMode = ParseMode.MAIN;
+ line = line.substring(1).trim();
+ }
+
+ ParseMode nextMode = parseMode;
+
+ // remove the \ at the end.
+ if (line.endsWith("\\")) {
+ line = line.substring(0, line.length() - 1).trim();
+ }
+
+ // detect : at the end indicating a parse mode change *after* we process this line.
+ if (line.endsWith(":")) {
+ nextMode = ParseMode.MAIN;
+ line = line.substring(0, line.length() - 1).trim();
+ }
+
+ if (!line.isEmpty()) {
+ switch (parseMode) {
+ case OUTPUT:
+ data.addOutputFile(line);
+ break;
+ case MAIN:
+ data.setMainFile(line);
+ nextMode = ParseMode.SECONDARY;
+ break;
+ case SECONDARY:
+ data.addSecondaryFile(line);
+ break;
+ }
+ }
+
+ parseMode = nextMode;
+ }
+
+ if (data.getMainFile() == null) {
+ return null;
+ }
+
+ return data;
+ }
+
+ @Override
+ public String toString() {
+ return "DependencyData{" +
+ "mMainFile='" + mMainFile + '\'' +
+ ", mSecondaryFiles=" + mSecondaryFiles +
+ ", mOutputFiles=" + mOutputFiles +
+ '}';
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/incremental/DependencyDataStore.java b/build-system/builder/src/main/java/com/android/builder/internal/incremental/DependencyDataStore.java
new file mode 100644
index 0000000..482032a
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/incremental/DependencyDataStore.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.internal.incremental;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.VisibleForTesting;
+import com.google.common.base.Charsets;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.io.Closeables;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ *
+ * Stores a collection of {@link DependencyData}.
+ *
+ * The format is binary and follows the following format:
+ *
+ * (Header Tag)(version number: int)
+ * (Start Tag)(Main File)[(2ndary Tag)(2ndary File)...][(Output tag)(output file)...]
+ * (Start Tag)(Main File)[(2ndary Tag)(2ndary File)...][(Output tag)(output file)...]
+ * ...
+ *
+ * All files are written as (size in int)(byte array, using UTF8 encoding).
+ */
+public class DependencyDataStore {
+
+ private static final byte TAG_HEADER = 0x7F;
+ private static final byte TAG_START = 0x70;
+ private static final byte TAG_2NDARY_FILE = 0x71;
+ private static final byte TAG_OUTPUT = 0x73;
+ private static final byte TAG_END = 0x77;
+
+ private static final int CURRENT_VERSION = 1;
+
+ private final Map<String, DependencyData> mMainFileMap = Maps.newHashMap();
+
+ public DependencyDataStore() {
+
+ }
+
+ public void addData(List<DependencyData> dataList) {
+ for (DependencyData data : dataList) {
+ mMainFileMap.put(data.getMainFile(), data);
+ }
+ }
+
+ public void addData(DependencyData data) {
+ mMainFileMap.put(data.getMainFile(), data);
+ }
+
+ public void remove(DependencyData data) {
+ mMainFileMap.remove(data.getMainFile());
+ }
+
+ public void updateAll(List<DependencyData> dataList) {
+ for (DependencyData data : dataList) {
+ mMainFileMap.put(data.getMainFile(), data);
+ }
+ }
+
+ @NonNull
+ public Collection<DependencyData> getData() {
+ return mMainFileMap.values();
+ }
+
+ @VisibleForTesting
+ DependencyData getByMainFile(String path) {
+ return mMainFileMap.get(path);
+ }
+
+ /**
+ * Returns the map of data using the main file as key.
+ *
+ * @see com.android.builder.internal.incremental.DependencyData#getMainFile()
+ */
+ @NonNull
+ public Map<String, DependencyData> getMainFileMap() {
+ return mMainFileMap;
+ }
+
+ /**
+ * Saves the dependency data to a given file.
+ *
+ * @param file the file to save the data to.
+ * @throws IOException
+ */
+ public void saveTo(File file) throws IOException {
+ FileOutputStream fos = new FileOutputStream(file);
+
+ try {
+ fos.write(TAG_HEADER);
+ writeInt(fos, CURRENT_VERSION);
+
+ for (DependencyData data : getData()) {
+ fos.write(TAG_START);
+ writePath(fos, data.getMainFile());
+ for (String path : data.getSecondaryFiles()) {
+ fos.write(TAG_2NDARY_FILE);
+ writePath(fos, path);
+ }
+
+ for (String path : data.getOutputFiles()) {
+ fos.write(TAG_OUTPUT);
+ writePath(fos, path);
+ }
+ }
+ } finally {
+ Closeables.closeQuietly(fos);
+ }
+ }
+
+ private static class ReusableBuffer {
+ byte[] intBuffer = new byte[4];
+ byte[] pathBuffer = null;
+ }
+
+ /**
+ * Loads the dependency data from the given file.
+ *
+ * @param file the file to load the data from.
+ * @return a map of file-> list of impacted dependency data.
+ * @throws IOException
+ */
+ public Multimap<String, DependencyData> loadFrom(File file) throws IOException {
+ Multimap<String, DependencyData> inputMap = ArrayListMultimap.create();
+
+ FileInputStream fis = new FileInputStream(file);
+
+ // reusable buffer
+ ReusableBuffer buffers = new ReusableBuffer();
+
+ // read the header
+ if (readByte(fis, buffers) != TAG_HEADER) {
+ throw new IllegalStateException("Wrong first byte on " + file.getAbsolutePath());
+ }
+
+ int version = readInt(fis, buffers);
+ if (version != CURRENT_VERSION) {
+ throw new IOException("Unsupported file version: " + version);
+ }
+
+ try {
+ // just read the first byte since it should be the TAG_START
+ byte currentTag = readByte(fis, buffers);
+ if (currentTag != TAG_START) {
+ throw new IllegalStateException("Wrong first tag on " + file.getAbsolutePath());
+ }
+
+ DependencyData currentData = new DependencyData();
+
+ while (currentTag != TAG_END) {
+ // read the path
+ String path = readPath(fis, buffers);
+
+ switch (currentTag) {
+ case TAG_START:
+ currentData.setMainFile(path);
+ mMainFileMap.put(path, currentData);
+ inputMap.put(path, currentData);
+ break;
+ case TAG_2NDARY_FILE:
+ currentData.addSecondaryFile(path);
+ inputMap.put(path, currentData);
+ break;
+ case TAG_OUTPUT:
+ currentData.addOutputFile(path);
+ break;
+ }
+
+ // read the next tag.
+ currentTag = readByte(fis, buffers);
+
+ if (currentTag == TAG_START) {
+ currentData = new DependencyData();
+ }
+ }
+
+ return inputMap;
+ } finally {
+ Closeables.closeQuietly(fis);
+ }
+ }
+
+ private void writeInt(FileOutputStream fos, int value) throws IOException {
+ ByteBuffer b = ByteBuffer.allocate(4);
+ b.putInt(value);
+ fos.write(b.array());
+ }
+
+ private void writePath(FileOutputStream fos, String path) throws IOException {
+ byte[] pathBytes = path.getBytes(Charsets.UTF_8);
+
+ writeInt(fos, pathBytes.length);
+ fos.write(pathBytes);
+ }
+
+ private byte readByte(FileInputStream fis, ReusableBuffer buffers) throws IOException {
+ int read = fis.read(buffers.intBuffer, 0, 1);
+ if (read != 1) {
+ return TAG_END;
+ }
+
+ return buffers.intBuffer[0];
+ }
+
+ private int readInt(FileInputStream fis, ReusableBuffer buffers) throws IOException {
+ int read = fis.read(buffers.intBuffer);
+
+ // there must always be 4 bytes for the path length
+ if (read != 4) {
+ throw new IOException("Failed to read path length");
+ }
+
+ // get the int value.
+ ByteBuffer b = ByteBuffer.wrap(buffers.intBuffer);
+ return b.getInt();
+ }
+
+ private String readPath(FileInputStream fis, ReusableBuffer buffers) throws IOException {
+ int length = readInt(fis, buffers);
+
+ if (buffers.pathBuffer == null || buffers.pathBuffer.length < length) {
+ buffers.pathBuffer = new byte[length];
+ }
+
+ int read = fis.read(buffers.pathBuffer, 0, length);
+ if (read != length) {
+ throw new IOException("Failed to read path");
+ }
+
+ return new String(buffers.pathBuffer, 0, length, Charsets.UTF_8);
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/packaging/JavaResourceProcessor.java b/build-system/builder/src/main/java/com/android/builder/internal/packaging/JavaResourceProcessor.java
new file mode 100644
index 0000000..5508d42
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/packaging/JavaResourceProcessor.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.internal.packaging;
+
+
+import com.android.builder.packaging.DuplicateFileException;
+import com.android.builder.packaging.PackagerException;
+import com.android.builder.packaging.SealedPackageException;
+import com.android.ide.common.packaging.PackagingUtils;
+
+import java.io.File;
+import java.io.IOException;
+
+public class JavaResourceProcessor {
+
+ private final IArchiveBuilder mBuilder;
+
+ public interface IArchiveBuilder {
+
+ /**
+ * Adds a file to the archive at a given path
+ * @param file the file to add
+ * @param archivePath the path of the file inside the APK archive.
+ * @throws com.android.builder.packaging.PackagerException if an error occurred
+ * @throws com.android.builder.packaging.SealedPackageException if the archive is already sealed.
+ * @throws com.android.builder.packaging.DuplicateFileException if a file conflicts with another already added to the APK
+ * at the same location inside the APK archive.
+ */
+ void addFile(File file, String archivePath) throws PackagerException,
+ SealedPackageException, DuplicateFileException;
+ }
+
+
+ public JavaResourceProcessor(IArchiveBuilder builder) {
+ mBuilder = builder;
+ }
+
+ /**
+ * Adds the resources from a source folder to a given {@link IArchiveBuilder}
+ * @param sourceLocation the source folder.
+ * @throws PackagerException if an error occurred
+ * @throws SealedPackageException if the APK is already sealed.
+ * @throws DuplicateFileException if a file conflicts with another already added to the APK
+ * at the same location inside the APK archive.
+ */
+ public void addSourceFolder(String sourceLocation)
+ throws PackagerException, DuplicateFileException, SealedPackageException {
+ File sourceFolder = new File(sourceLocation);
+ if (sourceFolder.isDirectory()) {
+ try {
+ // file is a directory, process its content.
+ File[] files = sourceFolder.listFiles();
+ for (File file : files) {
+ processFileForResource(file, null);
+ }
+ } catch (DuplicateFileException e) {
+ throw e;
+ } catch (SealedPackageException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new PackagerException(e, "Failed to add %s", sourceFolder);
+ }
+ } else {
+ // not a directory? check if it's a file or doesn't exist
+ if (sourceFolder.exists()) {
+ throw new PackagerException("%s is not a folder", sourceFolder);
+ }
+ }
+ }
+
+
+ /**
+ * Processes a {@link File} that could be an APK {@link File}, or a folder containing
+ * java resources.
+ *
+ * @param file the {@link File} to process.
+ * @param path the relative path of this file to the source folder.
+ * Can be <code>null</code> to identify a root file.
+ * @throws IOException
+ * @throws DuplicateFileException if a file conflicts with another already added
+ * to the APK at the same location inside the APK archive.
+ * @throws PackagerException if an error occurred
+ * @throws SealedPackageException if the APK is already sealed.
+ */
+ private void processFileForResource(File file, String path)
+ throws IOException, DuplicateFileException, PackagerException, SealedPackageException {
+ if (file.isDirectory()) {
+ // a directory? we check it
+ if (PackagingUtils.checkFolderForPackaging(file.getName())) {
+ // if it's valid, we append its name to the current path.
+ if (path == null) {
+ path = file.getName();
+ } else {
+ path = path + "/" + file.getName();
+ }
+
+ // and process its content.
+ File[] files = file.listFiles();
+ for (File contentFile : files) {
+ processFileForResource(contentFile, path);
+ }
+ }
+ } else {
+ // a file? we check it to make sure it should be added
+ if (PackagingUtils.checkFileForPackaging(file.getName())) {
+ // we append its name to the current path
+ if (path == null) {
+ path = file.getName();
+ } else {
+ path = path + "/" + file.getName();
+ }
+
+ // and add it to the apk
+ mBuilder.addFile(file, path);
+ }
+ }
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/packaging/Packager.java b/build-system/builder/src/main/java/com/android/builder/internal/packaging/Packager.java
new file mode 100644
index 0000000..3231262
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/packaging/Packager.java
@@ -0,0 +1,593 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.internal.packaging;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.internal.packaging.JavaResourceProcessor.IArchiveBuilder;
+import com.android.builder.packaging.DuplicateFileException;
+import com.android.builder.packaging.PackagerException;
+import com.android.builder.packaging.SealedPackageException;
+import com.android.builder.signing.CertificateInfo;
+import com.android.builder.signing.SignedJarBuilder;
+import com.android.builder.signing.SignedJarBuilder.IZipEntryFilter;
+import com.android.ide.common.packaging.PackagingUtils;
+import com.android.utils.ILogger;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+import java.util.regex.Pattern;
+
+/**
+ * Class making the final app package.
+ * The inputs are:
+ * - packaged resources (output of aapt)
+ * - code file (ouput of dx)
+ * - Java resources coming from the project, its libraries, and its jar files
+ * - Native libraries from the project or its library.
+ *
+ */
+public final class Packager implements IArchiveBuilder {
+
+ private static final Pattern PATTERN_NATIVELIB_EXT = Pattern.compile("^.+\\.so$",
+ Pattern.CASE_INSENSITIVE);
+
+ /**
+ * A No-op zip filter. It's used to detect conflicts.
+ *
+ */
+ private final class NullZipFilter implements IZipEntryFilter {
+ private File mInputFile;
+
+ void reset(File inputFile) {
+ mInputFile = inputFile;
+ }
+
+ @Override
+ public boolean checkEntry(String archivePath) throws ZipAbortException {
+ mLogger.verbose("=> %s", archivePath);
+
+ File duplicate = checkFileForDuplicate(archivePath);
+ if (duplicate != null) {
+ throw new DuplicateFileException(archivePath, duplicate, mInputFile);
+ } else {
+ mAddedFiles.put(archivePath, mInputFile);
+ }
+
+ return true;
+ }
+ }
+
+ /**
+ * Custom {@link IZipEntryFilter} to filter out everything that is not a standard java
+ * resources, and also record whether the zip file contains native libraries.
+ * <p/>Used in {@link SignedJarBuilder#writeZip(java.io.InputStream, IZipEntryFilter)} when
+ * we only want the java resources from external jars.
+ */
+ private final class JavaAndNativeResourceFilter implements IZipEntryFilter {
+ private final List<String> mNativeLibs = new ArrayList<String>();
+ private boolean mNativeLibsConflict = false;
+ private File mInputFile;
+
+ @Override
+ public boolean checkEntry(String archivePath) throws ZipAbortException {
+ // split the path into segments.
+ String[] segments = archivePath.split("/");
+
+ // empty path? skip to next entry.
+ if (segments.length == 0) {
+ return false;
+ }
+
+ // Check each folders to make sure they should be included.
+ // Folders like CVS, .svn, etc.. should already have been excluded from the
+ // jar file, but we need to exclude some other folder (like /META-INF) so
+ // we check anyway.
+ for (int i = 0 ; i < segments.length - 1; i++) {
+ if (!PackagingUtils.checkFolderForPackaging(segments[i])) {
+ return false;
+ }
+ }
+
+ // get the file name from the path
+ String fileName = segments[segments.length-1];
+
+ boolean check = PackagingUtils.checkFileForPackaging(fileName);
+
+ // only do additional checks if the file passes the default checks.
+ if (check) {
+ mLogger.verbose("=> %s", archivePath);
+
+ File duplicate = checkFileForDuplicate(archivePath);
+ if (duplicate != null) {
+ throw new DuplicateFileException(archivePath, duplicate, mInputFile);
+ } else {
+ mAddedFiles.put(archivePath, mInputFile);
+ }
+
+ if (archivePath.endsWith(".so")) {
+ mNativeLibs.add(archivePath);
+
+ // only .so located in lib/ will interfere with the installation
+ if (archivePath.startsWith(SdkConstants.FD_APK_NATIVE_LIBS + "/")) {
+ mNativeLibsConflict = true;
+ }
+ } else if (archivePath.endsWith(".jnilib")) {
+ mNativeLibs.add(archivePath);
+ }
+ }
+
+ return check;
+ }
+
+ List<String> getNativeLibs() {
+ return mNativeLibs;
+ }
+
+ boolean getNativeLibsConflict() {
+ return mNativeLibsConflict;
+ }
+
+ void reset(File inputFile) {
+ mInputFile = inputFile;
+ mNativeLibs.clear();
+ mNativeLibsConflict = false;
+ }
+ }
+
+ private SignedJarBuilder mBuilder = null;
+ private final ILogger mLogger;
+ private boolean mJniDebugMode = false;
+ private boolean mIsSealed = false;
+
+ private final NullZipFilter mNullFilter = new NullZipFilter();
+ private final JavaAndNativeResourceFilter mFilter = new JavaAndNativeResourceFilter();
+ private final HashMap<String, File> mAddedFiles = new HashMap<String, File>();
+
+ /**
+ * Status for the addition of a jar file resources into the APK.
+ * This indicates possible issues with native library inside the jar file.
+ */
+ public interface JarStatus {
+ /**
+ * Returns the list of native libraries found in the jar file.
+ */
+ List<String> getNativeLibs();
+
+ /**
+ * Returns whether some of those libraries were located in the location that Android
+ * expects its native libraries.
+ */
+ boolean hasNativeLibsConflicts();
+
+ }
+
+ /** Internal implementation of {@link JarStatus}. */
+ private static final class JarStatusImpl implements JarStatus {
+ public final List<String> mLibs;
+ public final boolean mNativeLibsConflict;
+
+ private JarStatusImpl(List<String> libs, boolean nativeLibsConflict) {
+ mLibs = libs;
+ mNativeLibsConflict = nativeLibsConflict;
+ }
+
+ @Override
+ public List<String> getNativeLibs() {
+ return mLibs;
+ }
+
+ @Override
+ public boolean hasNativeLibsConflicts() {
+ return mNativeLibsConflict;
+ }
+ }
+
+ /**
+ * Creates a new instance.
+ *
+ * This creates a new builder that will create the specified output file, using the two
+ * mandatory given input files.
+ *
+ * An optional debug keystore can be provided. If set, it is expected that the store password
+ * is 'android' and the key alias and password are 'androiddebugkey' and 'android'.
+ *
+ * An optional {@link ILogger} can also be provided for verbose output. If null, there will
+ * be no output.
+ *
+ * @param apkLocation the file to create
+ * @param resLocation the file representing the packaged resource file.
+ * @param dexLocation the file representing the dex file. This can be null for apk with no code.
+ * @param certificateInfo the signing information used to sign the package. Optional the OS path to the debug keystore, if needed or null.
+ * @param logger the logger.
+ * @throws com.android.builder.packaging.PackagerException
+ */
+ public Packager(
+ @NonNull String apkLocation,
+ @NonNull String resLocation,
+ @NonNull String dexLocation,
+ CertificateInfo certificateInfo,
+ @Nullable String createdBy,
+ ILogger logger) throws PackagerException {
+
+ try {
+ File apkFile = new File(apkLocation);
+ checkOutputFile(apkFile);
+
+ File resFile = new File(resLocation);
+ checkInputFile(resFile);
+
+ File dexFile = null;
+ if (dexLocation != null) {
+ dexFile = new File(dexLocation);
+ checkInputFile(dexFile);
+ }
+
+ mLogger = logger;
+
+ mBuilder = new SignedJarBuilder(
+ new FileOutputStream(apkFile, false /* append */),
+ certificateInfo != null ? certificateInfo.getKey() : null,
+ certificateInfo != null ? certificateInfo.getCertificate() : null,
+ getLocalVersion(),
+ createdBy);
+
+ mLogger.verbose("Packaging %s", apkFile.getName());
+
+ // add the resources
+ addZipFile(resFile);
+
+ // add the class dex file at the root of the apk
+ if (dexFile != null) {
+ addFile(dexFile, SdkConstants.FN_APK_CLASSES_DEX);
+ }
+
+ } catch (PackagerException e) {
+ if (mBuilder != null) {
+ mBuilder.cleanUp();
+ }
+ throw e;
+ } catch (Exception e) {
+ if (mBuilder != null) {
+ mBuilder.cleanUp();
+ }
+ throw new PackagerException(e);
+ }
+ }
+
+ /**
+ * Sets the JNI debug mode. In debug mode, when native libraries are present, the packaging
+ * will also include one or more copies of gdbserver in the final APK file.
+ *
+ * These are used for debugging native code, to ensure that gdbserver is accessible to the
+ * application.
+ *
+ * There will be one version of gdbserver for each ABI supported by the application.
+ *
+ * the gbdserver files are placed in the libs/abi/ folders automatically by the NDK.
+ *
+ * @param jniDebugMode the jni-debug mode flag.
+ */
+ public void setJniDebugMode(boolean jniDebugMode) {
+ mJniDebugMode = jniDebugMode;
+ }
+
+ /**
+ * Adds a file to the APK at a given path
+ * @param file the file to add
+ * @param archivePath the path of the file inside the APK archive.
+ * @throws PackagerException if an error occurred
+ * @throws com.android.builder.packaging.SealedPackageException if the APK is already sealed.
+ * @throws DuplicateFileException if a file conflicts with another already added to the APK
+ * at the same location inside the APK archive.
+ */
+ @Override
+ public void addFile(File file, String archivePath) throws PackagerException,
+ SealedPackageException, DuplicateFileException {
+ if (mIsSealed) {
+ throw new SealedPackageException("APK is already sealed");
+ }
+
+ try {
+ doAddFile(file, archivePath);
+ } catch (DuplicateFileException e) {
+ mBuilder.cleanUp();
+ throw e;
+ } catch (Exception e) {
+ mBuilder.cleanUp();
+ throw new PackagerException(e, "Failed to add %s", file);
+ }
+ }
+
+ /**
+ * Adds the content from a zip file.
+ * All file keep the same path inside the archive.
+ * @param zipFile the zip File.
+ * @throws PackagerException if an error occurred
+ * @throws SealedPackageException if the APK is already sealed.
+ * @throws DuplicateFileException if a file conflicts with another already added to the APK
+ * at the same location inside the APK archive.
+ */
+ void addZipFile(File zipFile) throws PackagerException, SealedPackageException,
+ DuplicateFileException {
+ if (mIsSealed) {
+ throw new SealedPackageException("APK is already sealed");
+ }
+
+ try {
+ mLogger.verbose("%s:", zipFile);
+
+ // reset the filter with this input.
+ mNullFilter.reset(zipFile);
+
+ // ask the builder to add the content of the file.
+ FileInputStream fis = new FileInputStream(zipFile);
+ mBuilder.writeZip(fis, mNullFilter);
+ } catch (DuplicateFileException e) {
+ mBuilder.cleanUp();
+ throw e;
+ } catch (Exception e) {
+ mBuilder.cleanUp();
+ throw new PackagerException(e, "Failed to add %s", zipFile);
+ }
+ }
+
+ /**
+ * Adds the resources from a jar file.
+ * @param jarFile the jar File.
+ * @return a {@link JarStatus} object indicating if native libraries where found in
+ * the jar file.
+ * @throws PackagerException if an error occurred
+ * @throws SealedPackageException if the APK is already sealed.
+ * @throws DuplicateFileException if a file conflicts with another already added to the APK
+ * at the same location inside the APK archive.
+ */
+ public JarStatus addResourcesFromJar(File jarFile) throws PackagerException,
+ SealedPackageException, DuplicateFileException {
+ if (mIsSealed) {
+ throw new SealedPackageException("APK is already sealed");
+ }
+
+ try {
+ mLogger.verbose("%s:", jarFile);
+
+ // reset the filter with this input.
+ mFilter.reset(jarFile);
+
+ // ask the builder to add the content of the file, filtered to only let through
+ // the java resources.
+ FileInputStream fis = new FileInputStream(jarFile);
+ mBuilder.writeZip(fis, mFilter);
+
+ // check if native libraries were found in the external library. This should
+ // constitutes an error or warning depending on if they are in lib/
+ return new JarStatusImpl(mFilter.getNativeLibs(), mFilter.getNativeLibsConflict());
+ } catch (DuplicateFileException e) {
+ mBuilder.cleanUp();
+ throw e;
+ } catch (Exception e) {
+ mBuilder.cleanUp();
+ throw new PackagerException(e, "Failed to add %s", jarFile);
+ }
+ }
+
+ /**
+ * Adds the native libraries from the top native folder.
+ * The content of this folder must be the various ABI folders.
+ *
+ * This may or may not copy gdbserver into the apk based on whether the debug mode is set.
+ *
+ * @param nativeFolder the root folder containing the abi folders which contain the .so
+ *
+ * @throws PackagerException if an error occurred
+ * @throws SealedPackageException if the APK is already sealed.
+ * @throws DuplicateFileException if a file conflicts with another already added to the APK
+ * at the same location inside the APK archive.
+ *
+ * @see #setJniDebugMode(boolean)
+ */
+ public void addNativeLibraries(@NonNull File nativeFolder, @Nullable Set<String> abiFilters)
+ throws PackagerException, SealedPackageException, DuplicateFileException {
+ if (mIsSealed) {
+ throw new SealedPackageException("APK is already sealed");
+ }
+
+ if (!nativeFolder.isDirectory()) {
+ // not a directory? check if it's a file or doesn't exist
+ if (nativeFolder.exists()) {
+ throw new PackagerException("%s is not a folder", nativeFolder);
+ } else {
+ throw new PackagerException("%s does not exist", nativeFolder);
+ }
+ }
+
+ File[] abiList = nativeFolder.listFiles();
+
+ mLogger.verbose("Native folder: %s", nativeFolder);
+
+ if (abiList != null) {
+ for (File abi : abiList) {
+ if (abiFilters != null && !abiFilters.contains(abi.getName())) {
+ continue;
+ }
+
+ if (abi.isDirectory()) { // ignore files
+
+ File[] libs = abi.listFiles();
+ if (libs != null) {
+ for (File lib : libs) {
+ // only consider files that are .so or, if in debug mode, that
+ // are gdbserver executables
+ String libName = lib.getName();
+ if (lib.isFile() &&
+ (PATTERN_NATIVELIB_EXT.matcher(lib.getName()).matches() ||
+ (mJniDebugMode &&
+ (SdkConstants.FN_GDBSERVER.equals(libName) ||
+ SdkConstants.FN_GDB_SETUP.equals(libName))))) {
+
+ String path =
+ SdkConstants.FD_APK_NATIVE_LIBS + "/" +
+ abi.getName() + "/" + libName;
+
+ try {
+ doAddFile(lib, path);
+ } catch (IOException e) {
+ mBuilder.cleanUp();
+ throw new PackagerException(e, "Failed to add %s", lib);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Seals the APK, and signs it if necessary.
+ *
+ * @throws PackagerException if an error occurred
+ * @throws SealedPackageException if the APK is already sealed.
+ */
+ public void sealApk() throws PackagerException, SealedPackageException {
+ if (mIsSealed) {
+ throw new SealedPackageException("APK is already sealed");
+ }
+
+ // close and sign the application package.
+ try {
+ mBuilder.close();
+ mIsSealed = true;
+ } catch (Exception e) {
+ throw new PackagerException(e, "Failed to seal APK");
+ } finally {
+ mBuilder.cleanUp();
+ }
+ }
+
+ private void doAddFile(File file, String archivePath) throws DuplicateFileException,
+ IOException {
+ mLogger.verbose("%1$s => %2$s", file, archivePath);
+
+ File duplicate = checkFileForDuplicate(archivePath);
+ if (duplicate != null) {
+ throw new DuplicateFileException(archivePath, duplicate, file);
+ }
+
+ mAddedFiles.put(archivePath, file);
+ mBuilder.writeFile(file, archivePath);
+ }
+
+ /**
+ * Checks if the given path in the APK archive has not already been used and if it has been,
+ * then returns a {@link File} object for the source of the duplicate
+ * @param archivePath the archive path to test.
+ * @return A File object of either a file at the same location or an archive that contains a
+ * file that was put at the same location.
+ */
+ private File checkFileForDuplicate(String archivePath) {
+ return mAddedFiles.get(archivePath);
+ }
+
+ /**
+ * Checks an output {@link File} object.
+ * This checks the following:
+ * - the file is not an existing directory.
+ * - if the file exists, that it can be modified.
+ * - if it doesn't exists, that a new file can be created.
+ * @param file the File to check
+ * @throws PackagerException If the check fails
+ */
+ private void checkOutputFile(File file) throws PackagerException {
+ if (file.isDirectory()) {
+ throw new PackagerException("%s is a directory!", file);
+ }
+
+ if (file.exists()) { // will be a file in this case.
+ if (!file.canWrite()) {
+ throw new PackagerException("Cannot write %s", file);
+ }
+ } else {
+ try {
+ if (!file.createNewFile()) {
+ throw new PackagerException("Failed to create %s", file);
+ }
+ } catch (IOException e) {
+ throw new PackagerException(
+ "Failed to create '%1$ss': %2$s", file, e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Checks an input {@link File} object.
+ * This checks the following:
+ * - the file is not an existing directory.
+ * - that the file exists (if <var>throwIfDoesntExist</var> is <code>false</code>) and can
+ * be read.
+ * @param file the File to check
+ * @throws FileNotFoundException if the file is not here.
+ * @throws PackagerException If the file is a folder or a file that cannot be read.
+ */
+ private static void checkInputFile(File file) throws FileNotFoundException, PackagerException {
+ if (file.isDirectory()) {
+ throw new PackagerException("%s is a directory!", file);
+ }
+
+ if (file.exists()) {
+ if (!file.canRead()) {
+ throw new PackagerException("Cannot read %s", file);
+ }
+ } else {
+ throw new FileNotFoundException(String.format("%s does not exist", file));
+ }
+ }
+
+ private static String getLocalVersion() {
+ Class clazz = Packager.class;
+ String className = clazz.getSimpleName() + ".class";
+ String classPath = clazz.getResource(className).toString();
+ if (!classPath.startsWith("jar")) {
+ // Class not from JAR, unlikely
+ return null;
+ }
+ try {
+ String manifestPath = classPath.substring(0, classPath.lastIndexOf('!') + 1) +
+ "/META-INF/MANIFEST.MF";
+ Manifest manifest = new Manifest(new URL(manifestPath).openStream());
+ Attributes attr = manifest.getMainAttributes();
+ return attr.getValue("Builder-Version");
+ } catch (MalformedURLException ignored) {
+ } catch (IOException ignored) {
+ }
+
+ return null;
+ }
+
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/testing/CustomTestRunListener.java b/build-system/builder/src/main/java/com/android/builder/internal/testing/CustomTestRunListener.java
new file mode 100644
index 0000000..1c1a908
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/testing/CustomTestRunListener.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.internal.testing;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.ddmlib.testrunner.TestResult;
+import com.android.ddmlib.testrunner.XmlTestRunListener;
+import com.android.utils.ILogger;
+import com.google.common.collect.Sets;
+import org.kxml2.io.KXmlSerializer;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Custom version of {@link com.android.ddmlib.testrunner.ITestRunListener}.
+ */
+public class CustomTestRunListener extends XmlTestRunListener {
+
+ @NonNull
+ private final String mDeviceName;
+ @NonNull
+ private final String mProjectName;
+ @NonNull
+ private final String mFlavorName;
+ private final ILogger mLogger;
+ private final Set<TestIdentifier> mFailedTests = Sets.newHashSet();
+
+
+ public CustomTestRunListener(@NonNull String deviceName,
+ @NonNull String projectName, @NonNull String flavorName,
+ @Nullable ILogger logger) {
+ mDeviceName = deviceName;
+ mProjectName = projectName;
+ mFlavorName = flavorName;
+ mLogger = logger;
+ }
+
+ @Override
+ protected File getResultFile(File reportDir) throws IOException {
+ return new File(reportDir,
+ "TEST-" + mDeviceName + "-" + mProjectName + "-" + mFlavorName + ".xml");
+ }
+
+ @Override
+ protected String getTestSuiteName() {
+ // in order for the gradle report to look good we put the test suite name as one of the
+ // test class name.
+
+ Map<TestIdentifier, TestResult> testResults = getRunResult().getTestResults();
+ if (testResults.isEmpty()) {
+ return null;
+ }
+
+ Map.Entry<TestIdentifier, TestResult> testEntry = testResults.entrySet().iterator().next();
+ return testEntry.getKey().getClassName();
+ }
+
+ @Override
+ protected void setPropertiesAttributes(KXmlSerializer serializer, String namespace)
+ throws IOException {
+ super.setPropertiesAttributes(serializer, namespace);
+
+ serializer.attribute(null, "device", mDeviceName);
+ serializer.attribute(null, "flavor", mFlavorName);
+ serializer.attribute(null, "project", mProjectName);
+ }
+
+ @Override
+ public void testRunStarted(String runName, int testCount) {
+ if (mLogger != null) {
+ mLogger.info(
+ String.format("Starting %1$d tests on %2$s", testCount, mDeviceName));
+ }
+ super.testRunStarted(runName, testCount);
+ }
+
+ @Override
+ public void testFailed(TestFailure status, TestIdentifier test, String trace) {
+ if (mLogger != null) {
+ mLogger.warning(
+ String.format("\n%1$s > %2$s[%3$s] \033[31mFAILED \033[0m",
+ test.getClassName(), test.getTestName(), mDeviceName));
+ mLogger.warning(getModifiedTrace(trace));
+ }
+
+ mFailedTests.add(test);
+
+ // Force test to be a failure and not an error to go around a limitation of
+ // Gradle's reporting that handle errors like success!
+ // TODO: support ERROR test failures.
+ super.testFailed(TestFailure.FAILURE, test, trace);
+ }
+
+ @Override
+ public void testEnded(TestIdentifier test, Map<String, String> testMetrics) {
+ if (!mFailedTests.remove(test)) {
+ // if wasn't present in the list, then the test succeeded.
+ if (mLogger != null) {
+ mLogger.info(
+ String.format("\n%1$s > %2$s[%3$s] \033[32mSUCCESS \033[0m",
+ test.getClassName(), test.getTestName(), mDeviceName));
+ }
+
+ }
+
+ super.testEnded(test, testMetrics);
+ }
+
+ @Override
+ public void testRunFailed(String errorMessage) {
+ if (mLogger != null) {
+ mLogger.warning("Tests on %1$s failed: %2$s", mDeviceName, errorMessage);
+ }
+ super.testRunFailed(errorMessage);
+ }
+
+ private String getModifiedTrace(String trace) {
+ // split lines
+ String[] lines = trace.split("\n");
+
+ if (lines.length < 2) {
+ return trace;
+ }
+
+ // get the first two lines, and prepend \t on them
+ return "\t" + lines[0] + "\n\t" + lines[1];
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/internal/testing/SimpleTestCallable.java b/build-system/builder/src/main/java/com/android/builder/internal/testing/SimpleTestCallable.java
new file mode 100644
index 0000000..9ddb355
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/internal/testing/SimpleTestCallable.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.internal.testing;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.testing.TestData;
+import com.android.builder.testing.api.DeviceConnector;
+import com.android.builder.testing.api.DeviceException;
+import com.android.ddmlib.testrunner.ITestRunListener;
+import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.utils.ILogger;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+/**
+ * Basic Callable to run tests on a given {@link DeviceConnector} using
+ * {@link RemoteAndroidTestRunner}.
+ */
+public class SimpleTestCallable implements Callable<Boolean> {
+
+ @NonNull
+ private final String projectName;
+ @NonNull
+ private final DeviceConnector device;
+ @NonNull
+ private final String flavorName;
+ @NonNull
+ private final TestData testData;
+ @NonNull
+ private final File resultsDir;
+ @NonNull
+ private final File testApk;
+ @Nullable
+ private final File testedApk;
+ private final int timeout;
+ @NonNull
+ private final ILogger logger;
+
+ public SimpleTestCallable(
+ @NonNull DeviceConnector device,
+ @NonNull String projectName,
+ @NonNull String flavorName,
+ @NonNull File testApk,
+ @Nullable File testedApk,
+ @NonNull TestData testData,
+ @NonNull File resultsDir,
+ int timeout,
+ @NonNull ILogger logger) {
+ this.projectName = projectName;
+ this.device = device;
+ this.flavorName = flavorName;
+ this.resultsDir = resultsDir;
+ this.testApk = testApk;
+ this.testedApk = testedApk;
+ this.testData = testData;
+ this.timeout = timeout;
+ this.logger = logger;
+ }
+
+ @Override
+ public Boolean call() throws Exception {
+ String deviceName = device.getName();
+ boolean isInstalled = false;
+
+ CustomTestRunListener runListener = new CustomTestRunListener(
+ deviceName, projectName, flavorName, logger);
+ runListener.setReportDir(resultsDir);
+
+ long time = System.currentTimeMillis();
+
+ try {
+ device.connect(timeout, logger);
+
+ if (testedApk != null) {
+ logger.verbose("DeviceConnector '%s': installing %s", deviceName, testedApk);
+ device.installPackage(testedApk, timeout, logger);
+ }
+
+ logger.verbose("DeviceConnector '%s': installing %s", deviceName, testApk);
+ device.installPackage(testApk, timeout, logger);
+ isInstalled = true;
+
+ RemoteAndroidTestRunner runner = new RemoteAndroidTestRunner(
+ testData.getPackageName(),
+ testData.getInstrumentationRunner(),
+ device);
+
+ runner.setRunName(deviceName);
+ runner.setMaxtimeToOutputResponse(timeout);
+
+ runner.run(runListener);
+
+ return runListener.getRunResult().hasFailedTests();
+ } catch (Exception e) {
+ Map<String, String> emptyMetrics = Collections.emptyMap();
+
+ // create a fake test output
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ PrintWriter pw = new PrintWriter(baos, true);
+ e.printStackTrace(pw);
+ TestIdentifier fakeTest = new TestIdentifier(device.getClass().getName(), "runTests");
+ runListener.testStarted(fakeTest);
+ runListener.testFailed(ITestRunListener.TestFailure.ERROR, fakeTest , baos.toString());
+ runListener.testEnded(fakeTest, emptyMetrics);
+
+ // end the run to generate the XML file.
+ runListener.testRunEnded(System.currentTimeMillis() - time, emptyMetrics);
+
+ // and throw
+ throw e;
+ } finally {
+ if (isInstalled) {
+ // uninstall the apps
+ // This should really not be null, because if it was the build
+ // would have broken before.
+ uninstall(testApk, testData.getPackageName(), deviceName);
+
+ if (testedApk != null) {
+ uninstall(testedApk, testData.getTestedPackageName(), deviceName);
+ }
+ }
+
+ device.disconnect(timeout, logger);
+ }
+ }
+
+ private void uninstall(@NonNull File apkFile, @Nullable String packageName,
+ @NonNull String deviceName)
+ throws DeviceException {
+ if (packageName != null) {
+ logger.verbose("DeviceConnector '%s': uninstalling %s", deviceName, packageName);
+ device.uninstallPackage(packageName, timeout, logger);
+ } else {
+ logger.verbose("DeviceConnector '%s': unable to uninstall %s: unable to get package name",
+ deviceName, apkFile);
+ }
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/packaging/DuplicateFileException.java b/build-system/builder/src/main/java/com/android/builder/packaging/DuplicateFileException.java
new file mode 100644
index 0000000..132cce8
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/packaging/DuplicateFileException.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.packaging;
+
+import com.android.annotations.NonNull;
+import com.android.builder.signing.SignedJarBuilder.IZipEntryFilter.ZipAbortException;
+
+import java.io.File;
+
+/**
+ * An exception thrown during packaging of an APK file.
+ */
+public final class DuplicateFileException extends ZipAbortException {
+ private static final long serialVersionUID = 1L;
+ private final String mArchivePath;
+ private final File mFile1;
+ private final File mFile2;
+
+ public DuplicateFileException(@NonNull String archivePath, @NonNull File file1,
+ @NonNull File file2) {
+ super();
+ mArchivePath = archivePath;
+ mFile1 = file1;
+ mFile2 = file2;
+ }
+
+ public String getArchivePath() {
+ return mArchivePath;
+ }
+
+ public File getFile1() {
+ return mFile1;
+ }
+
+ public File getFile2() {
+ return mFile2;
+ }
+
+ @Override
+ public String getMessage() {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("Duplicate files copied in APK ").append(mArchivePath).append('\n');
+ sb.append("\tFile 1: ").append(mFile1).append('\n');
+ sb.append("\tFile 2: ").append(mFile1).append('\n');
+
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/build-system/builder/src/main/java/com/android/builder/packaging/PackagerException.java b/build-system/builder/src/main/java/com/android/builder/packaging/PackagerException.java
new file mode 100644
index 0000000..aa33c53
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/packaging/PackagerException.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.packaging;
+
+/**
+ * An exception thrown during packaging of an APK file.
+ */
+public final class PackagerException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public PackagerException(String format, Object... args) {
+ super(String.format(format, args));
+ }
+
+ public PackagerException(Throwable cause, String format, Object... args) {
+ super(String.format(format, args), cause);
+ }
+
+ public PackagerException(Throwable cause) {
+ super(cause);
+ }
+}
\ No newline at end of file
diff --git a/build-system/builder/src/main/java/com/android/builder/packaging/SealedPackageException.java b/build-system/builder/src/main/java/com/android/builder/packaging/SealedPackageException.java
new file mode 100644
index 0000000..fd35b1e
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/packaging/SealedPackageException.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.packaging;
+
+/**
+ * An exception thrown when trying to add files to a sealed APK.
+ */
+public final class SealedPackageException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public SealedPackageException(String format, Object... args) {
+ super(String.format(format, args));
+ }
+
+ public SealedPackageException(Throwable cause, String format, Object... args) {
+ super(String.format(format, args), cause);
+ }
+
+ public SealedPackageException(Throwable cause) {
+ super(cause);
+ }
+}
\ No newline at end of file
diff --git a/build-system/builder/src/main/java/com/android/builder/packaging/SigningException.java b/build-system/builder/src/main/java/com/android/builder/packaging/SigningException.java
new file mode 100644
index 0000000..efa4b2e
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/packaging/SigningException.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.packaging;
+
+/**
+ * An exception thrown when signing fails.
+ */
+public final class SigningException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public SigningException(String format, Object... args) {
+ super(String.format(format, args));
+ }
+
+ public SigningException(Throwable cause, String format, Object... args) {
+ super(String.format(format, args), cause);
+ }
+
+ public SigningException(Throwable cause) {
+ super(cause);
+ }
+}
\ No newline at end of file
diff --git a/build-system/builder/src/main/java/com/android/builder/signing/CertificateInfo.java b/build-system/builder/src/main/java/com/android/builder/signing/CertificateInfo.java
new file mode 100644
index 0000000..6c34fde
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/signing/CertificateInfo.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.signing;
+
+import com.android.annotations.NonNull;
+
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Signing information.
+ *
+ * Both the {@link PrivateKey} and the {@link X509Certificate} are guaranteed to be non-null.
+ *
+ */
+public class CertificateInfo {
+ public final PrivateKey mKey;
+ public final X509Certificate mCertificate;
+
+ public CertificateInfo(@NonNull PrivateKey key, @NonNull X509Certificate certificate) {
+ mKey = checkNotNull(key, "Key cannot be null.");
+ mCertificate = checkNotNull(certificate, "Certificate cannot be null.");
+ }
+
+ public PrivateKey getKey() {
+ return mKey;
+ }
+
+ public X509Certificate getCertificate() {
+ return mCertificate;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/signing/DefaultSigningConfig.java b/build-system/builder/src/main/java/com/android/builder/signing/DefaultSigningConfig.java
new file mode 100644
index 0000000..ff47542
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/signing/DefaultSigningConfig.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.signing;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.SigningConfig;
+import com.android.prefs.AndroidLocation.AndroidLocationException;
+import com.google.common.base.Objects;
+
+import java.io.File;
+import java.security.KeyStore;
+
+/**
+ * SigningConfig encapsulates the information necessary to access certificates in a keystore file
+ * that can be used to sign APKs.
+ */
+public class DefaultSigningConfig implements SigningConfig {
+
+ public static final String DEFAULT_PASSWORD = "android";
+ public static final String DEFAULT_ALIAS = "AndroidDebugKey";
+
+ @NonNull
+ protected final String mName;
+ private File mStoreFile = null;
+ private String mStorePassword = null;
+ private String mKeyAlias = null;
+ private String mKeyPassword = null;
+ private String mStoreType = KeyStore.getDefaultType();
+
+ /**
+ * Creates a SigningConfig.
+ */
+ public DefaultSigningConfig(@NonNull String name) {
+ mName = name;
+ }
+
+ /**
+ * Initializes the SigningConfig with the debug keystore/key alias data.
+ *
+ * @throws AndroidLocationException if the debug keystore location cannot be found
+ */
+ public void initDebug() throws AndroidLocationException {
+ mStoreFile = new File(KeystoreHelper.defaultDebugKeystoreLocation());
+ mStorePassword = DEFAULT_PASSWORD;
+ mKeyAlias = DEFAULT_ALIAS;
+ mKeyPassword = DEFAULT_PASSWORD;
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ @Override
+ @Nullable
+ public File getStoreFile() {
+ return mStoreFile;
+ }
+
+ @NonNull
+ public DefaultSigningConfig setStoreFile(File storeFile) {
+ mStoreFile = storeFile;
+ return this;
+ }
+
+ @Override
+ @Nullable
+ public String getStorePassword() {
+ return mStorePassword;
+ }
+
+ @NonNull
+ public DefaultSigningConfig setStorePassword(String storePassword) {
+ mStorePassword = storePassword;
+ return this;
+ }
+
+ @Override
+ @Nullable
+ public String getKeyAlias() {
+ return mKeyAlias;
+ }
+
+ @NonNull
+ public DefaultSigningConfig setKeyAlias(String keyAlias) {
+ mKeyAlias = keyAlias;
+ return this;
+ }
+
+ @Override
+ @Nullable
+ public String getKeyPassword() {
+ return mKeyPassword;
+ }
+
+ @NonNull
+ public DefaultSigningConfig setKeyPassword(String keyPassword) {
+ mKeyPassword = keyPassword;
+ return this;
+ }
+
+ @Override
+ @Nullable
+ public String getStoreType() {
+ return mStoreType;
+ }
+
+ @NonNull
+ public DefaultSigningConfig setStoreType(String storeType) {
+ mStoreType = storeType;
+ return this;
+ }
+
+ @Override
+ public boolean isSigningReady() {
+ return mStoreFile != null &&
+ mStorePassword != null &&
+ mKeyAlias != null &&
+ mKeyPassword != null;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
+
+ DefaultSigningConfig that = (DefaultSigningConfig) o;
+
+ if (mKeyAlias != null ?
+ !mKeyAlias.equals(that.mKeyAlias) :
+ that.mKeyAlias != null)
+ return false;
+ if (mKeyPassword != null ?
+ !mKeyPassword.equals(that.mKeyPassword) :
+ that.mKeyPassword != null)
+ return false;
+ if (mStoreFile != null ?
+ !mStoreFile.equals(that.mStoreFile) :
+ that.mStoreFile != null)
+ return false;
+ if (mStorePassword != null ?
+ !mStorePassword.equals(that.mStorePassword) :
+ that.mStorePassword != null)
+ return false;
+ if (mStoreType != null ?
+ !mStoreType.equals(that.mStoreType) :
+ that.mStoreType != null)
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + (mStoreFile != null ?
+ mStoreFile.hashCode() : 0);
+ result = 31 * result + (mStorePassword != null ?
+ mStorePassword.hashCode() : 0);
+ result = 31 * result + (mKeyAlias != null ? mKeyAlias.hashCode() : 0);
+ result = 31 * result + (mKeyPassword != null ? mKeyPassword.hashCode() : 0);
+ result = 31 * result + (mStoreType != null ? mStoreType.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .add("storeFile", mStoreFile.getAbsolutePath())
+ .add("storePassword", mStorePassword)
+ .add("keyAlias", mKeyAlias)
+ .add("keyPassword", mKeyPassword)
+ .add("storeType", mStoreType)
+ .toString();
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/signing/KeystoreHelper.java b/build-system/builder/src/main/java/com/android/builder/signing/KeystoreHelper.java
new file mode 100644
index 0000000..957f8f6
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/signing/KeystoreHelper.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.signing;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.SigningConfig;
+import com.android.prefs.AndroidLocation;
+import com.android.prefs.AndroidLocation.AndroidLocationException;
+import com.android.sdklib.util.GrabProcessOutput;
+import com.android.sdklib.util.GrabProcessOutput.IProcessOutput;
+import com.android.sdklib.util.GrabProcessOutput.Wait;
+import com.android.utils.ILogger;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.security.KeyStore;
+import java.security.KeyStore.PrivateKeyEntry;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+
+/**
+ * A Helper to create and read keystore/keys.
+ */
+public final class KeystoreHelper {
+
+ // Certificate CN value. This is a hard-coded value for the debug key.
+ // Android Market checks against this value in order to refuse applications signed with
+ // debug keys.
+ private static final String CERTIFICATE_DESC = "CN=Android Debug,O=Android,C=US";
+
+
+ /**
+ * Returns the location of the default debug keystore.
+ *
+ * @return The location of the default debug keystore.
+ * @throws AndroidLocationException if the location cannot be computed
+ */
+ @NonNull
+ public static String defaultDebugKeystoreLocation() throws AndroidLocationException {
+ //this is guaranteed to either return a non null value (terminated with a platform
+ // specific separator), or throw.
+ String folder = AndroidLocation.getFolder();
+ return folder + "debug.keystore";
+ }
+
+ /**
+ * Creates a new debug store with the location, keyalias, and passwords specified in the
+ * config.
+ *
+ * @param signingConfig The signing config
+ * @param logger a logger object to receive the log of the creation.
+ * @throws KeytoolException
+ */
+ public static boolean createDebugStore(@NonNull SigningConfig signingConfig,
+ @NonNull ILogger logger) throws KeytoolException {
+
+ return createNewStore(signingConfig, CERTIFICATE_DESC, 30 /* validity*/, logger);
+ }
+
+ /**
+ * Creates a new store
+ *
+ * @param signingConfig the Signing Configuration
+ * @param description description
+ * @param validityYears
+ * @param logger
+ * @throws KeytoolException
+ */
+ private static boolean createNewStore(
+ @NonNull SigningConfig signingConfig,
+ @NonNull String description,
+ int validityYears,
+ @NonNull final ILogger logger)
+ throws KeytoolException {
+
+ // get the executable name of keytool depending on the platform.
+ String os = System.getProperty("os.name");
+
+ String keytoolCommand;
+ if (os.startsWith("Windows")) {
+ keytoolCommand = "keytool.exe";
+ } else {
+ keytoolCommand = "keytool";
+ }
+
+ String javaHome = System.getProperty("java.home");
+
+ if (javaHome != null && javaHome.length() > 0) {
+ keytoolCommand = javaHome + File.separator + "bin" + File.separator + keytoolCommand;
+ }
+
+ // create the command line to call key tool to build the key with no user input.
+ ArrayList<String> commandList = new ArrayList<String>();
+ commandList.add(keytoolCommand);
+ commandList.add("-genkey");
+ commandList.add("-alias");
+ commandList.add(signingConfig.getKeyAlias());
+ commandList.add("-keyalg");
+ commandList.add("RSA");
+ commandList.add("-dname");
+ commandList.add(description);
+ commandList.add("-validity");
+ commandList.add(Integer.toString(validityYears * 365));
+ commandList.add("-keypass");
+ commandList.add(signingConfig.getKeyPassword());
+ commandList.add("-keystore");
+ commandList.add(signingConfig.getStoreFile().getAbsolutePath());
+ commandList.add("-storepass");
+ commandList.add(signingConfig.getStorePassword());
+ if (signingConfig.getStoreType() != null) {
+ commandList.add("-storetype");
+ commandList.add(signingConfig.getStoreType());
+ }
+
+ String[] commandArray = commandList.toArray(new String[commandList.size()]);
+
+ // launch the command line process
+ int result = 0;
+ try {
+ Process process = Runtime.getRuntime().exec(commandArray);
+ result = GrabProcessOutput.grabProcessOutput(
+ process,
+ Wait.WAIT_FOR_READERS,
+ new IProcessOutput() {
+ @Override
+ public void out(@Nullable String line) {
+ if (line != null) {
+ logger.info(line);
+ }
+ }
+
+ @Override
+ public void err(@Nullable String line) {
+ if (line != null) {
+ logger.error(null /*throwable*/, line);
+ }
+ }
+ });
+ } catch (Exception e) {
+ // create the command line as one string for debugging purposes
+ StringBuilder builder = new StringBuilder();
+ boolean firstArg = true;
+ for (String arg : commandArray) {
+ boolean hasSpace = arg.indexOf(' ') != -1;
+
+ if (firstArg) {
+ firstArg = false;
+ } else {
+ builder.append(' ');
+ }
+
+ if (hasSpace) {
+ builder.append('"');
+ }
+
+ builder.append(arg);
+
+ if (hasSpace) {
+ builder.append('"');
+ }
+ }
+
+ throw new KeytoolException("Failed to create key: " + e.getMessage(),
+ javaHome, builder.toString());
+ }
+
+ return result == 0;
+ }
+
+ /**
+ * Returns the CertificateInfo for the given signing configuration.
+ *
+ * Returns null if the key could not be found. If the passwords are wrong,
+ * it throws an exception
+ *
+ * @param signingConfig the signing configuration
+ * @return the certificate info if it could be loaded.
+ * @throws KeytoolException
+ * @throws FileNotFoundException
+ */
+ public static CertificateInfo getCertificateInfo(@NonNull SigningConfig signingConfig)
+ throws KeytoolException, FileNotFoundException {
+
+ try {
+ KeyStore keyStore = KeyStore.getInstance(
+ signingConfig.getStoreType() != null ?
+ signingConfig.getStoreType() : KeyStore.getDefaultType());
+
+ FileInputStream fis = new FileInputStream(signingConfig.getStoreFile());
+ //noinspection ConstantConditions
+ keyStore.load(fis, signingConfig.getStorePassword().toCharArray());
+ fis.close();
+
+ //noinspection ConstantConditions
+ char[] keyPassword = signingConfig.getKeyPassword().toCharArray();
+ PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(
+ signingConfig.getKeyAlias(),
+ new KeyStore.PasswordProtection(keyPassword));
+
+ if (entry != null) {
+ return new CertificateInfo(entry.getPrivateKey(),
+ (X509Certificate) entry.getCertificate());
+ }
+ } catch (FileNotFoundException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new KeytoolException(
+ String.format("Failed to read key %1$s from store \"%2$s\": %3$s",
+ signingConfig.getKeyAlias(), signingConfig.getStoreFile(),
+ e.getMessage()),
+ e);
+ }
+
+ return null;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/signing/KeytoolException.java b/build-system/builder/src/main/java/com/android/builder/signing/KeytoolException.java
new file mode 100644
index 0000000..890096c
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/signing/KeytoolException.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.signing;
+
+public class KeytoolException extends Exception {
+ /** default serial uid */
+ private static final long serialVersionUID = 1L;
+ private String mJavaHome = null;
+ private String mCommandLine = null;
+
+ KeytoolException(String message) {
+ super(message);
+ }
+
+ KeytoolException(String message, Throwable t) {
+ super(message, t);
+ }
+
+ KeytoolException(String message, String javaHome, String commandLine) {
+ super(message);
+
+ mJavaHome = javaHome;
+ mCommandLine = commandLine;
+ }
+
+ public String getJavaHome() {
+ return mJavaHome;
+ }
+
+ public String getCommandLine() {
+ return mCommandLine;
+ }
+
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/signing/SignedJarBuilder.java b/build-system/builder/src/main/java/com/android/builder/signing/SignedJarBuilder.java
new file mode 100644
index 0000000..c3e2d84
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/signing/SignedJarBuilder.java
@@ -0,0 +1,443 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.signing;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.signing.SignedJarBuilder.IZipEntryFilter.ZipAbortException;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.DEROutputStream;
+import org.bouncycastle.cert.jcajce.JcaCertStore;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.CMSSignedDataGenerator;
+import org.bouncycastle.cms.CMSTypedData;
+import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
+import org.bouncycastle.util.encoders.Base64;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.security.DigestOutputStream;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.Signature;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+/**
+ * A Jar file builder with signature support.
+ */
+public class SignedJarBuilder {
+ private static final String DIGEST_ALGORITHM = "SHA1";
+ private static final String DIGEST_ATTR = "SHA1-Digest";
+ private static final String DIGEST_MANIFEST_ATTR = "SHA1-Digest-Manifest";
+
+ /** Write to another stream and track how many bytes have been
+ * written.
+ */
+ private static class CountOutputStream extends FilterOutputStream {
+ private int mCount = 0;
+
+ public CountOutputStream(OutputStream out) {
+ super(out);
+ mCount = 0;
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ super.write(b);
+ mCount++;
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ super.write(b, off, len);
+ mCount += len;
+ }
+
+ public int size() {
+ return mCount;
+ }
+ }
+
+ private JarOutputStream mOutputJar;
+ private PrivateKey mKey;
+ private X509Certificate mCertificate;
+ private Manifest mManifest;
+ private MessageDigest mMessageDigest;
+
+ private byte[] mBuffer = new byte[4096];
+
+ /**
+ * Classes which implement this interface provides a method to check whether a file should
+ * be added to a Jar file.
+ */
+ public interface IZipEntryFilter {
+
+ /**
+ * An exception thrown during packaging of a zip file into APK file.
+ * This is typically thrown by implementations of
+ * {@link IZipEntryFilter#checkEntry(String)}.
+ */
+ public static class ZipAbortException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public ZipAbortException() {
+ super();
+ }
+
+ public ZipAbortException(String format, Object... args) {
+ super(String.format(format, args));
+ }
+
+ public ZipAbortException(Throwable cause, String format, Object... args) {
+ super(String.format(format, args), cause);
+ }
+
+ public ZipAbortException(Throwable cause) {
+ super(cause);
+ }
+ }
+
+
+ /**
+ * Checks a file for inclusion in a Jar archive.
+ * @param archivePath the archive file path of the entry
+ * @return <code>true</code> if the file should be included.
+ * @throws ZipAbortException if writing the file should be aborted.
+ */
+ public boolean checkEntry(String archivePath) throws ZipAbortException;
+ }
+
+ /**
+ * Creates a {@link SignedJarBuilder} with a given output stream, and signing information.
+ * <p/>If either <code>key</code> or <code>certificate</code> is <code>null</code> then
+ * the archive will not be signed.
+ * @param out the {@link OutputStream} where to write the Jar archive.
+ * @param key the {@link PrivateKey} used to sign the archive, or <code>null</code>.
+ * @param certificate the {@link X509Certificate} used to sign the archive, or
+ * <code>null</code>.
+ * @throws IOException
+ * @throws NoSuchAlgorithmException
+ */
+ public SignedJarBuilder(@NonNull OutputStream out,
+ @Nullable PrivateKey key,
+ @Nullable X509Certificate certificate,
+ @Nullable String builtBy,
+ @Nullable String createdBy)
+ throws IOException, NoSuchAlgorithmException {
+ mOutputJar = new JarOutputStream(new BufferedOutputStream(out));
+ mOutputJar.setLevel(9);
+ mKey = key;
+ mCertificate = certificate;
+
+ if (mKey != null && mCertificate != null) {
+ mManifest = new Manifest();
+ Attributes main = mManifest.getMainAttributes();
+ main.putValue("Manifest-Version", "1.0");
+ if (builtBy != null) {
+ main.putValue("Built-By", builtBy);
+ }
+ if (createdBy != null) {
+ main.putValue("Created-By", createdBy);
+ }
+
+ mMessageDigest = MessageDigest.getInstance(DIGEST_ALGORITHM);
+ }
+ }
+
+ /**
+ * Writes a new {@link File} into the archive.
+ * @param inputFile the {@link File} to write.
+ * @param jarPath the filepath inside the archive.
+ * @throws IOException
+ */
+ public void writeFile(File inputFile, String jarPath) throws IOException {
+ // Get an input stream on the file.
+ FileInputStream fis = new FileInputStream(inputFile);
+ try {
+
+ // create the zip entry
+ JarEntry entry = new JarEntry(jarPath);
+ entry.setTime(inputFile.lastModified());
+
+ writeEntry(fis, entry);
+ } finally {
+ // close the file stream used to read the file
+ fis.close();
+ }
+ }
+
+ /**
+ * Copies the content of a Jar/Zip archive into the receiver archive.
+ * <p/>An optional {@link IZipEntryFilter} allows to selectively choose which files
+ * to copy over.
+ * @param input the {@link InputStream} for the Jar/Zip to copy.
+ * @param filter the filter or <code>null</code>
+ * @throws IOException
+ * @throws ZipAbortException if the {@link IZipEntryFilter} filter indicated that the write
+ * must be aborted.
+ */
+ public void writeZip(InputStream input, IZipEntryFilter filter)
+ throws IOException, ZipAbortException {
+ ZipInputStream zis = new ZipInputStream(input);
+
+ try {
+ // loop on the entries of the intermediary package and put them in the final package.
+ ZipEntry entry;
+ while ((entry = zis.getNextEntry()) != null) {
+ String name = entry.getName();
+
+ // do not take directories or anything inside a potential META-INF folder.
+ if (entry.isDirectory()) {
+ continue;
+ }
+
+ // ignore some of the content in META-INF/ but not all
+ if (name.startsWith("META-INF/")) {
+ // ignore the manifest file.
+ String subName = name.substring(9);
+ if ("MANIFEST.MF".equals(subName)) {
+ continue;
+ }
+
+ // special case for Maven meta-data because we really don't care about them in apks.
+ if (name.startsWith("META-INF/maven/")) {
+ continue;
+ }
+
+
+ // check for subfolder
+ int index = subName.indexOf('/');
+ if (index == -1) {
+ // no sub folder, ignores signature files.
+ if (subName.endsWith(".SF") || name.endsWith(".RSA") || name.endsWith(".DSA")) {
+ continue;
+ }
+ }
+ }
+
+ // if we have a filter, we check the entry against it
+ if (filter != null && !filter.checkEntry(name)) {
+ continue;
+ }
+
+ JarEntry newEntry;
+
+ // Preserve the STORED method of the input entry.
+ if (entry.getMethod() == JarEntry.STORED) {
+ newEntry = new JarEntry(entry);
+ } else {
+ // Create a new entry so that the compressed len is recomputed.
+ newEntry = new JarEntry(name);
+ }
+
+ writeEntry(zis, newEntry);
+
+ zis.closeEntry();
+ }
+ } finally {
+ zis.close();
+ }
+ }
+
+ /**
+ * Closes the Jar archive by creating the manifest, and signing the archive.
+ * @throws IOException
+ * @throws SigningException
+ */
+ public void close() throws IOException, SigningException {
+ if (mManifest != null) {
+ // write the manifest to the jar file
+ mOutputJar.putNextEntry(new JarEntry(JarFile.MANIFEST_NAME));
+ mManifest.write(mOutputJar);
+
+ try {
+ // CERT.SF
+ Signature signature = Signature.getInstance("SHA1with" + mKey.getAlgorithm());
+ signature.initSign(mKey);
+ mOutputJar.putNextEntry(new JarEntry("META-INF/CERT.SF"));
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ writeSignatureFile(baos);
+ byte[] signedData = baos.toByteArray();
+ mOutputJar.write(signedData);
+
+ // CERT.*
+ mOutputJar.putNextEntry(new JarEntry("META-INF/CERT." + mKey.getAlgorithm()));
+ writeSignatureBlock(new CMSProcessableByteArray(signedData), mCertificate, mKey);
+ } catch (Exception e) {
+ throw new SigningException(e);
+ }
+ }
+
+ mOutputJar.close();
+ mOutputJar = null;
+ }
+
+ /**
+ * Clean up of the builder for interrupted workflow.
+ * This does nothing if {@link #close()} was called successfully.
+ */
+ public void cleanUp() {
+ if (mOutputJar != null) {
+ try {
+ mOutputJar.close();
+ } catch (IOException e) {
+ // pass
+ }
+ }
+ }
+
+ /**
+ * Adds an entry to the output jar, and write its content from the {@link InputStream}
+ * @param input The input stream from where to write the entry content.
+ * @param entry the entry to write in the jar.
+ * @throws IOException
+ */
+ private void writeEntry(InputStream input, JarEntry entry) throws IOException {
+ // add the entry to the jar archive
+ mOutputJar.putNextEntry(entry);
+
+ // read the content of the entry from the input stream, and write it into the archive.
+ int count;
+ while ((count = input.read(mBuffer)) != -1) {
+ mOutputJar.write(mBuffer, 0, count);
+
+ // update the digest
+ if (mMessageDigest != null) {
+ mMessageDigest.update(mBuffer, 0, count);
+ }
+ }
+
+ // close the entry for this file
+ mOutputJar.closeEntry();
+
+ if (mManifest != null) {
+ // update the manifest for this entry.
+ Attributes attr = mManifest.getAttributes(entry.getName());
+ if (attr == null) {
+ attr = new Attributes();
+ mManifest.getEntries().put(entry.getName(), attr);
+ }
+ attr.putValue(DIGEST_ATTR,
+ new String(Base64.encode(mMessageDigest.digest()), "ASCII"));
+ }
+ }
+
+ /** Writes a .SF file with a digest to the manifest. */
+ private void writeSignatureFile(OutputStream out)
+ throws IOException, GeneralSecurityException {
+ Manifest sf = new Manifest();
+ Attributes main = sf.getMainAttributes();
+ main.putValue("Signature-Version", "1.0");
+ main.putValue("Created-By", "1.0 (Android)");
+
+ MessageDigest md = MessageDigest.getInstance(DIGEST_ALGORITHM);
+ PrintStream print = new PrintStream(
+ new DigestOutputStream(new ByteArrayOutputStream(), md),
+ true, "UTF-8");
+
+ // Digest of the entire manifest
+ mManifest.write(print);
+ print.flush();
+ main.putValue(DIGEST_MANIFEST_ATTR, new String(Base64.encode(md.digest()), "ASCII"));
+
+ Map<String, Attributes> entries = mManifest.getEntries();
+ for (Map.Entry<String, Attributes> entry : entries.entrySet()) {
+ // Digest of the manifest stanza for this entry.
+ print.print("Name: " + entry.getKey() + "\r\n");
+ for (Map.Entry<Object, Object> att : entry.getValue().entrySet()) {
+ print.print(att.getKey() + ": " + att.getValue() + "\r\n");
+ }
+ print.print("\r\n");
+ print.flush();
+
+ Attributes sfAttr = new Attributes();
+ sfAttr.putValue(DIGEST_ATTR, new String(Base64.encode(md.digest()), "ASCII"));
+ sf.getEntries().put(entry.getKey(), sfAttr);
+ }
+ CountOutputStream cout = new CountOutputStream(out);
+ sf.write(cout);
+
+ // A bug in the java.util.jar implementation of Android platforms
+ // up to version 1.6 will cause a spurious IOException to be thrown
+ // if the length of the signature file is a multiple of 1024 bytes.
+ // As a workaround, add an extra CRLF in this case.
+ if ((cout.size() % 1024) == 0) {
+ cout.write('\r');
+ cout.write('\n');
+ }
+ }
+
+ /** Write the certificate file with a digital signature. */
+ private void writeSignatureBlock(CMSTypedData data, X509Certificate publicKey,
+ PrivateKey privateKey)
+ throws IOException,
+ CertificateEncodingException,
+ OperatorCreationException,
+ CMSException {
+
+ ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>();
+ certList.add(publicKey);
+ JcaCertStore certs = new JcaCertStore(certList);
+
+ CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+ ContentSigner sha1Signer = new JcaContentSignerBuilder(
+ "SHA1with" + privateKey.getAlgorithm())
+ .build(privateKey);
+ gen.addSignerInfoGenerator(
+ new JcaSignerInfoGeneratorBuilder(
+ new JcaDigestCalculatorProviderBuilder()
+ .build())
+ .setDirectSignature(true)
+ .build(sha1Signer, publicKey));
+ gen.addCertificates(certs);
+ CMSSignedData sigData = gen.generate(data, false);
+
+ ASN1InputStream asn1 = new ASN1InputStream(sigData.getEncoded());
+ DEROutputStream dos = new DEROutputStream(mOutputJar);
+ dos.writeObject(asn1.readObject());
+
+ dos.flush();
+ dos.close();
+ asn1.close();
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/signing/SigningException.java b/build-system/builder/src/main/java/com/android/builder/signing/SigningException.java
new file mode 100644
index 0000000..2691517
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/signing/SigningException.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.signing;
+
+/**
+ */
+public class SigningException extends Exception {
+
+ public SigningException() {
+ super();
+ }
+
+ public SigningException(String message) {
+ super(message);
+ }
+
+ public SigningException(String message, Throwable throwable) {
+ super(message, throwable);
+ }
+
+ public SigningException(Throwable throwable) {
+ super(throwable);
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/testing/ConnectedDevice.java b/build-system/builder/src/main/java/com/android/builder/testing/ConnectedDevice.java
new file mode 100644
index 0000000..7e6ae77
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/testing/ConnectedDevice.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.testing;
+
+import com.android.annotations.NonNull;
+import com.android.builder.testing.api.DeviceConnector;
+import com.android.builder.testing.api.DeviceException;
+import com.android.ddmlib.AdbCommandRejectedException;
+import com.android.ddmlib.IDevice;
+import com.android.ddmlib.IShellOutputReceiver;
+import com.android.ddmlib.ShellCommandUnresponsiveException;
+import com.android.ddmlib.TimeoutException;
+import com.android.utils.ILogger;
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Local device connected to with ddmlib. This is a wrapper around {@link IDevice}.
+ */
+public class ConnectedDevice extends DeviceConnector {
+
+ private final IDevice iDevice;
+
+ public ConnectedDevice(@NonNull IDevice iDevice) {
+ this.iDevice = iDevice;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ String version = iDevice.getProperty(IDevice.PROP_BUILD_VERSION);
+ boolean emulator = iDevice.isEmulator();
+
+ String name;
+ if (emulator) {
+ name = iDevice.getAvdName() != null ?
+ iDevice.getAvdName() + "(AVD)" :
+ iDevice.getSerialNumber();
+ } else {
+ String model = iDevice.getProperty(IDevice.PROP_DEVICE_MODEL);
+ name = model != null ? model : iDevice.getSerialNumber();
+ }
+
+ return version != null ? name + " - " + version : name;
+ }
+
+ @Override
+ public void connect(int timeout, ILogger logger) throws TimeoutException {
+ // nothing to do here
+ }
+
+ @Override
+ public void disconnect(int timeout, ILogger logger) throws TimeoutException {
+ // nothing to do here
+ }
+
+ @Override
+ public void installPackage(@NonNull File apkFile, int timeout, ILogger logger) throws DeviceException {
+ try {
+ iDevice.installPackage(apkFile.getAbsolutePath(), true /*reinstall*/);
+ } catch (Exception e) {
+ logger.error(e, "Unable to install " + apkFile.getAbsolutePath());
+ throw new DeviceException(e);
+ }
+ }
+
+ @Override
+ public void uninstallPackage(@NonNull String packageName, int timeout, ILogger logger) throws DeviceException {
+ try {
+ iDevice.uninstallPackage(packageName);
+ } catch (Exception e) {
+ logger.error(e, "Unable to uninstall " + packageName);
+ throw new DeviceException(e);
+ }
+ }
+
+ @Override
+ public void executeShellCommand(String command, IShellOutputReceiver receiver,
+ long maxTimeToOutputResponse, TimeUnit maxTimeUnits)
+ throws TimeoutException, AdbCommandRejectedException,
+ ShellCommandUnresponsiveException, IOException {
+ iDevice.executeShellCommand(command, receiver, maxTimeToOutputResponse, maxTimeUnits);
+ }
+
+ @Override
+ public int getApiLevel() {
+ String sdkVersion = iDevice.getProperty(IDevice.PROP_BUILD_API_LEVEL);
+ if (sdkVersion != null) {
+ try {
+ return Integer.valueOf(sdkVersion);
+ } catch (NumberFormatException e) {
+
+ }
+ }
+
+ // can't get it, return 0.
+ return 0;
+ }
+
+ @NonNull
+ @Override
+ public List<String> getAbis() {
+ List<String> abis = Lists.newArrayListWithExpectedSize(2);
+ String abi = iDevice.getProperty(IDevice.PROP_DEVICE_CPU_ABI);
+ if (abi != null) {
+ abis.add(abi);
+ }
+
+ abi = iDevice.getProperty(IDevice.PROP_DEVICE_CPU_ABI2);
+ if (abi != null) {
+ abis.add(abi);
+ }
+
+ return abis;
+ }
+
+ @Override
+ public int getDensity() {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public int getHeight() {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public int getWidth() {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/testing/ConnectedDeviceProvider.java b/build-system/builder/src/main/java/com/android/builder/testing/ConnectedDeviceProvider.java
new file mode 100644
index 0000000..ae81aa2
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/testing/ConnectedDeviceProvider.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.testing;
+
+import com.android.annotations.NonNull;
+import com.android.builder.SdkParser;
+import com.android.builder.testing.api.DeviceConnector;
+import com.android.builder.testing.api.DeviceException;
+import com.android.builder.testing.api.DeviceProvider;
+import com.android.ddmlib.AndroidDebugBridge;
+import com.android.ddmlib.IDevice;
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+/**
+ * DeviceProvider for locally connected devices. Basically returns the list of devices that
+ * are currently connected at the time {@link #init()} is called.
+ */
+public class ConnectedDeviceProvider extends DeviceProvider {
+
+
+ @NonNull
+ private final SdkParser sdkParser;
+
+ @NonNull
+ private final List<ConnectedDevice> localDevices = Lists.newArrayList();
+
+ public ConnectedDeviceProvider(@NonNull SdkParser sdkParser) {
+ this.sdkParser = sdkParser;
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return "connected";
+ }
+
+ @Override
+ @NonNull
+ public List<? extends DeviceConnector> getDevices() {
+ return localDevices;
+ }
+
+ @Override
+ public void init() throws DeviceException {
+ try {
+ AndroidDebugBridge.initIfNeeded(false /*clientSupport*/);
+
+ AndroidDebugBridge bridge = AndroidDebugBridge.createBridge(
+ sdkParser.getAdb().getAbsolutePath(), false /*forceNewBridge*/);
+
+ long timeOut = 30000; // 30 sec
+ int sleepTime = 1000;
+ while (!bridge.hasInitialDeviceList() && timeOut > 0) {
+ Thread.sleep(sleepTime);
+ timeOut -= sleepTime;
+ }
+
+ if (timeOut <= 0 && !bridge.hasInitialDeviceList()) {
+ throw new RuntimeException("Timeout getting device list.", null);
+ }
+
+ IDevice[] devices = bridge.getDevices();
+
+ if (devices.length == 0) {
+ throw new RuntimeException("No connected devices!", null);
+ }
+
+ for (IDevice iDevice : devices) {
+ localDevices.add(new ConnectedDevice(iDevice));
+ }
+ } catch (Exception e) {
+ throw new DeviceException(e);
+ }
+ }
+
+ @Override
+ public void terminate() throws DeviceException {
+ // nothing to be done here.
+ }
+
+ @Override
+ public int getTimeout() {
+ return 0;
+ }
+
+ @Override
+ public boolean isConfigured() {
+ return true;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/testing/SimpleTestRunner.java b/build-system/builder/src/main/java/com/android/builder/testing/SimpleTestRunner.java
new file mode 100644
index 0000000..237d871
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/testing/SimpleTestRunner.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.testing;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.internal.testing.SimpleTestCallable;
+import com.android.builder.testing.api.DeviceConnector;
+import com.android.builder.testing.api.TestException;
+import com.android.ide.common.internal.WaitableExecutor;
+import com.android.utils.ILogger;
+
+import java.io.File;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Basic {@link TestRunner} running tests on all devices.
+ */
+public class SimpleTestRunner implements TestRunner {
+
+ @Override
+ public boolean runTests(
+ @NonNull String projectName,
+ @NonNull String variantName,
+ @NonNull File testApk,
+ @Nullable File testedApk,
+ @NonNull TestData testData,
+ @NonNull List<? extends DeviceConnector> deviceList,
+ int maxThreads,
+ int timeout,
+ @NonNull File resultsDir,
+ @NonNull ILogger logger) throws TestException, InterruptedException {
+
+ WaitableExecutor<Boolean> executor = new WaitableExecutor<Boolean>(maxThreads);
+
+ for (DeviceConnector device : deviceList) {
+ if (filterOutDevice(device, testData, logger, projectName, variantName)) {
+ executor.execute(new SimpleTestCallable(device, projectName, variantName,
+ testApk, testedApk, testData,
+ resultsDir, timeout, logger));
+ }
+ }
+
+ List<WaitableExecutor.TaskResult<Boolean>> results = executor.waitForAllTasks();
+
+ boolean success = true;
+
+ // check if one test failed or if there was an exception.
+ for (WaitableExecutor.TaskResult<Boolean> result : results) {
+ if (result.value != null) {
+ // true means there are failed tests!
+ success &= !result.value;
+ } else {
+ success = false;
+ logger.error(result.exception, null);
+ }
+ }
+
+ return success;
+ }
+
+ private boolean filterOutDevice(@NonNull DeviceConnector device, @NonNull TestData testData,
+ @NonNull ILogger logger,
+ @NonNull String projectName, @NonNull String variantName) {
+ int deviceApiLevel = device.getApiLevel();
+ if (deviceApiLevel == 0) {
+ logger.info("Skipping device '%s' for '%s:%s': Unknown API Level",
+ device.getName(), projectName, variantName);
+ return false;
+ }
+
+ if (testData.getMinSdkVersion() > deviceApiLevel) {
+ logger.info("Skipping device '%s' for '%s:%s'",
+ device.getName(), projectName, variantName);
+
+ return false;
+ }
+
+ Set<String> appAbis = testData.getSupportedAbis();
+ if (appAbis != null) {
+ List<String> deviceAbis = device.getAbis();
+ if (deviceAbis == null || deviceAbis.isEmpty()) {
+ logger.info("Skipping device '%s' for '%s:%s': Unknown ABI",
+ device.getName(), projectName, variantName);
+ return false;
+ }
+
+ boolean compatibleAbi = false;
+ for (String deviceAbi : deviceAbis) {
+ if (appAbis.contains(deviceAbi)) {
+ compatibleAbi = true;
+ }
+ }
+
+ if (!compatibleAbi) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/testing/TestData.java b/build-system/builder/src/main/java/com/android/builder/testing/TestData.java
new file mode 100644
index 0000000..a736026
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/testing/TestData.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.testing;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.util.Set;
+
+/**
+ */
+public interface TestData {
+
+ /**
+ * Returns the package name.
+ *
+ * @return the package name
+ */
+ @NonNull
+ String getPackageName();
+
+ /**
+ * Returns the tested package name. This can be empty if the test package is self-contained.
+ *
+ * @return the package name or null.
+ */
+ @Nullable
+ String getTestedPackageName();
+
+ @NonNull
+ String getInstrumentationRunner();
+
+ @NonNull
+ Boolean getHandleProfiling();
+
+ @NonNull
+ Boolean getFunctionalTest();
+
+ int getMinSdkVersion();
+
+ /**
+ * List of supported ABIs. Null means all.
+ * @return a list of abi or null for all
+ */
+ @Nullable
+ Set<String> getSupportedAbis();
+}
diff --git a/build-system/builder/src/main/java/com/android/builder/testing/TestRunner.java b/build-system/builder/src/main/java/com/android/builder/testing/TestRunner.java
new file mode 100644
index 0000000..e0dc29c
--- /dev/null
+++ b/build-system/builder/src/main/java/com/android/builder/testing/TestRunner.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.testing;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.testing.api.DeviceConnector;
+import com.android.builder.testing.api.TestException;
+import com.android.utils.ILogger;
+import com.google.common.annotations.Beta;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * A test runner able to run tests on a list of {@link DeviceConnector}
+ */
+@Beta
+public interface TestRunner {
+
+ /**
+ * Returns true if the tests succeeded.
+ *
+ * @param projectName
+ * @param variantName
+ * @param testApk
+ * @param testedApk
+ * @param testData
+ * @param deviceList
+ * @param maxThreads the max number of threads to run in parallel. 0 means unlimited.
+ * @param timeout
+ * @param resultsDir
+ * @param logger
+ * @return true if the test succeed
+ *
+ * @throws TestException
+ * @throws InterruptedException
+ */
+ boolean runTests(
+ @NonNull String projectName,
+ @NonNull String variantName,
+ @NonNull File testApk,
+ @Nullable File testedApk,
+ @NonNull TestData testData,
+ @NonNull List<? extends DeviceConnector> deviceList,
+ int maxThreads,
+ int timeout,
+ @NonNull File resultsDir,
+ @NonNull ILogger logger) throws TestException, InterruptedException;
+}
diff --git a/build-system/builder/src/main/resources/com/android/builder/internal/AndroidManifest.template b/build-system/builder/src/main/resources/com/android/builder/internal/AndroidManifest.template
new file mode 100644
index 0000000..ed50233
--- /dev/null
+++ b/build-system/builder/src/main/resources/com/android/builder/internal/AndroidManifest.template
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="#PACKAGE#">
+
+ <uses-sdk android:minSdkVersion="#MINSDKVERSION#" android:targetSdkVersion="#TARGETSDKVERSION#" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="#TESTRUNNER#"
+ android:targetPackage="#TESTEDPACKAGE#"
+ android:handleProfiling="#HANDLEPROFILING#"
+ android:functionalTest="#FUNCTIONALTEST#"
+ android:label="Tests for #TESTEDPACKAGE#"/>
+</manifest>
diff --git a/build-system/builder/src/test/java/com/android/builder/DefaultProductFlavorTest.java b/build-system/builder/src/test/java/com/android/builder/DefaultProductFlavorTest.java
new file mode 100644
index 0000000..f618085
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/DefaultProductFlavorTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder;
+
+import com.android.builder.model.ProductFlavor;
+import junit.framework.TestCase;
+
+public class DefaultProductFlavorTest extends TestCase {
+
+ private DefaultProductFlavor mDefault;
+ private DefaultProductFlavor mDefault2;
+ private DefaultProductFlavor mCustom;
+
+ @Override
+ protected void setUp() throws Exception {
+ mDefault = new DefaultProductFlavor("default");
+ mDefault2 = new DefaultProductFlavor("default2");
+
+ mCustom = new DefaultProductFlavor("custom");
+ mCustom.setMinSdkVersion(42);
+ mCustom.setTargetSdkVersion(43);
+ mCustom.setRenderscriptTargetApi(17);
+ mCustom.setVersionCode(44);
+ mCustom.setVersionName("42.0");
+ mCustom.setPackageName("com.forty.two");
+ mCustom.setTestPackageName("com.forty.two.test");
+ mCustom.setTestInstrumentationRunner("com.forty.two.test.Runner");
+ mCustom.setTestHandleProfiling(true);
+ mCustom.setTestFunctionalTest(true);
+ }
+
+ public void testMergeOnDefault() {
+ ProductFlavor flavor = mCustom.mergeOver(mDefault);
+
+ assertEquals(42, flavor.getMinSdkVersion());
+ assertEquals(43, flavor.getTargetSdkVersion());
+ assertEquals(17, flavor.getRenderscriptTargetApi());
+ assertEquals(44, flavor.getVersionCode());
+ assertEquals("42.0", flavor.getVersionName());
+ assertEquals("com.forty.two", flavor.getPackageName());
+ assertEquals("com.forty.two.test", flavor.getTestPackageName());
+ assertEquals("com.forty.two.test.Runner", flavor.getTestInstrumentationRunner());
+ assertEquals(Boolean.TRUE, flavor.getTestHandleProfiling());
+ assertEquals(Boolean.TRUE, flavor.getTestFunctionalTest());
+ }
+
+ public void testMergeOnCustom() {
+ ProductFlavor flavor = mDefault.mergeOver(mCustom);
+
+ assertEquals(42, flavor.getMinSdkVersion());
+ assertEquals(43, flavor.getTargetSdkVersion());
+ assertEquals(17, flavor.getRenderscriptTargetApi());
+ assertEquals(44, flavor.getVersionCode());
+ assertEquals("42.0", flavor.getVersionName());
+ assertEquals("com.forty.two", flavor.getPackageName());
+ assertEquals("com.forty.two.test", flavor.getTestPackageName());
+ assertEquals("com.forty.two.test.Runner", flavor.getTestInstrumentationRunner());
+ assertEquals(Boolean.TRUE, flavor.getTestHandleProfiling());
+ assertEquals(Boolean.TRUE, flavor.getTestFunctionalTest());
+ }
+
+ public void testMergeDefaultOnDefault() {
+ ProductFlavor flavor = mDefault.mergeOver(mDefault2);
+
+ assertEquals(-1, flavor.getMinSdkVersion());
+ assertEquals(-1, flavor.getTargetSdkVersion());
+ assertEquals(-1, flavor.getRenderscriptTargetApi());
+ assertEquals(-1, flavor.getVersionCode());
+ assertNull(flavor.getVersionName());
+ assertNull(flavor.getPackageName());
+ assertNull(flavor.getTestPackageName());
+ assertNull(flavor.getTestInstrumentationRunner());
+ assertNull(flavor.getTestHandleProfiling());
+ assertNull(flavor.getTestFunctionalTest());
+ }
+}
diff --git a/build-system/builder/src/test/java/com/android/builder/MockSourceProvider.java b/build-system/builder/src/test/java/com/android/builder/MockSourceProvider.java
new file mode 100644
index 0000000..bb5fe39
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/MockSourceProvider.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder;
+
+import com.android.annotations.NonNull;
+import com.android.builder.model.SourceProvider;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Implementation of SourceProvider for testing that provides the default convention paths.
+ */
+class MockSourceProvider implements SourceProvider {
+
+ public MockSourceProvider(String root) {
+ mRoot = root;
+ }
+
+ private final String mRoot;
+
+ @NonNull
+ @Override
+ public Set<File> getJavaDirectories() {
+ return Collections.singleton(new File(mRoot, "java"));
+ }
+
+ @NonNull
+ @Override
+ public Set<File> getResourcesDirectories() {
+ return Collections.singleton(new File(mRoot, "resources"));
+ }
+
+ @Override
+ @NonNull
+ public Set<File> getResDirectories() {
+ return Collections.singleton(new File(mRoot, "res"));
+ }
+
+ @Override
+ @NonNull
+ public Set<File> getAssetsDirectories() {
+ return Collections.singleton(new File(mRoot, "assets"));
+ }
+
+ @Override
+ @NonNull
+ public File getManifestFile() {
+ return new File(mRoot, "AndroidManifest.xml");
+ }
+
+ @Override
+ @NonNull
+ public Set<File> getAidlDirectories() {
+ return Collections.singleton(new File(mRoot, "aidl"));
+ }
+
+ @Override
+ @NonNull
+ public Set<File> getRenderscriptDirectories() {
+ return Collections.singleton(new File(mRoot, "rs"));
+ }
+
+ @Override
+ @NonNull
+ public Set<File> getJniDirectories() {
+ return Collections.singleton(new File(mRoot, "jni"));
+ }
+}
diff --git a/build-system/builder/src/test/java/com/android/builder/VariantConfigurationTest.java b/build-system/builder/src/test/java/com/android/builder/VariantConfigurationTest.java
new file mode 100644
index 0000000..e1ffc3d
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/VariantConfigurationTest.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import junit.framework.TestCase;
+
+import java.io.File;
+
+public class VariantConfigurationTest extends TestCase {
+
+ private DefaultProductFlavor mDefaultConfig;
+ private DefaultProductFlavor mFlavorConfig;
+ private DefaultBuildType mBuildType;
+
+ private static class ManifestParserMock implements ManifestParser {
+
+ private final String mPackageName;
+
+ ManifestParserMock(String packageName) {
+ mPackageName = packageName;
+ }
+
+ @Nullable
+ @Override
+ public String getPackage(@NonNull File manifestFile) {
+ return mPackageName;
+ }
+
+ @Override
+ public int getMinSdkVersion(@NonNull File manifestFile) {
+ return 0;
+ }
+
+ @Override
+ public int getTargetSdkVersion(@NonNull File manifestFile) {
+ return -1;
+ }
+
+ @Nullable
+ @Override
+ public String getVersionName(@NonNull File manifestFile) {
+ return "1.0";
+ }
+
+ @Override
+ public int getVersionCode(@NonNull File manifestFile) {
+ return 1;
+ }
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ mDefaultConfig = new DefaultProductFlavor("main");
+ mFlavorConfig = new DefaultProductFlavor("flavor");
+ mBuildType = new DefaultBuildType("debug");
+ }
+
+ public void testPackageOverrideNone() {
+ VariantConfiguration variant = getVariant();
+
+ assertNull(variant.getPackageOverride());
+ }
+
+ public void testPackageOverridePackageFromFlavor() {
+ mFlavorConfig.setPackageName("foo.bar");
+
+ VariantConfiguration variant = getVariant();
+
+ assertEquals("foo.bar", variant.getPackageOverride());
+ }
+
+ public void testPackageOverridePackageFromFlavorWithSuffix() {
+ mFlavorConfig.setPackageName("foo.bar");
+ mBuildType.setPackageNameSuffix(".fortytwo");
+
+ VariantConfiguration variant = getVariant();
+
+ assertEquals("foo.bar.fortytwo", variant.getPackageOverride());
+ }
+
+ public void testPackageOverridePackageFromFlavorWithSuffix2() {
+ mFlavorConfig.setPackageName("foo.bar");
+ mBuildType.setPackageNameSuffix("fortytwo");
+
+ VariantConfiguration variant = getVariant();
+
+ assertEquals("foo.bar.fortytwo", variant.getPackageOverride());
+ }
+
+ public void testPackageOverridePackageWithSuffixOnly() {
+
+ mBuildType.setPackageNameSuffix("fortytwo");
+
+ VariantConfiguration variant = getVariantWithManifestPackage("fake.package.name");
+
+ assertEquals("fake.package.name.fortytwo", variant.getPackageOverride());
+ }
+
+ public void testVersionNameFromFlavorWithSuffix() {
+ mFlavorConfig.setVersionName("1.0");
+ mBuildType.setVersionNameSuffix("-DEBUG");
+
+ VariantConfiguration variant = getVariant();
+
+ assertEquals("1.0-DEBUG", variant.getVersionName());
+ }
+
+ public void testVersionNameWithSuffixOnly() {
+ mBuildType.setVersionNameSuffix("-DEBUG");
+
+ VariantConfiguration variant = getVariantWithManifestVersion("2.0b1");
+
+ assertEquals("2.0b1-DEBUG", variant.getVersionName());
+ }
+
+ private VariantConfiguration getVariant() {
+ VariantConfiguration variant = new VariantConfiguration(
+ mDefaultConfig, new MockSourceProvider("main"),
+ mBuildType, new MockSourceProvider("debug"),
+ VariantConfiguration.Type.DEFAULT) {
+ // don't do validation.
+ @Override
+ protected void validate() {
+
+ }
+ };
+
+ variant.addProductFlavor(mFlavorConfig, new MockSourceProvider("custom"), "");
+
+ return variant;
+ }
+
+ private VariantConfiguration getVariantWithManifestPackage(final String packageName) {
+ VariantConfiguration variant = new VariantConfiguration(
+ mDefaultConfig, new MockSourceProvider("main"),
+ mBuildType, new MockSourceProvider("debug"),
+ VariantConfiguration.Type.DEFAULT) {
+ @Override
+ public String getPackageFromManifest() {
+ return packageName;
+ }
+ // don't do validation.
+ @Override
+ protected void validate() {
+
+ }
+ };
+
+ variant.addProductFlavor(mFlavorConfig, new MockSourceProvider("custom"), "");
+ return variant;
+ }
+
+ private VariantConfiguration getVariantWithManifestVersion(final String versionName) {
+ VariantConfiguration variant = new VariantConfiguration(
+ mDefaultConfig, new MockSourceProvider("main"),
+ mBuildType, new MockSourceProvider("debug"),
+ VariantConfiguration.Type.DEFAULT) {
+ @Override
+ public String getVersionNameFromManifest() {
+ return versionName;
+ }
+ // don't do validation.
+ @Override
+ protected void validate() {
+
+ }
+ };
+
+ variant.addProductFlavor(mFlavorConfig, new MockSourceProvider("custom"), "");
+ return variant;
+ }
+}
diff --git a/build-system/builder/src/test/java/com/android/builder/compiling/BuildConfigGeneratorTest.java b/build-system/builder/src/test/java/com/android/builder/compiling/BuildConfigGeneratorTest.java
new file mode 100644
index 0000000..0bb2200
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/compiling/BuildConfigGeneratorTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.compiling;
+
+import com.android.builder.AndroidBuilder;
+import com.google.common.base.Charsets;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.util.List;
+
+@SuppressWarnings("ResultOfMethodCallIgnored")
+public class BuildConfigGeneratorTest extends TestCase {
+ public void testFalse() throws Exception {
+ File tempDir = Files.createTempDir();
+ BuildConfigGenerator generator = new BuildConfigGenerator(tempDir.getPath(),
+ "my.app.pkg");
+
+ generator.addField("boolean", "DEBUG", "false").generate();
+
+ File file = generator.getBuildConfigFile();
+ assertTrue(file.exists());
+ String actual = Files.toString(file, Charsets.UTF_8);
+ assertEquals(
+ "/**\n" +
+ " * Automatically generated file. DO NOT MODIFY\n" +
+ " */\n" +
+ "package my.app.pkg;\n" +
+ "\n" +
+ "public final class BuildConfig {\n" +
+ " public static final boolean DEBUG = false;\n" +
+ "}\n", actual);
+ file.delete();
+ tempDir.delete();
+ }
+
+ public void testTrue() throws Exception {
+ File tempDir = Files.createTempDir();
+ BuildConfigGenerator generator = new BuildConfigGenerator(tempDir.getPath(),
+ "my.app.pkg");
+ generator.addField("boolean", "DEBUG", "Boolean.parseBoolean(\"true\")").generate();
+
+ File file = generator.getBuildConfigFile();
+ assertTrue(file.exists());
+ String actual = Files.toString(file, Charsets.UTF_8);
+ assertEquals(
+ "/**\n" +
+ " * Automatically generated file. DO NOT MODIFY\n" +
+ " */\n" +
+ "package my.app.pkg;\n" +
+ "\n" +
+ "public final class BuildConfig {\n" +
+ " public static final boolean DEBUG = Boolean.parseBoolean(\"true\");\n" +
+ "}\n", actual);
+ file.delete();
+ tempDir.delete();
+ }
+
+ public void testExtra() throws Exception {
+ File tempDir = Files.createTempDir();
+ BuildConfigGenerator generator = new BuildConfigGenerator(tempDir.getPath(),
+ "my.app.pkg");
+
+ List<Object> items = Lists.newArrayList();
+ items.add("Extra line");
+ items.add(AndroidBuilder.createClassField("int", "EXTRA", "42"));
+
+ generator.addItems(items).generate();
+
+ File file = generator.getBuildConfigFile();
+ assertTrue(file.exists());
+ String actual = Files.toString(file, Charsets.UTF_8);
+ assertEquals(
+ "/**\n" +
+ " * Automatically generated file. DO NOT MODIFY\n" +
+ " */\n" +
+ "package my.app.pkg;\n" +
+ "\n" +
+ "public final class BuildConfig {\n" +
+ " // Extra line\n" +
+ " public static final int EXTRA = 42;\n" +
+ "}\n", actual);
+ file.delete();
+ tempDir.delete();
+ }
+}
diff --git a/build-system/builder/src/test/java/com/android/builder/internal/SymbolLoaderTest.java b/build-system/builder/src/test/java/com/android/builder/internal/SymbolLoaderTest.java
new file mode 100644
index 0000000..e5b9170
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/internal/SymbolLoaderTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.builder.internal;
+
+import com.android.utils.NullLogger;
+import com.google.common.base.Charsets;
+import com.google.common.collect.Table;
+import com.google.common.io.Files;
+import junit.framework.TestCase;
+
+import java.io.File;
+
+@SuppressWarnings("javadoc")
+public class SymbolLoaderTest extends TestCase {
+ public void test() throws Exception {
+ String r = "" +
+ "int xml authenticator 0x7f040000\n";
+ File file = File.createTempFile(getClass().getSimpleName(), "txt");
+ file.deleteOnExit();
+ Files.write(r, file, Charsets.UTF_8);
+ SymbolLoader loader = new SymbolLoader(file, NullLogger.getLogger());
+ loader.load();
+ Table<String, String, SymbolLoader.SymbolEntry> symbols = loader.getSymbols();
+ assertNotNull(symbols);
+ assertEquals(1, symbols.size());
+ assertNotNull(symbols.get("xml", "authenticator"));
+ assertEquals("0x7f040000", symbols.get("xml", "authenticator").getValue());
+ }
+
+ public void testStyleables() throws Exception {
+ String r = "" +
+ "int[] styleable LimitedSizeLinearLayout { 0x7f010000, 0x7f010001 }\n" +
+ "int styleable LimitedSizeLinearLayout_max_height 1\n" +
+ "int styleable LimitedSizeLinearLayout_max_width 0\n" +
+ "int xml authenticator 0x7f040000\n";
+ File file = File.createTempFile(getClass().getSimpleName(), "txt");
+ file.deleteOnExit();
+ Files.write(r, file, Charsets.UTF_8);
+ SymbolLoader loader = new SymbolLoader(file, NullLogger.getLogger());
+ loader.load();
+ Table<String, String, SymbolLoader.SymbolEntry> symbols = loader.getSymbols();
+ assertNotNull(symbols);
+ assertEquals(4, symbols.size());
+ }
+}
diff --git a/build-system/builder/src/test/java/com/android/builder/internal/SymbolWriterTest.java b/build-system/builder/src/test/java/com/android/builder/internal/SymbolWriterTest.java
new file mode 100644
index 0000000..a6f8595
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/internal/SymbolWriterTest.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.builder.internal;
+
+import com.android.utils.NullLogger;
+import com.google.common.base.Charsets;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Table;
+import com.google.common.io.Files;
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.util.List;
+
+@SuppressWarnings("javadoc")
+public class SymbolWriterTest extends TestCase {
+ private void check(String packageName, String rJava, String rValues, String... rTexts)
+ throws Exception {
+ if (rValues == null) {
+ if (rTexts.length == 1) {
+ rValues = rTexts[0];
+ } else {
+ throw new IllegalArgumentException(
+ "Can't have a null rValues with rTexts.length!=1");
+ }
+ }
+
+ // Load the symbol values
+ // 1. write rText in a temp file
+ File file = File.createTempFile(getClass().getSimpleName(), "txt");
+ file.deleteOnExit();
+ Files.write(rValues, file, Charsets.UTF_8);
+ // 2. load symbol from temp file.
+ SymbolLoader symbolValues = new SymbolLoader(file, NullLogger.getLogger());
+ symbolValues.load();
+ Table<String, String, SymbolLoader.SymbolEntry> values = symbolValues.getSymbols();
+ assertNotNull(values);
+
+
+ // Load the symbols to write
+ List<SymbolLoader> symbolList = Lists.newArrayListWithCapacity(rTexts.length);
+ for (String rText : rTexts) {
+ // 1. write rText in a temp file
+ file = File.createTempFile(getClass().getSimpleName(), "txt");
+ file.deleteOnExit();
+ Files.write(rText, file, Charsets.UTF_8);
+ // 2. load symbol from temp file.
+ SymbolLoader loader = new SymbolLoader(file, NullLogger.getLogger());
+ loader.load();
+ Table<String, String, SymbolLoader.SymbolEntry> symbols = loader.getSymbols();
+ assertNotNull(symbols);
+ symbolList.add(loader);
+ }
+
+ // Write symbols
+ File outFolder = Files.createTempDir();
+ outFolder.mkdirs();
+
+ SymbolWriter writer = new SymbolWriter(outFolder.getPath(), packageName, symbolValues);
+ for (SymbolLoader symbolLoader : symbolList) {
+ writer.addSymbolsToWrite(symbolLoader);
+ }
+ writer.write();
+
+ String contents = Files.toString(new File(outFolder,
+ packageName.replace('.', File.separatorChar) + File.separator + "R.java"),
+ Charsets.UTF_8);
+
+ // Ensure we wrote what was expected
+ assertEquals(rJava, contents.replaceAll("\t", " "));
+ }
+
+ public void test1() throws Exception {
+ check(
+ // Package
+ "test.pkg",
+
+ // R.java
+ "/* AUTO-GENERATED FILE. DO NOT MODIFY.\n" +
+ " *\n" +
+ " * This class was automatically generated by the\n" +
+ " * aapt tool from the resource data it found. It\n" +
+ " * should not be modified by hand.\n" +
+ " */\n" +
+ "package test.pkg;\n" +
+ "\n" +
+ "public final class R {\n" +
+ " public static final class xml {\n" +
+ " public static final int authenticator = 0x7f040000;\n" +
+ " }\n" +
+ "}\n",
+
+ // R values
+ null,
+
+ // R.txt
+ "int xml authenticator 0x7f040000\n"
+ );
+ }
+
+ public void test2() throws Exception {
+ check(
+ // Package
+ "test.pkg",
+
+ // R.java
+ "/* AUTO-GENERATED FILE. DO NOT MODIFY.\n" +
+ " *\n" +
+ " * This class was automatically generated by the\n" +
+ " * aapt tool from the resource data it found. It\n" +
+ " * should not be modified by hand.\n" +
+ " */\n" +
+ "package test.pkg;\n" +
+ "\n" +
+ "public final class R {\n" +
+ " public static final class drawable {\n" +
+ " public static final int foobar = 0x7f020000;\n" +
+ " public static final int ic_launcher = 0x7f020001;\n" +
+ " }\n" +
+ " public static final class string {\n" +
+ " public static final int app_name = 0x7f030000;\n" +
+ " public static final int lib1 = 0x7f030001;\n" +
+ " }\n" +
+ " public static final class style {\n" +
+ " public static final int AppBaseTheme = 0x7f040000;\n" +
+ " public static final int AppTheme = 0x7f040001;\n" +
+ " }\n" +
+ "}\n",
+
+ // R values
+ "int drawable foobar 0x7f020000\n" +
+ "int drawable ic_launcher 0x7f020001\n" +
+ "int string app_name 0x7f030000\n" +
+ "int string lib1 0x7f030001\n" +
+ "int style AppBaseTheme 0x7f040000\n" +
+ "int style AppTheme 0x7f040001\n",
+
+ // R.txt
+ "int drawable foobar 0x7fffffff\n" +
+ "int drawable ic_launcher 0x7fffffff\n" +
+ "int string app_name 0x7fffffff\n" +
+ "int string lib1 0x7fffffff\n" +
+ "int style AppBaseTheme 0x7fffffff\n" +
+ "int style AppTheme 0x7fffffff\n"
+ );
+ }
+
+ public void testStyleables1() throws Exception {
+ check(
+ // Package
+ "test.pkg",
+
+ // R.java
+ "/* AUTO-GENERATED FILE. DO NOT MODIFY.\n" +
+ " *\n" +
+ " * This class was automatically generated by the\n" +
+ " * aapt tool from the resource data it found. It\n" +
+ " * should not be modified by hand.\n" +
+ " */\n" +
+ "package test.pkg;\n" +
+ "\n" +
+ "public final class R {\n" +
+ " public static final class styleable {\n" +
+ " public static final int[] TiledView = { 0x7f010000, 0x7f010001, 0x7f010002, 0x7f010003, 0x7f010004 };\n" +
+ " public static final int TiledView_tileName = 2;\n" +
+ " public static final int TiledView_tilingEnum = 4;\n" +
+ " public static final int TiledView_tilingMode = 3;\n" +
+ " public static final int TiledView_tilingProperty = 0;\n" +
+ " public static final int TiledView_tilingResource = 1;\n" +
+ " }\n" +
+ "}\n",
+
+ // R values
+ null,
+
+ // R.txt
+ "int[] styleable TiledView { 0x7f010000, 0x7f010001, 0x7f010002, 0x7f010003, 0x7f010004 }\n" +
+ "int styleable TiledView_tileName 2\n" +
+ "int styleable TiledView_tilingEnum 4\n" +
+ "int styleable TiledView_tilingMode 3\n" +
+ "int styleable TiledView_tilingProperty 0\n" +
+ "int styleable TiledView_tilingResource 1\n"
+ );
+ }
+
+ public void testStyleables2() throws Exception {
+ check(
+ // Package
+ "test.pkg",
+
+ // R.java
+ "/* AUTO-GENERATED FILE. DO NOT MODIFY.\n" +
+ " *\n" +
+ " * This class was automatically generated by the\n" +
+ " * aapt tool from the resource data it found. It\n" +
+ " * should not be modified by hand.\n" +
+ " */\n" +
+ "package test.pkg;\n" +
+ "\n" +
+ "public final class R {\n" +
+ " public static final class styleable {\n" +
+ " public static final int[] LimitedSizeLinearLayout = { 0x7f010000, 0x7f010001 };\n" +
+ " public static final int LimitedSizeLinearLayout_max_height = 1;\n" +
+ " public static final int LimitedSizeLinearLayout_max_width = 0;\n" +
+ " }\n" +
+ " public static final class xml {\n" +
+ " public static final int authenticator = 0x7f040000;\n" +
+ " }\n" +
+ "}\n",
+
+ // R values
+ null,
+
+ // R.txt
+ "int[] styleable LimitedSizeLinearLayout { 0x7f010000, 0x7f010001 }\n" +
+ "int styleable LimitedSizeLinearLayout_max_height 1\n" +
+ "int styleable LimitedSizeLinearLayout_max_width 0\n" +
+ "int xml authenticator 0x7f040000\n"
+ );
+ }
+
+ public void testMerge() throws Exception {
+ check(
+ // Package
+ "test.pkg",
+
+ // R.java
+ "/* AUTO-GENERATED FILE. DO NOT MODIFY.\n" +
+ " *\n" +
+ " * This class was automatically generated by the\n" +
+ " * aapt tool from the resource data it found. It\n" +
+ " * should not be modified by hand.\n" +
+ " */\n" +
+ "package test.pkg;\n" +
+ "\n" +
+ "public final class R {\n" +
+ " public static final class drawable {\n" +
+ " public static final int foobar = 0x7f020000;\n" +
+ " public static final int ic_launcher = 0x7f020001;\n" +
+ " }\n" +
+ " public static final class string {\n" +
+ " public static final int app_name = 0x7f030000;\n" +
+ " public static final int lib1 = 0x7f030001;\n" +
+ " }\n" +
+ " public static final class style {\n" +
+ " public static final int AppBaseTheme = 0x7f040000;\n" +
+ " public static final int AppTheme = 0x7f040001;\n" +
+ " }\n" +
+ "}\n",
+
+ // R values
+ "int drawable foobar 0x7f020000\n" +
+ "int drawable ic_launcher 0x7f020001\n" +
+ "int string app_name 0x7f030000\n" +
+ "int string lib1 0x7f030001\n" +
+ "int style AppBaseTheme 0x7f040000\n" +
+ "int style AppTheme 0x7f040001\n",
+
+ // R.txt 1
+ "int drawable foobar 0x7fffffff\n" +
+ "int drawable ic_launcher 0x7fffffff\n" +
+ "int string app_name 0x7fffffff\n" +
+ "int string lib1 0x7fffffff\n" +
+
+ // R.txt 2
+ "int string app_name 0x80000000\n" +
+ "int string lib1 0x80000000\n" +
+ "int style AppBaseTheme 0x80000000\n" +
+ "int style AppTheme 0x80000000\n"
+ );
+ }
+}
diff --git a/build-system/builder/src/test/java/com/android/builder/internal/incremental/DependencyDataStoreTest.java b/build-system/builder/src/test/java/com/android/builder/internal/incremental/DependencyDataStoreTest.java
new file mode 100644
index 0000000..122de45
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/internal/incremental/DependencyDataStoreTest.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.internal.incremental;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+
+public class DependencyDataStoreTest extends TestCase {
+
+ public void testStoreSingleData() throws IOException {
+ // create a DependencyData object.
+ DependencyData data = new DependencyData();
+ data.setMainFile("/main/file");
+ data.addSecondaryFile("/secondary/file");
+ data.addOutputFile("/output/file");
+
+ // create a store and add the data.
+ DependencyDataStore store = new DependencyDataStore();
+ store.addData(data);
+
+ // and store it to disk
+ File file = File.createTempFile("DependencyDataStoreTest", "");
+ file.deleteOnExit();
+ store.saveTo(file);
+
+ // now load it
+ store = new DependencyDataStore();
+ store.loadFrom(file);
+
+ Collection<DependencyData> newDataList = store.getData();
+ assertNotNull(newDataList);
+ assertEquals(1, newDataList.size());
+
+ DependencyData newData = newDataList.iterator().next();
+ assertNotNull(newData);
+
+ // compare the values
+ assertEquals(data.getMainFile(), newData.getMainFile());
+ assertEquals(data.getSecondaryFiles(), newData.getSecondaryFiles());
+ assertEquals(data.getOutputFiles(), newData.getOutputFiles());
+ }
+
+ public void testStoreSingleDataWithMultiFiles() throws IOException {
+ // create a DependencyData object.
+ DependencyData data = new DependencyData();
+ data.setMainFile("/main/file");
+ data.addSecondaryFile("/secondary/file");
+ data.addSecondaryFile("/secondary/file2");
+ data.addOutputFile("/output/file");
+ data.addOutputFile("/output/file2");
+
+ // create a store and add the data.
+ DependencyDataStore store = new DependencyDataStore();
+ store.addData(data);
+
+ // and store it to disk
+ File file = File.createTempFile("DependencyDataStoreTest", "");
+ file.deleteOnExit();
+ store.saveTo(file);
+
+ // now load it
+ store = new DependencyDataStore();
+ store.loadFrom(file);
+
+ Collection<DependencyData> newDataList = store.getData();
+ assertNotNull(newDataList);
+ assertEquals(1, newDataList.size());
+
+ DependencyData newData = newDataList.iterator().next();
+ assertNotNull(newData);
+
+ // compare the values
+ assertEquals(data.getMainFile(), newData.getMainFile());
+ assertEquals(data.getSecondaryFiles(), newData.getSecondaryFiles());
+ assertEquals(data.getOutputFiles(), newData.getOutputFiles());
+ }
+
+
+ public void testStoreMultiData() throws IOException {
+ // create a DependencyData object.
+ DependencyData data = new DependencyData();
+ data.setMainFile("/1/main/file");
+ data.addSecondaryFile("/1/secondary/file");
+ data.addOutputFile("/1/output/file");
+
+ DependencyData data2 = new DependencyData();
+ data2.setMainFile("/2/main/file");
+ data2.addSecondaryFile("/2/secondary/file");
+ data2.addOutputFile("/2/output/file");
+
+ // create a store and add the data.
+ DependencyDataStore store = new DependencyDataStore();
+ store.addData(data);
+ store.addData(data2);
+
+ // and store it to disk
+ File file = File.createTempFile("DependencyDataStoreTest", "");
+ file.deleteOnExit();
+ store.saveTo(file);
+
+ // now load it
+ store = new DependencyDataStore();
+ store.loadFrom(file);
+
+ // get the collection to check on the size.
+ Collection<DependencyData> newDataList = store.getData();
+ assertEquals(2, newDataList.size());
+
+ DependencyData firstData = store.getByMainFile("/1/main/file");
+ assertNotNull(firstData);
+
+ // compare the values
+ assertEquals(data.getMainFile(), firstData.getMainFile());
+ assertEquals(data.getSecondaryFiles(), firstData.getSecondaryFiles());
+ assertEquals(data.getOutputFiles(), firstData.getOutputFiles());
+
+ DependencyData secondData = store.getByMainFile("/2/main/file");
+ assertNotNull(secondData);
+
+ // compare the values
+ assertEquals(data2.getMainFile(), secondData.getMainFile());
+ assertEquals(data2.getSecondaryFiles(), secondData.getSecondaryFiles());
+ assertEquals(data2.getOutputFiles(), secondData.getOutputFiles());
+ }
+
+ public void testStoreNoOutputData() throws IOException {
+ // create a DependencyData object.
+ DependencyData data = new DependencyData();
+ data.setMainFile("/1/main/file");
+ data.addSecondaryFile("/1/secondary/file");
+
+ DependencyData data2 = new DependencyData();
+ data2.setMainFile("/2/main/file");
+ data2.addSecondaryFile("/2/secondary/file");
+ data2.addOutputFile("/2/output/file");
+
+ // create a store and add the data.
+ DependencyDataStore store = new DependencyDataStore();
+ store.addData(data);
+ store.addData(data2);
+
+ // and store it to disk
+ File file = File.createTempFile("DependencyDataStoreTest", "");
+ file.deleteOnExit();
+ store.saveTo(file);
+
+ // now load it
+ store = new DependencyDataStore();
+ store.loadFrom(file);
+
+ // get the collection to check on the size.
+ Collection<DependencyData> newDataList = store.getData();
+ assertEquals(2, newDataList.size());
+
+ DependencyData firstData = store.getByMainFile("/1/main/file");
+ assertNotNull(firstData);
+
+ // compare the values
+ assertEquals(data.getMainFile(), firstData.getMainFile());
+ assertEquals(data.getSecondaryFiles(), firstData.getSecondaryFiles());
+ assertEquals(0, firstData.getOutputFiles().size());
+
+ DependencyData secondData = store.getByMainFile("/2/main/file");
+ assertNotNull(secondData);
+
+ // compare the values
+ assertEquals(data2.getMainFile(), secondData.getMainFile());
+ assertEquals(data2.getSecondaryFiles(), secondData.getSecondaryFiles());
+ assertEquals(data2.getOutputFiles(), secondData.getOutputFiles());
+ }
+
+ public void testStoreHeaderData() throws IOException {
+ // create a DependencyData object.
+ DependencyData data = new DependencyData();
+ data.setMainFile("/1/main/file");
+
+ DependencyData data2 = new DependencyData();
+ data2.setMainFile("/2/main/file");
+
+ // create a store and add the data.
+ DependencyDataStore store = new DependencyDataStore();
+ store.addData(data);
+ store.addData(data2);
+
+ // and store it to disk
+ File file = File.createTempFile("DependencyDataStoreTest", "");
+ file.deleteOnExit();
+ store.saveTo(file);
+
+ // now load it
+ store = new DependencyDataStore();
+ store.loadFrom(file);
+
+ // get the collection to check on the size.
+ Collection<DependencyData> newDataList = store.getData();
+ assertEquals(2, newDataList.size());
+
+ DependencyData firstData = store.getByMainFile("/1/main/file");
+ assertNotNull(firstData);
+
+ // compare the values
+ assertEquals(data.getMainFile(), firstData.getMainFile());
+ assertEquals(0, firstData.getSecondaryFiles().size());
+ assertEquals(0, firstData.getOutputFiles().size());
+
+ DependencyData secondData = store.getByMainFile("/2/main/file");
+ assertNotNull(secondData);
+
+ // compare the values
+ assertEquals(data2.getMainFile(), secondData.getMainFile());
+ assertEquals(0, secondData.getSecondaryFiles().size());
+ assertEquals(0, secondData.getOutputFiles().size());
+ }
+}
diff --git a/build-system/builder/src/test/java/com/android/builder/internal/incremental/DependencyDataTest.java b/build-system/builder/src/test/java/com/android/builder/internal/incremental/DependencyDataTest.java
new file mode 100644
index 0000000..cba11f1
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/internal/incremental/DependencyDataTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.internal.incremental;
+
+import com.android.testutils.TestUtils;
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+public class DependencyDataTest extends TestCase {
+
+ public void testWindowsMode1() throws Exception {
+ DependencyData data = getData("windows_mode1.d");
+
+ assertEquals("C:\\path\\to\\main input.bar", data.getMainFile());
+
+ List<String> secondaryFiles = data.getSecondaryFiles();
+ assertEquals(2, secondaryFiles.size());
+ assertEquals("C:\\path\\to\\some input1.bar", secondaryFiles.get(0));
+ assertEquals("C:\\path\\to\\some input2.bar", secondaryFiles.get(1));
+
+ List<String> outputs = data.getOutputFiles();
+ assertEquals(1, outputs.size());
+
+ assertEquals("C:\\path\\to\\some output.foo", outputs.get(0));
+
+ }
+
+ public void testWindowsMode2() throws Exception {
+ DependencyData data = getData("windows_mode2.d");
+
+ assertEquals("C:\\path\\to\\main input.bar", data.getMainFile());
+
+ List<String> secondaryFiles = data.getSecondaryFiles();
+ assertEquals(2, secondaryFiles.size());
+ assertEquals("C:\\path\\to\\some input1.bar", secondaryFiles.get(0));
+ assertEquals("C:\\path\\to\\some input2.bar", secondaryFiles.get(1));
+
+ List<String> outputs = data.getOutputFiles();
+ assertEquals(1, outputs.size());
+
+ assertEquals("C:\\path\\to\\some output.foo", outputs.get(0));
+ }
+
+ public void testNoOutput() throws Exception {
+ DependencyData data = getData("no_output.d");
+
+ assertEquals(0, data.getSecondaryFiles().size());
+ assertEquals(0, data.getOutputFiles().size());
+
+ assertEquals("/path/to/main input.bar", data.getMainFile());
+ }
+
+ private DependencyData getData(String name) throws IOException {
+ File depFile = new File(TestUtils.getRoot("dependencyData"), name);
+ DependencyData data = DependencyData.parseDependencyFile(depFile);
+ assertNotNull(data);
+ return data;
+ }
+
+}
diff --git a/build-system/builder/src/test/java/com/android/builder/signing/KeyStoreHelperTest.java b/build-system/builder/src/test/java/com/android/builder/signing/KeyStoreHelperTest.java
new file mode 100755
index 0000000..453230c
--- /dev/null
+++ b/build-system/builder/src/test/java/com/android/builder/signing/KeyStoreHelperTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.builder.signing;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.utils.ILogger;
+import com.google.common.io.Files;
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.Calendar;
+
+public class KeyStoreHelperTest extends TestCase {
+
+ public void testCreateAndCheckKey() throws Exception {
+ File tempFolder = Files.createTempDir();
+ File keystoreFile = new File(tempFolder, "debug.keystore");
+ keystoreFile.deleteOnExit();
+
+ FakeLogger fakeLogger = new FakeLogger();
+
+ // create a default signing Config.
+ DefaultSigningConfig signingConfig = new DefaultSigningConfig("");
+ signingConfig.initDebug();
+ signingConfig.setStoreFile(keystoreFile);
+
+ // "now" is just slightly before the key was created
+ long now = System.currentTimeMillis();
+
+ // create the keystore
+ KeystoreHelper.createDebugStore(signingConfig, fakeLogger);
+
+ // read the key back
+ CertificateInfo certificateInfo = KeystoreHelper.getCertificateInfo(signingConfig);
+
+ assertNotNull(certificateInfo);
+
+ assertEquals("", fakeLogger.getErr());
+
+ PrivateKey key = certificateInfo.getKey();
+ assertNotNull(key);
+
+ X509Certificate certificate = certificateInfo.getCertificate();
+ assertNotNull(certificate);
+
+ // The "not-after" (a.k.a. expiration) date should be after "now"
+ Calendar c = Calendar.getInstance();
+ c.setTimeInMillis(now);
+ assertTrue(certificate.getNotAfter().compareTo(c.getTime()) > 0);
+
+ // It should be valid after 1 year from now (adjust by a second since the 'now' time
+ // doesn't exactly match the creation time... 1 second should be enough.)
+ c.add(Calendar.DAY_OF_YEAR, 365);
+ c.add(Calendar.SECOND, -1);
+ assertTrue("1 year expiration failed",
+ certificate.getNotAfter().compareTo(c.getTime()) > 0);
+
+ // and 30 years from now
+ c.add(Calendar.DAY_OF_YEAR, 29 * 365);
+ // remove 1 hour to handle for PST/PDT issue
+ c.add(Calendar.HOUR_OF_DAY, -1);
+ assertTrue("30 year expiration failed",
+ certificate.getNotAfter().compareTo(c.getTime()) > 0);
+
+ // however expiration date should be passed in 30 years + a few hours
+ c.add(Calendar.HOUR, 5);
+ assertFalse("30 year and few hours expiration failed",
+ certificate.getNotAfter().compareTo(c.getTime()) > 0);
+ }
+
+ private static class FakeLogger implements ILogger {
+ private String mOut = "";
+ private String mErr = "";
+
+ public String getOut() {
+ return mOut;
+ }
+
+ public String getErr() {
+ return mErr;
+ }
+
+ @Override
+ public void error(@Nullable Throwable t, @Nullable String msgFormat, Object... args) {
+ String message = msgFormat != null ?
+ String.format(msgFormat, args) :
+ t != null ? t.getClass().getCanonicalName() : "ERROR!";
+ mErr += message + "\n";
+ }
+
+ @Override
+ public void warning(@NonNull String msgFormat, Object... args) {
+ String message = String.format(msgFormat, args);
+ mOut += message + "\n";
+ }
+
+ @Override
+ public void info(@NonNull String msgFormat, Object... args) {
+ String message = String.format(msgFormat, args);
+ mOut += message + "\n";
+ }
+
+ @Override
+ public void verbose(@NonNull String msgFormat, Object... args) {
+ String message = String.format(msgFormat, args);
+ mOut += message + "\n";
+ }
+ }
+}
diff --git a/build-system/builder/src/test/resources/testData/dependencyData/no_output.d b/build-system/builder/src/test/resources/testData/dependencyData/no_output.d
new file mode 100755
index 0000000..870145f
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/dependencyData/no_output.d
@@ -0,0 +1,2 @@
+ : \
+ /path/to/main input.bar
diff --git a/build-system/builder/src/test/resources/testData/dependencyData/windows_mode1.d b/build-system/builder/src/test/resources/testData/dependencyData/windows_mode1.d
new file mode 100755
index 0000000..752d092
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/dependencyData/windows_mode1.d
@@ -0,0 +1,4 @@
+C:\path\to\some output.foo : \
+C:\path\to\main input.bar \
+C:\path\to\some input1.bar \
+C:\path\to\some input2.bar \
diff --git a/build-system/builder/src/test/resources/testData/dependencyData/windows_mode2.d b/build-system/builder/src/test/resources/testData/dependencyData/windows_mode2.d
new file mode 100755
index 0000000..3a0ddf7
--- /dev/null
+++ b/build-system/builder/src/test/resources/testData/dependencyData/windows_mode2.d
@@ -0,0 +1,4 @@
+C:\path\to\some output.foo \
+ : C:\path\to\main input.bar \
+C:\path\to\some input1.bar \
+C:\path\to\some input2.bar \
diff --git a/build-system/changelog.txt b/build-system/changelog.txt
new file mode 100644
index 0000000..a07b919
--- /dev/null
+++ b/build-system/changelog.txt
@@ -0,0 +1,218 @@
+0.7.0
+- Requires Gradle 1.9
+- You can now have a variant specific source folder if you have flavors.
+ Only for app (not library or test). Name is src/flavorDebug/... or src/flavor1Flavor2Debug/
+ (note the camelcase naming, with lower case for first letter).
+ Its components (res, manifest, etc...) have higher priority than components from build type
+ or flavors.
+ There is also a "flavor combination" source folder available when more than one
+ flavor dimension is used.
+ For instance src/flavor1Flavor2/
+ Note that this is for all combinations of *all* dimensions.
+- Build config improvements and DSL changes.
+ The previous DSL proprety:
+ buildConfigLine "<value>"
+ has changed to
+ buildConfigField "<type>", "<name>", "<value>"
+ You can only add a single field at a time.
+ This allows override a field (see 'basic' sample)
+ Also, BuildConfig now automatically contains constants for
+ PACKAGE_NAME, VERSION_CODE, VERSION_NAME, BUILD_TYPE, FLAVOR as well as FLAVOR_<group> if there are several flavor dimensions.
+- Switch to ProGuard 4.10
+ - Added ability to test proguarded (obfuscated) apps.
+- New option on product Flavor (and defaultConfig) allow filtering of resources through the -c option of aapt
+ You can pass single or multiple values through the DSL. All values from the default config and flavors get combined and passed to aapt.
+ The DSL is
+ resConfig "en"
+ or
+ resConfigs "nodpi","hdpi"
+
+- Jar files are now pre-dexed for faster dexing.
+ Incremental dexing is disabled by default as it can lead to increased dex file size.
+- First pass at NDK integration. See the samples.
+- API to add new generated source folders:
+ variant.addJavaSourceFoldersToModel(sourceFolder1, sourceFolders2,...)
+ This adds the source folder to the model (for IDE support).
+ Another API:
+ variant.registerJavaGeneratingTask(task, sourceFolder1, sourceFolders2,...)
+ This automatically adds the dependency on the task, sets up the JavaCompile task inputs and propagates
+ the folders to the model for IDE integration.
+ See sample 'genFolderApi'
+- API to add extra artifacts on variants. This will allow to register Java or Android artifacts, for instance
+ for alternative test artifacts.
+ See sample 'artifactApi' for the API (sample is not meant to be used, it's for testing).
+- Revamped lint integration. Lint is now run as part of the check task, and will analyze all variants and then
+ merge the results and create a report which lists which variants each error applies to (unless an error
+ applies to all variants). You can also run lint on a specific variant, e.g. lintDebug or lintFreeRelease.
+ Lint will no longer report errors in AAR libraries. This version of the plugin also picks up some new lint
+ checks.
+ A new DSL allows configuration of lint from build.gradle. This is read and used in Studio
+- Fixed issue with parentActivityName when handling different package name in the manifest merger.
+- Allow files inside META-INF/ from jars to be packaged in the APK.
+- Disabled incremental dx mode as it can lead to broken dex files.
+
+0.6.3
+- Fixed ClassNotFoundException:MergingException introduced in 0.6.2
+
+0.6.2
+- Lint now picks up the SDK home from sdk.dir in local.properties
+- Error message shown when using an unsupported version of Gradle now explains how to update the Gradle wrapper
+- Merged resource files no longer place their source markers into the R file as comments
+- Project path can contain '--' (two dashes)
+- Internal changes to improve integration with Android Studio
+
+0.6.1
+
+- Fixed issues with lint task found in 0.6.0
+
+0.6.0
+
+- Enabled support for Gradle 1.8
+- Gradle 1.8 is now the minimum supported version
+- Default encoding for compiling Java code is UTF-8
+- Users can now specify the encoding to use to compile Java code
+- Fixed Gradle 1.8-specific bugs
+ - Importing projects with missing dependencies was broken
+ - Compiling projects with AIDL files was broken
+
+0.5.7
+
+- Proguard support for libraries.
+ Note the current DSL property 'proguardFiles' for library now sets the proguard rule file used when proguarding the library code.
+ The new property 'consumerProguardFiles' is used to package a rule file inside an aar.
+- Improved IDE support, including loading project with broken dependencies and anchor task to generate Java code
+- New hook tasks: preBuild and prebuild<VariantName>
+- First lint integration. This is a work in progress and therefore the lint task is not added to the check task.
+- Enable compatibility with 1.8
+
+0.5.6
+
+- Enabled support for 1.7
+
+0.5.5
+
+- Fix issue preventing to use Build Tools 18.0.1
+- access to the variants container don't force creating the task.
+ This means android.[application|Library|Test]Variants will be empty
+ during the evaluation phase. To use it, use .all instead of .each
+- Only package a library's own resources in its aar.
+- Fix incremental issues in the resource merger.
+- Misc bug fixes.
+
+0.5.4
+
+- Fixed incremental compilation issue with declare-styleable
+
+0.5.3
+
+- Fixed a crashing bug in PrepareDependenciesTask
+
+0.5.2
+
+- Better error reporting for cmd line tools, especially
+ if run in parallel in spawned threads
+- Fixed an issue due to windows path in merged resource files.
+
+0.5.1
+
+- Fixed issue in the dependency checker.
+
+0.5.0:
+
+- IDE Model is changed and is not compatible with earlier version! A new IDE
+ will required.
+- Fixed IDE model to contain the output file even if it's customized
+ through the DSL. Also fixed the DSL to get/set the output file on the
+ variant object so that it's not necessary to use variant.packageApplication
+ or variant.zipAlign
+- Fixed dependency resolution so that we resolved the combination of (default config,
+ build types, flavor(s)) together instead of separately.
+- Fixed dependency for tests of library project to properly include all the dependencies
+ of the library itself.
+- Fixed case where two dependencies have the same leaf name.
+- Fixed issue where proguard rules file cannot be applied on flavors.
+
+0.4.3:
+
+- Enabled crunching for all png files, not just .9.png
+- Fixed dealing with non resource files in res/ and assets/
+- Fixed crash when doing incremental aidl compilation due to broken method name (ah the joy of Groovy...)
+- Cleaned older R classes when the app package name has changed.
+
+0.4.2
+
+* Fixed incremental support for resource merging.
+* Fixed issue where all pngs would be processed in parallel with no limit
+ on the number of thread used, leading to failure to run aapt.
+* Fixed ignoreAsset support in aaptOptions
+* Added more logging on failure to merge manifests.
+* Added flavor names to the TestServer API.
+
+0.4.1:
+
+* Renamed 'package' scope to 'apk'
+ - variants are 'debugApk', 'releaseApk', 'flavor1Apk', etc...
+ - Now properly supported at build to allow package-only dependencies.
+* Only Jar dependencies can be package-only. Library projects must be added to the compile scope.
+* Fixed [application|library|test]Variants API (always returned empty on 0.4)
+* Fixed issue in Proguard where it would complain about duplicate Manifests.
+
+0.4
+
+* System requirements:
+ - Gradle 1.6+
+ - Android Build Tools 16.0.2+
+* Rename deviceCheck into connectedDevice
+* API for 3rd party Device Providers and Test Servers to run and deploy tests. API is @Beta
+* Support for ProGuard 4.9
+ - enable with BuildType.runProguard
+ - add proguard config files with BuiltType.proguardFile or ProductFlavor.proguardFile
+ - default proguard files accessible through android.getDefaultProguardFile(name) with name
+ being 'proguard-android.txt' or 'proguard-android-optimize.txt'
+* Implements Gradle 1.6 custom model for IDE Tooling support
+* Fixes:
+ - Fix support for subfolders in assets/
+ - Fix cases where Android Libraries have local Jars dependencies
+ - Fix renaming of package through DSL to ensure resources are compiled in the new namespace
+ - Fix DSL to add getSourceSets on the "android" extension.
+ - DSL to query variants has changed to applicationVariants and libraryVariants (depending on the plugin)
+ Also both plugin have testVariants (tests are not included in the default collection).
+
+0.3
+
+* System requirements:
+ - Gradle 1.3+ (tested on 1.3/1.4)
+ - Android Platform Tools 16.0.2+
+* New Features:
+ - Renderscript support.
+ - Support for multi resource folders. See 'multires' sample.
+ * PNG crunch is now done incrementally and in parallel.
+ - Support for multi asset folders.
+ - Support for asset folders in Library Projects.
+ - Support for versionName suffix provided by the BuildType.
+ - Testing
+ * Default sourceset for tests now src/instrumentTest (instrumentTest<Name> for flavors)
+ * Instrumentation tests now:
+ - started from "deviceCheck" instead of "check"
+ - run on all connected devices in parallel.
+ - break the build if any test fails.
+ - generate an HTML report for each flavor/project, but also aggregated.
+ * New plugin 'android-reporting' to aggregate android test results across projects. See 'flavorlib' sample.
+ - Improved DSL:
+ * replaced android.target with android.compileSdkVersion to make it less confusing with targetSdkVersion
+ * signing information now a SigningConfig object reusable across BuildType and ProductFlavor
+ * ability to relocate a full sourceSet. See 'migrated' sample.
+ * API to manipulate Build Variants.
+* Fixes:
+ - Default Java compile target set to 1.6.
+ - Fix generation of R classes in case libraries share same package name as the app project.
+
+0.2
+
+* Fixed support for windows.
+* Added support for customized sourceset. (http://tools.android.com/tech-docs/new-build-system/using-the-new-build-system#TOC-Working-with-and-Customizing-SourceSets)
+* Added support for dependency per configuration.
+* Fixed support for dependency on local jar files.
+* New samples "migrated" and "flavorlib"
+
+0.1: initial release
diff --git a/build-system/gradle-model/build.gradle b/build-system/gradle-model/build.gradle
new file mode 100644
index 0000000..8567674
--- /dev/null
+++ b/build-system/gradle-model/build.gradle
@@ -0,0 +1,42 @@
+apply plugin: 'java'
+apply plugin: 'clone-artifacts'
+apply plugin: 'idea'
+
+def toolingApiVersion = gradle.gradleVersion
+
+// Custom config that cloneArtifact will not look into, since this
+// artifact is not in mavenCentral, but in the gradle repo instead.
+configurations{
+ gradleRepo
+}
+
+dependencies {
+ compile project(':builder-model')
+
+ testCompile 'junit:junit:3.8.1'
+ testCompile project(':builder')
+
+ // Need an SLF4J implementation at runtime
+ testRuntime 'org.slf4j:slf4j-simple:1.7.2'
+
+ // this is technically testCompile, but we don't want
+ // it in there to avoid breaking cloneArtifact.
+ // we'll add it to the test compile classpath manually below
+ gradleRepo "org.gradle:gradle-tooling-api:${toolingApiVersion}"
+
+}
+
+//Include custom for compilation
+sourceSets.test.compileClasspath += configurations.gradleRepo
+sourceSets.test.runtimeClasspath += configurations.gradleRepo
+
+test.dependsOn ':gradle:publishLocal'
+
+idea {
+ module {
+ scopes.COMPILE.plus += configurations.gradleRepo
+ }
+}
+
+apply plugin: 'distrib'
+shipping.isShipping = false
diff --git a/build-system/gradle-model/gradle-model.iml b/build-system/gradle-model/gradle-model.iml
new file mode 100644
index 0000000..12701a8
--- /dev/null
+++ b/build-system/gradle-model/gradle-model.iml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module relativePaths="true" type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module" module-name="builder-model" exported="" />
+ <orderEntry type="module" module-name="builder" scope="TEST" />
+ <orderEntry type="library" scope="TEST" name="JUnit3" level="project" />
+ <orderEntry type="library" name="gradle-tooling-api-1.9" level="project" />
+ <orderEntry type="library" name="slf4j-api" level="project" />
+ <orderEntry type="library" scope="TEST" name="slf4j-simple" level="project" />
+ </component>
+</module>
+
diff --git a/build-system/gradle-model/src/test/java/com/android/build/gradle/model/AndroidProjectTest.java b/build-system/gradle-model/src/test/java/com/android/build/gradle/model/AndroidProjectTest.java
new file mode 100644
index 0000000..4da4114
--- /dev/null
+++ b/build-system/gradle-model/src/test/java/com/android/build/gradle/model/AndroidProjectTest.java
@@ -0,0 +1,1167 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.model;
+
+import static com.android.builder.model.AndroidProject.ARTIFACT_INSTRUMENT_TEST;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.internal.StringHelper;
+import com.android.builder.model.AndroidArtifact;
+import com.android.builder.model.AndroidLibrary;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.ArtifactMetaData;
+import com.android.builder.model.BuildTypeContainer;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.JavaArtifact;
+import com.android.builder.model.JavaCompileOptions;
+import com.android.builder.model.ProductFlavor;
+import com.android.builder.model.ProductFlavorContainer;
+import com.android.builder.model.SigningConfig;
+import com.android.builder.model.SourceProvider;
+import com.android.builder.model.SourceProviderContainer;
+import com.android.builder.model.Variant;
+import com.android.builder.signing.KeystoreHelper;
+import com.android.prefs.AndroidLocation;
+import com.google.common.collect.Maps;
+
+import junit.framework.TestCase;
+
+import org.gradle.tooling.GradleConnector;
+import org.gradle.tooling.ProjectConnection;
+import org.gradle.tooling.UnknownModelException;
+import org.gradle.tooling.model.GradleProject;
+
+import java.io.File;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.security.CodeSource;
+import java.security.KeyStore;
+import java.util.Collection;
+import java.util.Locale;
+import java.util.Map;
+
+public class AndroidProjectTest extends TestCase {
+
+ private final static String MODEL_VERSION = "0.7.0-SNAPSHOT";
+
+ private static final Map<String, ProjectData> sProjectModelMap = Maps.newHashMap();
+
+ private static final class ProjectData {
+ AndroidProject model;
+ File projectDir;
+
+ static ProjectData create(File projectDir, AndroidProject model) {
+ ProjectData projectData = new ProjectData();
+ projectData.model = model;
+ projectData.projectDir = projectDir;
+
+ return projectData;
+ }
+ }
+
+ private ProjectData getModelForProject(String projectName) {
+ ProjectData projectData = sProjectModelMap.get(projectName);
+
+ if (projectData == null) {
+ // Configure the connector and create the connection
+ GradleConnector connector = GradleConnector.newConnector();
+
+ File projectDir = new File(getTestDir(), projectName);
+ connector.forProjectDirectory(projectDir);
+
+ ProjectConnection connection = connector.connect();
+ try {
+ // Load the custom model for the project
+ AndroidProject model = connection.getModel(AndroidProject.class);
+ assertNotNull("Model Object null-check", model);
+ assertEquals("Model Name", projectName, model.getName());
+ assertEquals("Model version", MODEL_VERSION, model.getModelVersion());
+
+ projectData = ProjectData.create(projectDir, model);
+
+ sProjectModelMap.put(projectName, projectData);
+
+ return projectData;
+ } finally {
+ connection.close();
+ }
+ }
+
+ return projectData;
+ }
+
+ private Map<String, ProjectData> getModelForMultiProject(String projectName) throws Exception {
+ // Configure the connector and create the connection
+ GradleConnector connector = GradleConnector.newConnector();
+
+ File projectDir = new File(getTestDir(), projectName);
+ connector.forProjectDirectory(projectDir);
+
+ Map<String, ProjectData> map = Maps.newHashMap();
+
+ ProjectConnection connection = connector.connect();
+
+ try {
+ // Query the default Gradle Model.
+ GradleProject model = connection.getModel(GradleProject.class);
+ assertNotNull("Model Object null-check", model);
+
+ // Now get the children projects, recursively.
+ for (GradleProject child : model.getChildren()) {
+ String path = child.getPath();
+ String name = path.substring(1);
+ File childDir = new File(projectDir, name);
+
+ GradleConnector childConnector = GradleConnector.newConnector();
+
+ childConnector.forProjectDirectory(childDir);
+
+ ProjectConnection childConnection = childConnector.connect();
+ try {
+ AndroidProject androidProject = childConnection.getModel(AndroidProject.class);
+
+ assertNotNull("Model Object null-check for " + path, androidProject);
+ assertEquals("Model Name for " + path, name, androidProject.getName());
+ assertEquals("Model version", MODEL_VERSION, androidProject.getModelVersion());
+
+ map.put(path, ProjectData.create(childDir, androidProject));
+
+ } catch (UnknownModelException e) {
+ // probably a Java-only project, ignore.
+ } finally {
+ childConnection.close();
+ }
+ }
+ } finally {
+ connection.close();
+ }
+
+ return map;
+ }
+
+ public void testBasic() {
+ // Load the custom model for the project
+ ProjectData projectData = getModelForProject("basic");
+
+ AndroidProject model = projectData.model;
+
+ assertFalse("Library Project", model.isLibrary());
+ assertEquals("Compile Target", "android-15", model.getCompileTarget());
+ assertFalse("Non empty bootclasspath", model.getBootClasspath().isEmpty());
+
+ JavaCompileOptions javaCompileOptions = model.getJavaCompileOptions();
+ assertEquals("1.6", javaCompileOptions.getSourceCompatibility());
+ assertEquals("1.6", javaCompileOptions.getTargetCompatibility());
+ }
+
+ public void testBasicSourceProviders() throws Exception {
+ // Load the custom model for the project
+ ProjectData projectData = getModelForProject("basic");
+
+ AndroidProject model = projectData.model;
+ File projectDir = projectData.projectDir;
+
+ testDefaultSourceSets(model, projectDir);
+
+ // test the source provider for the artifacts
+ for (Variant variant : model.getVariants()) {
+ AndroidArtifact artifact = variant.getMainArtifact();
+ assertNull(artifact.getVariantSourceProvider());
+ assertNull(artifact.getMultiFlavorSourceProvider());
+ }
+ }
+
+ public void testBasicMultiFlavorsSourceProviders() throws Exception {
+ // Load the custom model for the project
+ ProjectData projectData = getModelForProject("basicMultiFlavors");
+
+ AndroidProject model = projectData.model;
+ File projectDir = projectData.projectDir;
+
+ testDefaultSourceSets(model, projectDir);
+
+ // test the source provider for the flavor
+ Collection<ProductFlavorContainer> productFlavors = model.getProductFlavors();
+ assertEquals("Product Flavor Count", 4, productFlavors.size());
+
+ for (ProductFlavorContainer pfContainer : productFlavors) {
+ String name = pfContainer.getProductFlavor().getName();
+ new SourceProviderTester(
+ model.getName(),
+ projectDir,
+ name,
+ pfContainer.getSourceProvider())
+ .test();
+
+ assertEquals(1, pfContainer.getExtraSourceProviders().size());
+ SourceProviderContainer container = getSourceProviderContainer(
+ pfContainer.getExtraSourceProviders(), ARTIFACT_INSTRUMENT_TEST);
+ assertNotNull(container);
+
+ new SourceProviderTester(
+ model.getName(),
+ projectDir,
+ "instrumentTest" + StringHelper.capitalize(name),
+ container.getSourceProvider())
+ .test();
+ }
+
+ // test the source provider for the artifacts
+ for (Variant variant : model.getVariants()) {
+ AndroidArtifact artifact = variant.getMainArtifact();
+ assertNotNull(artifact.getVariantSourceProvider());
+ assertNotNull(artifact.getMultiFlavorSourceProvider());
+ }
+ }
+
+ private void testDefaultSourceSets(@NonNull AndroidProject model, @NonNull File projectDir) {
+ ProductFlavorContainer defaultConfig = model.getDefaultConfig();
+
+ // test the main source provider
+ new SourceProviderTester(model.getName(), projectDir,
+ "main", defaultConfig.getSourceProvider())
+ .test();
+
+ // test the main instrumentTest source provider
+ SourceProviderContainer testSourceProviders = getSourceProviderContainer(
+ defaultConfig.getExtraSourceProviders(), ARTIFACT_INSTRUMENT_TEST);
+ assertNotNull("InstrumentTest source Providers null-check", testSourceProviders);
+
+ new SourceProviderTester(model.getName(), projectDir,
+ "instrumentTest", testSourceProviders.getSourceProvider())
+ .test();
+
+ // test the source provider for the build types
+ Collection<BuildTypeContainer> buildTypes = model.getBuildTypes();
+ assertEquals("Build Type Count", 2, buildTypes.size());
+
+ for (BuildTypeContainer btContainer : model.getBuildTypes()) {
+ new SourceProviderTester(
+ model.getName(),
+ projectDir,
+ btContainer.getBuildType().getName(),
+ btContainer.getSourceProvider())
+ .test();
+
+ assertEquals(0, btContainer.getExtraSourceProviders().size());
+ }
+ }
+
+ public void testBasicVariantDetails() throws Exception {
+ // Load the custom model for the project
+ ProjectData projectData = getModelForProject("basic");
+
+ AndroidProject model = projectData.model;
+
+ Collection<Variant> variants = model.getVariants();
+ assertEquals("Variant Count", 2 , variants.size());
+
+ // debug variant
+ Variant debugVariant = getVariant(variants, "debug");
+ assertNotNull("debug Variant null-check", debugVariant);
+ new ProductFlavorTester(debugVariant.getMergedFlavor(), "Debug Merged Flavor")
+ .setVersionCode(12)
+ .setVersionName("2.0")
+ .setMinSdkVersion(16)
+ .setTargetSdkVersion(16)
+ .setTestInstrumentationRunner("android.test.InstrumentationTestRunner")
+ .setTestHandleProfiling(Boolean.FALSE)
+ .setTestFunctionalTest(null)
+ .test();
+
+ AndroidArtifact debugMainInfo = debugVariant.getMainArtifact();
+ assertNotNull("Debug main info null-check", debugMainInfo);
+ assertEquals("Debug package name", "com.android.tests.basic.debug",
+ debugMainInfo.getPackageName());
+ assertTrue("Debug signed check", debugMainInfo.isSigned());
+ assertEquals("Debug signingConfig name", "myConfig", debugMainInfo.getSigningConfigName());
+ assertEquals("Debug sourceGenTask", "generateDebugSources", debugMainInfo.getSourceGenTaskName());
+ assertEquals("Debug javaCompileTask", "compileDebugJava", debugMainInfo.getJavaCompileTaskName());
+
+ Collection<AndroidArtifact> debugExtraAndroidArtifacts = debugVariant.getExtraAndroidArtifacts();
+
+
+ // this variant is tested.
+ AndroidArtifact debugTestInfo = getAndroidArtifact(debugExtraAndroidArtifacts,
+ ARTIFACT_INSTRUMENT_TEST);
+ assertNotNull("Test info null-check", debugTestInfo);
+ assertEquals("Test package name", "com.android.tests.basic.debug.test",
+ debugTestInfo.getPackageName());
+ assertNotNull("Test output file null-check", debugTestInfo.getOutputFile());
+ assertTrue("Test signed check", debugTestInfo.isSigned());
+ assertEquals("Test signingConfig name", "myConfig", debugTestInfo.getSigningConfigName());
+ assertEquals("Test sourceGenTask", "generateDebugTestSources", debugTestInfo.getSourceGenTaskName());
+ assertEquals("Test javaCompileTask", "compileDebugTestJava", debugTestInfo.getJavaCompileTaskName());
+
+ // release variant, not tested.
+ Variant releaseVariant = getVariant(variants, "release");
+ assertNotNull("release Variant null-check", releaseVariant);
+
+ AndroidArtifact relMainInfo = releaseVariant.getMainArtifact();
+ assertNotNull("Release main info null-check", relMainInfo);
+ assertEquals("Release package name", "com.android.tests.basic",
+ relMainInfo.getPackageName());
+ assertFalse("Release signed check", relMainInfo.isSigned());
+ assertNull("Release signingConfig name", relMainInfo.getSigningConfigName());
+ assertEquals("Release sourceGenTask", "generateReleaseSources", relMainInfo.getSourceGenTaskName());
+ assertEquals("Release javaCompileTask", "compileReleaseJava", relMainInfo.getJavaCompileTaskName());
+
+ Collection<AndroidArtifact> releaseExtraAndroidArtifacts = releaseVariant.getExtraAndroidArtifacts();
+ AndroidArtifact relTestInfo = getAndroidArtifact(releaseExtraAndroidArtifacts, ARTIFACT_INSTRUMENT_TEST);
+ assertNull("Release test info null-check", relTestInfo);
+
+ // check debug dependencies
+ Dependencies dependencies = debugMainInfo.getDependencies();
+ assertNotNull(dependencies);
+ assertEquals(2, dependencies.getJars().size());
+ assertEquals(1, dependencies.getLibraries().size());
+
+ AndroidLibrary lib = dependencies.getLibraries().iterator().next();
+ assertNotNull(lib);
+ assertNotNull(lib.getBundle());
+ assertNotNull(lib.getFolder());
+
+ assertTrue(dependencies.getProjects().isEmpty());
+ }
+
+ public void testBasicSigningConfigs() throws Exception {
+ // Load the custom model for the project
+ ProjectData projectData = getModelForProject("basic");
+
+ AndroidProject model = projectData.model;
+
+ Collection<SigningConfig> signingConfigs = model.getSigningConfigs();
+ assertNotNull("SigningConfigs null-check", signingConfigs);
+ assertEquals("Number of signingConfig", 2, signingConfigs.size());
+
+ SigningConfig debugSigningConfig = getSigningConfig(signingConfigs, "debug");
+ assertNotNull("debug signing config null-check", debugSigningConfig);
+ new SigningConfigTester(debugSigningConfig, "debug", true).test();
+
+ SigningConfig mySigningConfig = getSigningConfig(signingConfigs, "myConfig");
+ assertNotNull("myConfig signing config null-check", mySigningConfig);
+ new SigningConfigTester(mySigningConfig, "myConfig", true)
+ .setStoreFile(new File(projectData.projectDir, "debug.keystore"))
+ .test();
+ }
+
+ public void testMigrated() throws Exception {
+ // Load the custom model for the project
+ ProjectData projectData = getModelForProject("migrated");
+
+ AndroidProject model = projectData.model;
+ File projectDir = projectData.projectDir;
+
+ assertNotNull("Model Object null-check", model);
+ assertEquals("Model Name", "migrated", model.getName());
+ assertFalse("Library Project", model.isLibrary());
+
+ ProductFlavorContainer defaultConfig = model.getDefaultConfig();
+
+ new SourceProviderTester(model.getName(), projectDir,
+ "main", defaultConfig.getSourceProvider())
+ .setJavaDir("src")
+ .setResourcesDir("src")
+ .setAidlDir("src")
+ .setRenderscriptDir("src")
+ .setResDir("res")
+ .setAssetsDir("assets")
+ .setManifestFile("AndroidManifest.xml")
+ .test();
+
+ SourceProviderContainer testSourceProviderContainer = getSourceProviderContainer(
+ defaultConfig.getExtraSourceProviders(), ARTIFACT_INSTRUMENT_TEST);
+ assertNotNull("InstrumentTest source Providers null-check", testSourceProviderContainer);
+
+ new SourceProviderTester(model.getName(), projectDir,
+ "instrumentTest", testSourceProviderContainer.getSourceProvider())
+ .setJavaDir("tests/java")
+ .setResourcesDir("tests/resources")
+ .setAidlDir("tests/aidl")
+ .setJniDir("tests/jni")
+ .setRenderscriptDir("tests/rs")
+ .setResDir("tests/res")
+ .setAssetsDir("tests/assets")
+ .setManifestFile("tests/AndroidManifest.xml")
+ .test();
+ }
+
+ public void testRenamedApk() throws Exception {
+ // Load the custom model for the project
+ ProjectData projectData = getModelForProject("renamedApk");
+
+ AndroidProject model = projectData.model;
+ File projectDir = projectData.projectDir;
+
+ assertNotNull("Model Object null-check", model);
+ assertEquals("Model Name", "renamedApk", model.getName());
+
+ Collection<Variant> variants = model.getVariants();
+ assertEquals("Variant Count", 2 , variants.size());
+
+ File buildDir = new File(projectDir, "build");
+
+ for (Variant variant : variants) {
+ AndroidArtifact mainInfo = variant.getMainArtifact();
+ assertNotNull(
+ "Null-check on mainArtifactInfo for " + variant.getDisplayName(),
+ mainInfo);
+
+ assertEquals("Output file for " + variant.getName(),
+ new File(buildDir, variant.getName() + ".apk"),
+ mainInfo.getOutputFile());
+ }
+ }
+
+ public void testFlavors() {
+ // Load the custom model for the project
+ ProjectData projectData = getModelForProject("flavors");
+
+ AndroidProject model = projectData.model;
+ File projectDir = projectData.projectDir;
+
+ assertNotNull("Model Object null-check", model);
+ assertEquals("Model Name", "flavors", model.getName());
+ assertFalse("Library Project", model.isLibrary());
+
+ ProductFlavorContainer defaultConfig = model.getDefaultConfig();
+
+ new SourceProviderTester(model.getName(), projectDir,
+ "main", defaultConfig.getSourceProvider())
+ .test();
+
+ SourceProviderContainer testSourceProviderContainer = getSourceProviderContainer(
+ defaultConfig.getExtraSourceProviders(), ARTIFACT_INSTRUMENT_TEST);
+ assertNotNull("InstrumentTest source Providers null-check", testSourceProviderContainer);
+
+ new SourceProviderTester(model.getName(), projectDir,
+ "instrumentTest", testSourceProviderContainer.getSourceProvider())
+ .test();
+
+ Collection<BuildTypeContainer> buildTypes = model.getBuildTypes();
+ assertEquals("Build Type Count", 2, buildTypes.size());
+
+ Collection<Variant> variants = model.getVariants();
+ assertEquals("Variant Count", 8, variants.size());
+
+ Variant f1faDebugVariant = getVariant(variants, "f1FaDebug");
+ assertNotNull("f1faDebug Variant null-check", f1faDebugVariant);
+ new ProductFlavorTester(f1faDebugVariant.getMergedFlavor(), "F1faDebug Merged Flavor")
+ .test();
+ new VariantTester(f1faDebugVariant, projectDir, "flavors-f1-fa-debug-unaligned.apk").test();
+ }
+
+ public void testTicTacToe() throws Exception {
+ Map<String, ProjectData> map = getModelForMultiProject("tictactoe");
+
+ ProjectData libModelData = map.get(":lib");
+ assertNotNull("lib module model null-check", libModelData);
+ assertTrue("lib module library flag", libModelData.model.isLibrary());
+
+ ProjectData appModelData = map.get(":app");
+ assertNotNull("app module model null-check", appModelData);
+
+ Collection<Variant> variants = appModelData.model.getVariants();
+ Variant debugVariant = getVariant(variants, "debug");
+ assertNotNull("debug variant null-check", debugVariant);
+
+ Dependencies dependencies = debugVariant.getMainArtifact().getDependencies();
+ assertNotNull(dependencies);
+
+ Collection<AndroidLibrary> libs = dependencies.getLibraries();
+ assertNotNull(libs);
+ assertEquals(1, libs.size());
+
+ AndroidLibrary androidLibrary = libs.iterator().next();
+ assertNotNull(androidLibrary);
+
+ assertEquals("Dependency project path", ":lib", androidLibrary.getProject());
+
+ // TODO: right now we can only test the folder name efficiently
+ assertEquals("TictactoeLibUnspecified.aar", androidLibrary.getFolder().getName());
+ }
+
+ public void testFlavorLib() throws Exception {
+ Map<String, ProjectData> map = getModelForMultiProject("flavorlib");
+
+ ProjectData appModelData = map.get(":app");
+ assertNotNull("Module app null-check", appModelData);
+ AndroidProject model = appModelData.model;
+
+ assertFalse("Library Project", model.isLibrary());
+
+ Collection<Variant> variants = model.getVariants();
+ Collection<ProductFlavorContainer> productFlavors = model.getProductFlavors();
+
+ ProductFlavorContainer flavor1 = getProductFlavor(productFlavors, "flavor1");
+ assertNotNull(flavor1);
+
+ Variant flavor1Debug = getVariant(variants, "flavor1Debug");
+ assertNotNull(flavor1Debug);
+
+ Dependencies dependencies = flavor1Debug.getMainArtifact().getDependencies();
+ assertNotNull(dependencies);
+ Collection<AndroidLibrary> libs = dependencies.getLibraries();
+ assertNotNull(libs);
+ assertEquals(1, libs.size());
+ AndroidLibrary androidLibrary = libs.iterator().next();
+ assertNotNull(androidLibrary);
+ // TODO: right now we can only test the folder name efficiently
+ assertEquals("FlavorlibLib1Unspecified.aar", androidLibrary.getFolder().getName());
+
+ ProductFlavorContainer flavor2 = getProductFlavor(productFlavors, "flavor2");
+ assertNotNull(flavor2);
+
+ Variant flavor2Debug = getVariant(variants, "flavor2Debug");
+ assertNotNull(flavor2Debug);
+
+ dependencies = flavor2Debug.getMainArtifact().getDependencies();
+ assertNotNull(dependencies);
+ libs = dependencies.getLibraries();
+ assertNotNull(libs);
+ assertEquals(1, libs.size());
+ androidLibrary = libs.iterator().next();
+ assertNotNull(androidLibrary);
+ // TODO: right now we can only test the folder name efficiently
+ assertEquals("FlavorlibLib2Unspecified.aar", androidLibrary.getFolder().getName());
+ }
+
+ public void testMultiproject() throws Exception {
+ Map<String, ProjectData> map = getModelForMultiProject("multiproject");
+
+ ProjectData baseLibModelData = map.get(":baseLibrary");
+ assertNotNull("Module app null-check", baseLibModelData);
+ AndroidProject model = baseLibModelData.model;
+
+ Collection<Variant> variants = model.getVariants();
+ assertEquals("Variant count", 2, variants.size());
+
+ Variant variant = getVariant(variants, "release");
+ assertNotNull("release variant null-check", variant);
+
+ AndroidArtifact mainInfo = variant.getMainArtifact();
+ assertNotNull("Main Artifact null-check", mainInfo);
+
+ Dependencies dependencies = mainInfo.getDependencies();
+ assertNotNull("Dependencies null-check", dependencies);
+
+ Collection<String> projects = dependencies.getProjects();
+ assertNotNull("project dep list null-check", projects);
+ assertEquals("project dep count", 1, projects.size());
+ assertEquals("dep on :util check", ":util", projects.iterator().next());
+
+ Collection<File> jars = dependencies.getJars();
+ assertNotNull("jar dep list null-check", jars);
+ // TODO these are jars coming from ':util' They shouldn't be there.
+ assertEquals("jar dep count", 2, jars.size());
+ }
+
+ public void testTestWithDep() {
+ // Load the custom model for the project
+ ProjectData projectData = getModelForProject("testWithDep");
+
+ AndroidProject model = projectData.model;
+
+ Collection<Variant> variants = model.getVariants();
+ Variant debugVariant = getVariant(variants, "debug");
+ assertNotNull(debugVariant);
+
+ Collection<AndroidArtifact> extraAndroidArtifact = debugVariant.getExtraAndroidArtifacts();
+ AndroidArtifact testArtifact = getAndroidArtifact(extraAndroidArtifact,
+ ARTIFACT_INSTRUMENT_TEST);
+ assertNotNull(testArtifact);
+
+ Dependencies testDependencies = testArtifact.getDependencies();
+ assertEquals(1, testDependencies.getJars().size());
+ }
+
+
+ public void testGenFolderApi() throws Exception {
+ // Load the custom model for the project
+ ProjectData projectData = getModelForProject("genFolderApi");
+
+ AndroidProject model = projectData.model;
+ File projectDir = projectData.projectDir;
+
+ File buildDir = new File(projectDir, "build");
+
+ for (Variant variant : model.getVariants()) {
+
+ AndroidArtifact mainInfo = variant.getMainArtifact();
+ assertNotNull(
+ "Null-check on mainArtifactInfo for " + variant.getDisplayName(),
+ mainInfo);
+
+ // get the generated source folders.
+ Collection<File> genFolder = mainInfo.getGeneratedSourceFolders();
+
+ // We're looking for a custom folder
+ String folderStart = new File(buildDir, "customCode").getAbsolutePath() + File.separatorChar;
+ boolean found = false;
+ for (File f : genFolder) {
+ if (f.getAbsolutePath().startsWith(folderStart)) {
+ found = true;
+ break;
+ }
+ }
+
+ assertTrue("custom generated source folder check", found);
+ }
+ }
+
+ public void testArtifactApi() throws Exception {
+ // Load the custom model for the project
+ ProjectData projectData = getModelForProject("artifactApi");
+
+ AndroidProject model = projectData.model;
+
+ // check the Artifact Meta Data
+ Collection<ArtifactMetaData> extraArtifacts = model.getExtraArtifacts();
+ assertNotNull("Extra artifact collection null-check", extraArtifacts);
+ assertEquals("Extra artifact size check", 2, extraArtifacts.size());
+
+ assertNotNull("instrument test metadata null-check",
+ getArtifactMetaData(extraArtifacts, ARTIFACT_INSTRUMENT_TEST));
+
+ // get the custom one.
+ ArtifactMetaData extraArtifactMetaData = getArtifactMetaData(extraArtifacts, "__test__");
+ assertNotNull("custom extra metadata null-check", extraArtifactMetaData);
+ assertFalse("custom extra meta data is Test check", extraArtifactMetaData.isTest());
+ assertEquals("custom extra meta data type check", ArtifactMetaData.TYPE_JAVA, extraArtifactMetaData.getType());
+
+ // check the extra source provider on the build Types.
+ for (BuildTypeContainer btContainer : model.getBuildTypes()) {
+ String name = btContainer.getBuildType().getName();
+ Collection<SourceProviderContainer> extraSourceProviderContainers = btContainer.getExtraSourceProviders();
+ assertNotNull(
+ "Extra source provider containers for build type '" + name + "' null-check",
+ extraSourceProviderContainers);
+ assertEquals(
+ "Extra source provider containers for build type size '" + name + "' check",
+ 1,
+ extraSourceProviderContainers.size());
+
+ SourceProviderContainer sourceProviderContainer = extraSourceProviderContainers.iterator().next();
+ assertNotNull(
+ "Extra artifact source provider for " + name + " null check",
+ sourceProviderContainer);
+
+ assertEquals(
+ "Extra artifact source provider for " + name + " name check",
+ "__test__",
+ sourceProviderContainer.getArtifactName());
+
+ assertEquals(
+ "Extra artifact source provider for " + name + " value check",
+ "buildType:" + name,
+ sourceProviderContainer.getSourceProvider().getManifestFile().getPath());
+ }
+
+ // check the extra source provider on the product flavors.
+ for (ProductFlavorContainer pfContainer : model.getProductFlavors()) {
+ String name = pfContainer.getProductFlavor().getName();
+ Collection<SourceProviderContainer> extraSourceProviderContainers = pfContainer.getExtraSourceProviders();
+ assertNotNull(
+ "Extra source provider container for product flavor '" + name + "' null-check",
+ extraSourceProviderContainers);
+ assertEquals(
+ "Extra artifact source provider container for product flavor size '" + name + "' check",
+ 2,
+ extraSourceProviderContainers.size());
+
+ assertNotNull(
+ "Extra source provider container for product flavor '" + name + "': instTest check",
+ getSourceProviderContainer(extraSourceProviderContainers, ARTIFACT_INSTRUMENT_TEST));
+
+
+ SourceProviderContainer sourceProviderContainer = getSourceProviderContainer(
+ extraSourceProviderContainers, "__test__");
+ assertNotNull(
+ "Custom source provider container for " + name + " null check",
+ sourceProviderContainer);
+
+ assertEquals(
+ "Custom artifact source provider for " + name + " name check",
+ "__test__",
+ sourceProviderContainer.getArtifactName());
+
+ assertEquals(
+ "Extra artifact source provider for " + name + " value check",
+ "productFlavor:" + name,
+ sourceProviderContainer.getSourceProvider().getManifestFile().getPath());
+ }
+
+ // check the extra artifacts on the variants
+ for (Variant variant : model.getVariants()) {
+ String name = variant.getName();
+ Collection<JavaArtifact> javaArtifacts = variant.getExtraJavaArtifacts();
+ assertEquals(1, javaArtifacts.size());
+ JavaArtifact javaArtifact = javaArtifacts.iterator().next();
+ assertEquals("__test__", javaArtifact.getName());
+ assertEquals("assemble:" + name, javaArtifact.getAssembleTaskName());
+ assertEquals("compile:" + name, javaArtifact.getJavaCompileTaskName());
+ assertEquals(new File("classesFolder:" + name), javaArtifact.getClassesFolder());
+
+ SourceProvider variantSourceProvider = javaArtifact.getVariantSourceProvider();
+ assertNotNull(variantSourceProvider);
+ assertEquals("provider:" + name, variantSourceProvider.getManifestFile().getPath());
+ }
+ }
+
+ /**
+ * Returns the SDK folder as built from the Android source tree.
+ * @return the SDK
+ */
+ protected File getSdkDir() {
+ String androidHome = System.getenv("ANDROID_HOME");
+ if (androidHome != null) {
+ File f = new File(androidHome);
+ if (f.isDirectory()) {
+ return f;
+ }
+ }
+
+ throw new IllegalStateException("SDK not defined with ANDROID_HOME");
+ }
+
+ /**
+ * Returns the root dir for the gradle plugin project
+ */
+ private File getRootDir() {
+ CodeSource source = getClass().getProtectionDomain().getCodeSource();
+ if (source != null) {
+ URL location = source.getLocation();
+ try {
+ File dir = new File(location.toURI());
+ assertTrue(dir.getPath(), dir.exists());
+
+ File f;
+ if (System.getenv("IDE_MODE") != null) {
+ f = dir.getParentFile().getParentFile().getParentFile();
+ } else {
+ f = dir.getParentFile().getParentFile().getParentFile().getParentFile().getParentFile().getParentFile().getParentFile().getParentFile();
+ f = new File(f, "tools" + File.separator + "base" + File.separator + "build-system");
+ }
+ return f;
+ } catch (URISyntaxException e) {
+ fail(e.getLocalizedMessage());
+ }
+ }
+
+ fail("Fail to get the tools/build folder");
+ return null;
+ }
+
+ /**
+ * Returns the root folder for the tests projects.
+ */
+ private File getTestDir() {
+ File rootDir = getRootDir();
+ return new File(rootDir, "tests");
+ }
+
+ @Nullable
+ private static Variant getVariant(
+ @NonNull Collection<Variant> items,
+ @NonNull String name) {
+ for (Variant item : items) {
+ if (name.equals(item.getName())) {
+ return item;
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ private static ProductFlavorContainer getProductFlavor(
+ @NonNull Collection<ProductFlavorContainer> items,
+ @NonNull String name) {
+ for (ProductFlavorContainer item : items) {
+ assertNotNull("ProductFlavorContainer list item null-check:" + name, item);
+ assertNotNull("ProductFlavorContainer.getProductFlavor() list item null-check: " + name, item.getProductFlavor());
+ assertNotNull("ProductFlavorContainer.getProductFlavor().getName() list item null-check: " + name, item.getProductFlavor().getName());
+ if (name.equals(item.getProductFlavor().getName())) {
+ return item;
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ private static ArtifactMetaData getArtifactMetaData(
+ @NonNull Collection<ArtifactMetaData> items,
+ @NonNull String name) {
+ for (ArtifactMetaData item : items) {
+ assertNotNull("ArtifactMetaData list item null-check:" + name, item);
+ assertNotNull("ArtifactMetaData.getName() list item null-check: " + name, item.getName());
+ if (name.equals(item.getName())) {
+ return item;
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ private static AndroidArtifact getAndroidArtifact(
+ @NonNull Collection<AndroidArtifact> items,
+ @NonNull String name) {
+ for (AndroidArtifact item : items) {
+ assertNotNull("AndroidArtifact list item null-check:" + name, item);
+ assertNotNull("AndroidArtifact.getName() list item null-check: " + name, item.getName());
+ if (name.equals(item.getName())) {
+ return item;
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ private static SigningConfig getSigningConfig(
+ @NonNull Collection<SigningConfig> items,
+ @NonNull String name) {
+ for (SigningConfig item : items) {
+ assertNotNull("SigningConfig list item null-check:" + name, item);
+ assertNotNull("SigningConfig.getName() list item null-check: " + name, item.getName());
+ if (name.equals(item.getName())) {
+ return item;
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ private static SourceProviderContainer getSourceProviderContainer(
+ @NonNull Collection<SourceProviderContainer> items,
+ @NonNull String name) {
+ for (SourceProviderContainer item : items) {
+ assertNotNull("SourceProviderContainer list item null-check:" + name, item);
+ assertNotNull("SourceProviderContainer.getName() list item null-check: " + name, item.getArtifactName());
+ if (name.equals(item.getArtifactName())) {
+ return item;
+ }
+ }
+
+ return null;
+ }
+
+ private static final class ProductFlavorTester {
+ @NonNull private final ProductFlavor productFlavor;
+ @NonNull private final String name;
+
+ private String packageName = null;
+ private int versionCode = -1;
+ private String versionName = null;
+ private int minSdkVersion = -1;
+ private int targetSdkVersion = -1;
+ private int renderscriptTargetApi = -1;
+ private String testPackageName = null;
+ private String testInstrumentationRunner = null;
+ private Boolean testHandleProfiling = null;
+ private Boolean testFunctionalTest = null;
+
+ ProductFlavorTester(@NonNull ProductFlavor productFlavor, @NonNull String name) {
+ this.productFlavor = productFlavor;
+ this.name = name;
+ }
+
+ ProductFlavorTester setPackageName(String packageName) {
+ this.packageName = packageName;
+ return this;
+ }
+
+ ProductFlavorTester setVersionCode(int versionCode) {
+ this.versionCode = versionCode;
+ return this;
+ }
+
+ ProductFlavorTester setVersionName(String versionName) {
+ this.versionName = versionName;
+ return this;
+ }
+
+ ProductFlavorTester setMinSdkVersion(int minSdkVersion) {
+ this.minSdkVersion = minSdkVersion;
+ return this;
+ }
+
+ ProductFlavorTester setTargetSdkVersion(int targetSdkVersion) {
+ this.targetSdkVersion = targetSdkVersion;
+ return this;
+ }
+
+ ProductFlavorTester setRenderscriptTargetApi(int renderscriptTargetApi) {
+ this.renderscriptTargetApi = renderscriptTargetApi;
+ return this;
+ }
+
+ ProductFlavorTester setTestPackageName(String testPackageName) {
+ this.testPackageName = testPackageName;
+ return this;
+ }
+
+ ProductFlavorTester setTestInstrumentationRunner(String testInstrumentationRunner) {
+ this.testInstrumentationRunner = testInstrumentationRunner;
+ return this;
+ }
+
+ ProductFlavorTester setTestHandleProfiling(Boolean testHandleProfiling) {
+ this.testHandleProfiling = testHandleProfiling;
+ return this;
+ }
+
+ ProductFlavorTester setTestFunctionalTest(Boolean testFunctionalTest) {
+ this.testFunctionalTest = testFunctionalTest;
+ return this;
+ }
+
+ void test() {
+ assertEquals(name + ":packageName", packageName, productFlavor.getPackageName());
+ assertEquals(name + ":VersionCode", versionCode, productFlavor.getVersionCode());
+ assertEquals(name + ":VersionName", versionName, productFlavor.getVersionName());
+ assertEquals(name + ":minSdkVersion", minSdkVersion, productFlavor.getMinSdkVersion());
+ assertEquals(name + ":targetSdkVersion",
+ targetSdkVersion, productFlavor.getTargetSdkVersion());
+ assertEquals(name + ":renderscriptTargetApi",
+ renderscriptTargetApi, productFlavor.getRenderscriptTargetApi());
+ assertEquals(name + ":testPackageName",
+ testPackageName, productFlavor.getTestPackageName());
+ assertEquals(name + ":testInstrumentationRunner",
+ testInstrumentationRunner, productFlavor.getTestInstrumentationRunner());
+ assertEquals(name + ":testHandleProfiling",
+ testHandleProfiling, productFlavor.getTestHandleProfiling());
+ assertEquals(name + ":testFunctionalTest",
+ testFunctionalTest, productFlavor.getTestFunctionalTest());
+ }
+ }
+
+ private static final class SourceProviderTester {
+
+ @NonNull private final String projectName;
+ @NonNull private final String configName;
+ @NonNull private final SourceProvider sourceProvider;
+ @NonNull private final File projectDir;
+ private String javaDir;
+ private String resourcesDir;
+ private String manifestFile;
+ private String resDir;
+ private String assetsDir;
+ private String aidlDir;
+ private String renderscriptDir;
+ private String jniDir;
+
+ SourceProviderTester(@NonNull String projectName, @NonNull File projectDir,
+ @NonNull String configName, @NonNull SourceProvider sourceProvider) {
+ this.projectName = projectName;
+ this.projectDir = projectDir;
+ this.configName = configName;
+ this.sourceProvider = sourceProvider;
+ // configure tester with default relative paths
+ setJavaDir("src/" + configName + "/java");
+ setResourcesDir("src/" + configName + "/resources");
+ setManifestFile("src/" + configName + "/AndroidManifest.xml");
+ setResDir("src/" + configName + "/res");
+ setAssetsDir("src/" + configName + "/assets");
+ setAidlDir("src/" + configName + "/aidl");
+ setRenderscriptDir("src/" + configName + "/rs");
+ setJniDir("src/" + configName + "/jni");
+ }
+
+ SourceProviderTester setJavaDir(String javaDir) {
+ this.javaDir = javaDir;
+ return this;
+ }
+
+ SourceProviderTester setResourcesDir(String resourcesDir) {
+ this.resourcesDir = resourcesDir;
+ return this;
+ }
+
+ SourceProviderTester setManifestFile(String manifestFile) {
+ this.manifestFile = manifestFile;
+ return this;
+ }
+
+ SourceProviderTester setResDir(String resDir) {
+ this.resDir = resDir;
+ return this;
+ }
+
+ SourceProviderTester setAssetsDir(String assetsDir) {
+ this.assetsDir = assetsDir;
+ return this;
+ }
+
+ SourceProviderTester setAidlDir(String aidlDir) {
+ this.aidlDir = aidlDir;
+ return this;
+ }
+
+ SourceProviderTester setRenderscriptDir(String renderscriptDir) {
+ this.renderscriptDir = renderscriptDir;
+ return this;
+ }
+
+ SourceProviderTester setJniDir(String jniDir) {
+ this.jniDir = jniDir;
+ return this;
+ }
+
+ void test() {
+ testSinglePathCollection("java", javaDir, sourceProvider.getJavaDirectories());
+ testSinglePathCollection("resources", resourcesDir, sourceProvider.getResourcesDirectories());
+ testSinglePathCollection("res", resDir, sourceProvider.getResDirectories());
+ testSinglePathCollection("assets", assetsDir, sourceProvider.getAssetsDirectories());
+ testSinglePathCollection("aidl", aidlDir, sourceProvider.getAidlDirectories());
+ testSinglePathCollection("rs", renderscriptDir, sourceProvider.getRenderscriptDirectories());
+ testSinglePathCollection("jni", jniDir, sourceProvider.getJniDirectories());
+
+ assertEquals("AndroidManifest",
+ new File(projectDir, manifestFile).getAbsolutePath(),
+ sourceProvider.getManifestFile().getAbsolutePath());
+ }
+
+ private void testSinglePathCollection(
+ @NonNull String setName,
+ @NonNull String referencePath,
+ @NonNull Collection<File> pathSet) {
+ assertEquals(1, pathSet.size());
+ assertEquals(projectName + ": " + configName + "/" + setName,
+ new File(projectDir, referencePath).getAbsolutePath(),
+ pathSet.iterator().next().getAbsolutePath());
+ }
+
+ }
+
+ private static final class VariantTester {
+
+ private final Variant variant;
+ private final File projectDir;
+ private final String outputFileName;
+
+ VariantTester(Variant variant, File projectDir, String outputFileName) {
+ this.variant = variant;
+ this.projectDir = projectDir;
+ this.outputFileName = outputFileName;
+ }
+
+ void test() {
+ AndroidArtifact artifact = variant.getMainArtifact();
+ assertNotNull("Main Artifact null-check", artifact);
+
+ String variantName = variant.getName();
+ File build = new File(projectDir, "build");
+ File apk = new File(build, "apk/" + outputFileName);
+ assertEquals(variantName + " output", apk, artifact.getOutputFile());
+
+ Collection<File> sourceFolders = artifact.getGeneratedSourceFolders();
+ assertEquals("Gen src Folder count", 4, sourceFolders.size());
+
+ File manifest = artifact.getGeneratedManifest();
+ assertNotNull(manifest);
+ }
+ }
+
+ private static final class SigningConfigTester {
+
+ public static final String DEFAULT_PASSWORD = "android";
+ public static final String DEFAULT_ALIAS = "AndroidDebugKey";
+
+ @NonNull private final SigningConfig signingConfig;
+ @NonNull private final String name;
+ private File storeFile = null;
+ private String storePassword = null;
+ private String keyAlias = null;
+ private String keyPassword = null;
+ private String storeType = KeyStore.getDefaultType();
+ private boolean isSigningReady = false;
+
+ SigningConfigTester(@NonNull SigningConfig signingConfig, @NonNull String name,
+ boolean isDebug) throws AndroidLocation.AndroidLocationException {
+ assertNotNull(String.format("SigningConfig '%s' null-check", name), signingConfig);
+ this.signingConfig = signingConfig;
+ this.name = name;
+
+ if (isDebug) {
+ storeFile = new File(KeystoreHelper.defaultDebugKeystoreLocation());
+ storePassword = DEFAULT_PASSWORD;
+ keyAlias = DEFAULT_ALIAS;
+ keyPassword = DEFAULT_PASSWORD;
+ isSigningReady = true;
+ }
+ }
+
+ SigningConfigTester setStoreFile(File storeFile) {
+ this.storeFile = storeFile;
+ return this;
+ }
+
+ SigningConfigTester setStorePassword(String storePassword) {
+ this.storePassword = storePassword;
+ return this;
+ }
+
+ SigningConfigTester setKeyAlias(String keyAlias) {
+ this.keyAlias = keyAlias;
+ return this;
+ }
+
+ SigningConfigTester setKeyPassword(String keyPassword) {
+ this.keyPassword = keyPassword;
+ return this;
+ }
+
+ SigningConfigTester setStoreType(String storeType) {
+ this.storeType = storeType;
+ return this;
+ }
+
+ SigningConfigTester setSigningReady(boolean isSigningReady) {
+ this.isSigningReady = isSigningReady;
+ return this;
+ }
+
+ void test() {
+ assertEquals("SigningConfig name", name, signingConfig.getName());
+
+ assertEquals(String.format("SigningConfig '%s' storeFile", name),
+ storeFile, signingConfig.getStoreFile());
+
+ assertEquals(String.format("SigningConfig '%s' storePassword", name),
+ storePassword, signingConfig.getStorePassword());
+
+ String scAlias = signingConfig.getKeyAlias();
+ assertEquals(String.format("SigningConfig '%s' keyAlias", name),
+ keyAlias != null ? keyAlias.toLowerCase(Locale.getDefault()) : keyAlias,
+ scAlias != null ? scAlias.toLowerCase(Locale.getDefault()) : scAlias);
+
+ assertEquals(String.format("SigningConfig '%s' keyPassword", name),
+ keyPassword, signingConfig.getKeyPassword());
+
+ assertEquals(String.format("SigningConfig '%s' storeType", name),
+ storeType, signingConfig.getStoreType());
+
+ assertEquals(String.format("SigningConfig '%s' isSigningReady", name),
+ isSigningReady, signingConfig.isSigningReady());
+ }
+ }
+}
diff --git a/build-system/gradle/MODULE_LICENSE_APACHE2 b/build-system/gradle/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/build-system/gradle/MODULE_LICENSE_APACHE2
diff --git a/build-system/gradle/NOTICE b/build-system/gradle/NOTICE
new file mode 100644
index 0000000..448b38a
--- /dev/null
+++ b/build-system/gradle/NOTICE
@@ -0,0 +1,212 @@
+============================================================
+Notices for file(s):
+/src/fromGradle/*
+------------------------------------------------------------
+
+ Copyright 2011 the original author or authors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+============================================================
+Notices for all other files
+------------------------------------------------------------
+
+ Copyright (c) 2012, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/build-system/gradle/README b/build-system/gradle/README
new file mode 100644
index 0000000..1e5e0c7
--- /dev/null
+++ b/build-system/gradle/README
@@ -0,0 +1,17 @@
+The code under src/fromGradle/ comes from Gradle 1.3 and is under the following license:
+
+Copyright 2011 the original author or authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
diff --git a/build-system/gradle/build.gradle b/build-system/gradle/build.gradle
new file mode 100644
index 0000000..b3d559a
--- /dev/null
+++ b/build-system/gradle/build.gradle
@@ -0,0 +1,122 @@
+apply plugin: 'groovy'
+apply plugin: 'clone-artifacts'
+apply plugin: 'idea'
+
+configurations {
+ gradleApi
+ compile.extendsFrom gradleApi
+ gradleApi.extendsFrom groovy
+}
+
+sourceSets {
+ main {
+ groovy.srcDirs 'src/main/groovy', 'src/fromGradle/groovy'
+ resources.srcDirs 'src/main/resources', 'src/fromGradle/resources'
+ }
+ buildTest {
+ groovy.srcDir file('src/build-test/groovy')
+ resources.srcDir file('src/build-test/resources')
+ }
+ deviceTest {
+ groovy.srcDir file('src/device-test/groovy')
+ resources.srcDir file('src/device-test/resources')
+ }
+}
+
+dependencies {
+ gradleApi gradleApi()
+ groovy localGroovy()
+ compile project(':builder')
+ compile project(':lint')
+ compile 'net.sf.proguard:proguard-gradle:4.10'
+
+ testCompile 'junit:junit:3.8.1'
+
+ buildTestCompile sourceSets.main.output
+ buildTestCompile sourceSets.test.output
+ buildTestCompile configurations.testCompile
+ buildTestCompile configurations.testRuntime
+
+ deviceTestCompile sourceSets.main.output
+ deviceTestCompile sourceSets.test.output
+ deviceTestCompile sourceSets.buildTest.output
+ deviceTestCompile configurations.testCompile
+ deviceTestCompile configurations.testRuntime
+}
+
+// configuration for dependencies provided by the runtime,
+// in this case proguard.
+configurations{
+ provided
+}
+
+dependencies{
+ provided 'net.sf.proguard:proguard-gradle:4.10'
+}
+
+//Include provided for compilation
+sourceSets.main.compileClasspath += configurations.provided
+
+/*
+idea {
+ module {
+ testSourceDirs += files('src/build-test/groovy', 'src/device-test/groovy').files
+
+ scopes.COMPILE.plus += configurations.provided
+ }
+}
+*/
+
+group = 'com.android.tools.build'
+archivesBaseName = 'gradle'
+project.ext.pomName = 'Gradle Plug-in for Android'
+project.ext.pomDesc = 'Gradle plug-in to build Android applications.'
+
+apply from: '../../buildVersion.gradle'
+apply from: '../../publish.gradle'
+
+jar.manifest.attributes("Plugin-Version": version)
+publishLocal.dependsOn ':builder:publishLocal'
+
+task buildTest(type: Test, dependsOn: publishLocal) {
+ testClassesDir = sourceSets.buildTest.output.classesDir
+ classpath = sourceSets.buildTest.runtimeClasspath
+ description = "Runs the project build tests. This requires an SDK either from the Android source tree, under out/..., or an env var ANDROID_HOME."
+ group = "verification"
+ systemProperties['jar.path'] = jar.archivePath
+}
+
+task deviceTest(type: Test, dependsOn: publishLocal) {
+ testClassesDir = sourceSets.deviceTest.output.classesDir
+ classpath = sourceSets.deviceTest.runtimeClasspath
+ description = "Runs the device tests. This requires a device."
+ group = "verification"
+ systemProperties['jar.path'] = jar.archivePath
+}
+
+check.dependsOn buildTest
+
+
+groovydoc {
+ exclude "**/internal/**"
+ includePrivate false
+
+ docTitle "Gradle Plugin for Android"
+ header ""
+ footer "Copyright (C) 2012 The Android Open Source Project"
+ overview ""
+}
+
+task javadocJar(type: Jar, dependsOn:groovydoc) {
+ classifier 'javadoc'
+ from groovydoc.destinationDir
+}
+
+// add javadoc jar tasks as artifacts
+artifacts {
+ archives javadocJar
+}
+
+apply plugin: 'distrib'
+shipping.isShipping = false
+
diff --git a/build-system/gradle/gradle.iml b/build-system/gradle/gradle.iml
new file mode 100644
index 0000000..27b8d92
--- /dev/null
+++ b/build-system/gradle/gradle.iml
@@ -0,0 +1,1210 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module relativePaths="true" type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/fromGradle/groovy" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/fromGradle/resources" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/main/groovy" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/main/resources" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/test/groovy" isTestSource="true" />
+ <sourceFolder url="file://$MODULE_DIR$/src/device-test/groovy" isTestSource="true" />
+ <sourceFolder url="file://$MODULE_DIR$/src/build-test/groovy" isTestSource="true" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/groovy-all-1.8.6.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/gradle-core-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/asm-all-4.0.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/ant-1.9.2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/commons-collections-3.2.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/commons-io-1.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/commons-lang-2.6.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/ivy-2.2.0.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/logback-core-1.0.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/logback-classic-1.0.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/guava-jdk5-14.0.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/jcip-annotations-1.0.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/jul-to-slf4j-1.7.2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/jarjar-1.3.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/javax.inject-1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/slf4j-api-1.7.2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/log4j-over-slf4j-1.7.2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/jcl-over-slf4j-1.7.2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/ant-launcher-1.9.2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/jsch-0.1.46.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/gradle-docs-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/gradle-base-services-groovy-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/gradle-base-services-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/gradle-resources-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/gradle-cli-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/gradle-native-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/jna-3.2.7.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/native-platform-0.3-rc-2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/jna-posix-1.0.3.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/jansi-1.2.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/native-platform-osx-universal-0.3-rc-2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/native-platform-linux-amd64-0.3-rc-2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/native-platform-linux-i386-0.3-rc-2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/native-platform-windows-amd64-0.3-rc-2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/native-platform-windows-i386-0.3-rc-2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/gradle-messaging-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/kryo-2.20.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/asm-4.0.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/reflectasm-1.07-shaded.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/minlog-1.2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/objenesis-1.2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/gradle-core-impl-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jarjar-maven-settings-3.0.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jarjar-maven-repository-metadata-3.0.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jarjar-plexus-container-default-1.5.5.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jarjar-maven-aether-provider-3.0.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jarjar-wagon-provider-api-2.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jarjar-plexus-cipher-1.7.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jarjar-plexus-interpolation-1.14.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jarjar-plexus-utils-2.0.6.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jarjar-plexus-classworlds-2.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jarjar-maven-plugin-api-3.0.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jarjar-maven-model-builder-3.0.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jarjar-plexus-sec-dispatcher-1.3.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jarjar-plexus-component-annotations-1.5.5.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jarjar-aether-connector-wagon-1.13.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jarjar-maven-compat-3.0.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jarjar-wagon-http-2.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jarjar-aether-api-1.13.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jarjar-maven-settings-builder-3.0.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jarjar-aether-spi-1.13.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jarjar-maven-core-3.0.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jarjar-wagon-http-shared4-2.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jarjar-aether-util-1.13.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jarjar-maven-artifact-3.0.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jarjar-maven-model-3.0.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jarjar-aether-impl-1.13.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/httpclient-4.2.2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/maven-ant-tasks-2.1.3.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/nekohtml-1.9.14.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/xbean-reflect-3.4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/commons-codec-1.6.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/httpcore-4.2.2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jcifs-1.3.17.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/xml-apis-1.3.04.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/xercesImpl-2.9.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/gradle-tooling-api-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/gradle-plugins-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/junit-4.11.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/testng-6.3.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/commons-cli-1.2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/bsh-2.0b4.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jcommander-1.12.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/snakeyaml-1.6.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/hamcrest-core-1.3.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/gradle-code-quality-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/gradle-jetty-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jetty-6.1.25.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jetty-util-6.1.25.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/servlet-api-2.5-20081211.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jetty-plus-6.1.25.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jsp-2.1-6.1.14.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jetty-annotations-6.1.25.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/geronimo-annotation_1.0_spec-1.0.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jetty-naming-6.1.25.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/core-3.1.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jsp-api-2.1-6.1.14.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/gradle-antlr-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/ant-antlr-1.9.2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/antlr-2.7.7.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/gradle-wrapper-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/gradle-osgi-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/bndlib-2.1.0.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/gradle-maven-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/pmaven-common-0.8-20100325.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/pmaven-groovy-0.8-20100325.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/plexus-component-annotations-1.5.2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/gradle-ide-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/gradle-announce-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/gradle-scala-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/gradle-sonar-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/sonar-runner-2.0.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/sonar-batch-bootstrapper-2.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/gradle-signing-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/bcpg-jdk15-1.46.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/bcprov-jdk15-1.46.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/gradle-cpp-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/gradle-ear-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/gradle-javascript-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/rhino-1.7R3.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/gson-2.2.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/simple-4.1.21.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/gradle-build-comparison-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/gradle-diagnostics-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/gradle-reporting-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/jatl-0.2.2.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/gradle-publish-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/gradle-ivy-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/gradle-jacoco-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/gradle-build-init-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/gradle-language-jvm-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" exported="">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../out/host/gradle/tools/build/.gradle/wrapper/dists/gradle-1.9-bin/57mc7p3gas7d7kj33rqr8ak2qt/gradle-1.9/lib/plugins/gradle-language-base-1.9.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module" module-name="builder" exported="" />
+ <orderEntry type="library" exported="" scope="TEST" name="JUnit3" level="project" />
+ <orderEntry type="module" module-name="lint-cli" exported="" />
+ <orderEntry type="library" exported="" name="proguard-gradle" level="project" />
+ </component>
+</module>
+
diff --git a/build-system/gradle/src/build-test/groovy/com/android/build/gradle/AutomatedBuildTest.java b/build-system/gradle/src/build-test/groovy/com/android/build/gradle/AutomatedBuildTest.java
new file mode 100644
index 0000000..18e3277
--- /dev/null
+++ b/build-system/gradle/src/build-test/groovy/com/android/build/gradle/AutomatedBuildTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Automated tests building a set of projects using a set of gradle versions.
+ *
+ * This requires an SDK, found through the ANDROID_HOME environment variable or present in the
+ * Android Source tree under out/host/<platform>/sdk/... (result of 'make sdk')
+ */
+public class AutomatedBuildTest extends BuildTest {
+
+ private String projectName;
+ private String gradleVersion;
+ private TestType testType;
+
+ private static enum TestType { BUILD, REPORT }
+
+ private static final String[] sBuiltProjects = new String[] {
+ "aidl", "api", "applibtest", "assets", "attrOrder", "basic", "dependencies",
+ "dependencyChecker", "flavored", "flavorlib", "flavors", "genFolderApi",
+ "libProguardJarDep", "libProguardLibDep", "libTestDep", "libsTest", "localJars",
+ "migrated", "multiproject", "multires", "ndkSanAngeles", "ndkJniLib", "overlay1",
+ "overlay2", "pkgOverride", "proguard", "proguardLib", "renderscript", "renderscriptInLib",
+ "renderscriptMultiSrc", "rsSupportMode", "sameNamedLibs", "tictactoe" /*, "autorepo"*/
+ };
+
+ private static final String[] sReportProjects = new String[] {
+ "basic", "flavorlib"
+ };
+
+ public static Test suite() {
+ TestSuite suite = new TestSuite();
+ suite.setName("AutomatedBuildTest");
+
+ for (String gradleVersion : BasePlugin.GRADLE_SUPPORTED_VERSIONS) {
+ if (isIgnoredGradleVersion(gradleVersion)) {
+ continue;
+ }
+ // first the project we build on all available versions of Gradle
+ for (String projectName : sBuiltProjects) {
+ String testName = "build_" + projectName + "_" + gradleVersion;
+
+ AutomatedBuildTest test = (AutomatedBuildTest) TestSuite.createTest(
+ AutomatedBuildTest.class, testName);
+ test.setProjectInfo(projectName, gradleVersion, TestType.BUILD);
+ suite.addTest(test);
+ }
+
+ // then the project to run reports on
+ for (String projectName : sReportProjects) {
+ String testName = "report_" + projectName + "_" + gradleVersion;
+
+ AutomatedBuildTest test = (AutomatedBuildTest) TestSuite.createTest(
+ AutomatedBuildTest.class, testName);
+ test.setProjectInfo(projectName, gradleVersion, TestType.REPORT);
+ suite.addTest(test);
+ }
+ }
+
+ return suite;
+ }
+
+ private void setProjectInfo(String projectName, String gradleVersion, TestType testType) {
+ this.projectName = projectName;
+ this.gradleVersion = gradleVersion;
+ this.testType = testType;
+ }
+
+ @Override
+ protected void runTest() throws Throwable {
+ if (testType == TestType.BUILD) {
+ buildProject(projectName, gradleVersion);
+ } else if (testType == TestType.REPORT) {
+ runTasksOn(projectName, gradleVersion, "androidDependencies", "signingReport");
+ }
+ }
+}
diff --git a/build-system/gradle/src/build-test/groovy/com/android/build/gradle/BuildTest.java b/build-system/gradle/src/build-test/groovy/com/android/build/gradle/BuildTest.java
new file mode 100644
index 0000000..c81d0db
--- /dev/null
+++ b/build-system/gradle/src/build-test/groovy/com/android/build/gradle/BuildTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle;
+
+import com.android.build.gradle.internal.test.BaseTest;
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.util.Collection;
+
+/**
+ * Base class for build tests.
+ *
+ * This requires an SDK, found through the ANDROID_HOME environment variable or present in the
+ * Android Source tree under out/host/<platform>/sdk/... (result of 'make sdk')
+ */
+abstract class BuildTest extends BaseTest {
+ private static final Collection<String> IGNORED_GRADLE_VERSIONS = Lists.newArrayList();
+
+ protected File testDir;
+ protected File sdkDir;
+ protected File ndkDir;
+
+ @Override
+ protected void setUp() throws Exception {
+ testDir = getTestDir();
+ sdkDir = getSdkDir();
+ ndkDir = getNdkDir();
+ }
+
+ /**
+ * Indicates whether the given Gradle version should be ignored in tests (for example, when a Gradle version has
+ * not been publicly released yet.)
+ *
+ * @param gradleVersion the given Gradle version.
+ * @return {@code true} if the given Gradle version should be ignored, {@code false} otherwise.
+ */
+ protected static boolean isIgnoredGradleVersion(String gradleVersion) {
+ return IGNORED_GRADLE_VERSIONS.contains(gradleVersion);
+ }
+
+ protected File buildProject(String name, String gradleVersion) {
+ return runTasksOnProject(name, gradleVersion, "clean", "assembleDebug");
+ }
+
+ protected File runTasksOnProject(String name, String gradleVersion, String... tasks) {
+ File project = new File(testDir, name);
+
+ File buildGradle = new File(project, "build.gradle");
+ assertTrue("Missing build.gradle for " + name, buildGradle.isFile());
+
+ // build the project
+ runGradleTasks(sdkDir, ndkDir, gradleVersion, project, tasks);
+
+ return project;
+ }
+}
diff --git a/build-system/gradle/src/build-test/groovy/com/android/build/gradle/ManualBuildTest.java b/build-system/gradle/src/build-test/groovy/com/android/build/gradle/ManualBuildTest.java
new file mode 100644
index 0000000..e0c985b
--- /dev/null
+++ b/build-system/gradle/src/build-test/groovy/com/android/build/gradle/ManualBuildTest.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle;
+
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Some manual tests for building projects.
+ *
+ * This requires an SDK, found through the ANDROID_HOME environment variable or present in the
+ * Android Source tree under out/host/<platform>/sdk/... (result of 'make sdk')
+ */
+public class ManualBuildTest extends BuildTest {
+
+ private final static int RED = 0xFFFF0000;
+ private final static int GREEN = 0xFF00FF00;
+ private final static int BLUE = 0xFF0000FF;
+
+
+ public void testOverlay1Content() throws Exception {
+ File project = buildProject("overlay1", BasePlugin.GRADLE_MIN_VERSION);
+ File drawableOutput = new File(project, "build/res/all/debug/drawable");
+
+ checkImageColor(drawableOutput, "no_overlay.png", GREEN);
+ checkImageColor(drawableOutput, "type_overlay.png", GREEN);
+ }
+
+ public void testOverlay2Content() throws Exception {
+ File project = buildProject("overlay2", BasePlugin.GRADLE_MIN_VERSION);
+ File drawableOutput = new File(project, "build/res/all/one/debug/drawable");
+
+ checkImageColor(drawableOutput, "no_overlay.png", GREEN);
+ checkImageColor(drawableOutput, "type_overlay.png", GREEN);
+ checkImageColor(drawableOutput, "flavor_overlay.png", GREEN);
+ checkImageColor(drawableOutput, "type_flavor_overlay.png", GREEN);
+ checkImageColor(drawableOutput, "variant_type_flavor_overlay.png", GREEN);
+ }
+
+ public void testOverlay3Content() throws Exception {
+ File project = buildProject("overlay3", BasePlugin.GRADLE_MIN_VERSION);
+ File drawableOutput = new File(project, "build/res/all/freebeta/debug/drawable");
+
+ checkImageColor(drawableOutput, "no_overlay.png", GREEN);
+ checkImageColor(drawableOutput, "debug_overlay.png", GREEN);
+ checkImageColor(drawableOutput, "beta_overlay.png", GREEN);
+ checkImageColor(drawableOutput, "free_overlay.png", GREEN);
+ checkImageColor(drawableOutput, "free_beta_overlay.png", GREEN);
+ checkImageColor(drawableOutput, "free_beta_debug_overlay.png", GREEN);
+ checkImageColor(drawableOutput, "free_normal_overlay.png", RED);
+
+ drawableOutput = new File(project, "build/res/all/freenormal/debug/drawable");
+
+ checkImageColor(drawableOutput, "no_overlay.png", GREEN);
+ checkImageColor(drawableOutput, "debug_overlay.png", GREEN);
+ checkImageColor(drawableOutput, "beta_overlay.png", RED);
+ checkImageColor(drawableOutput, "free_overlay.png", GREEN);
+ checkImageColor(drawableOutput, "free_beta_overlay.png", RED);
+ checkImageColor(drawableOutput, "free_beta_debug_overlay.png", RED);
+ checkImageColor(drawableOutput, "free_normal_overlay.png", GREEN);
+
+ drawableOutput = new File(project, "build/res/all/paidbeta/debug/drawable");
+
+ checkImageColor(drawableOutput, "no_overlay.png", GREEN);
+ checkImageColor(drawableOutput, "debug_overlay.png", GREEN);
+ checkImageColor(drawableOutput, "beta_overlay.png", GREEN);
+ checkImageColor(drawableOutput, "free_overlay.png", RED);
+ checkImageColor(drawableOutput, "free_beta_overlay.png", RED);
+ checkImageColor(drawableOutput, "free_beta_debug_overlay.png", RED);
+ checkImageColor(drawableOutput, "free_normal_overlay.png", RED);
+ }
+
+ public void testRepo() {
+ File repo = new File(testDir, "repo");
+
+ try {
+ runGradleTasks(sdkDir, ndkDir, BasePlugin.GRADLE_MIN_VERSION,
+ new File(repo, "util"), "clean", "uploadArchives");
+ runGradleTasks(sdkDir, ndkDir, BasePlugin.GRADLE_MIN_VERSION,
+ new File(repo, "baseLibrary"), "clean", "uploadArchives");
+ runGradleTasks(sdkDir, ndkDir, BasePlugin.GRADLE_MIN_VERSION,
+ new File(repo, "library"), "clean", "uploadArchives");
+ runGradleTasks(sdkDir, ndkDir, BasePlugin.GRADLE_MIN_VERSION,
+ new File(repo, "app"), "clean", "assemble");
+ } finally {
+ // clean up the test repository.
+ File testRepo = new File(repo, "testrepo");
+ deleteFolder(testRepo);
+ }
+ }
+
+ // test whether a library project has its fields ProGuarded
+ public void testLibProguard() throws Exception {
+ File project = new File(testDir, "libProguard");
+ File fileOutput = new File(project, "build/proguard/release");
+
+ runGradleTasks(sdkDir, ndkDir, BasePlugin.GRADLE_MIN_VERSION,
+ project, "clean", "build");
+ checkFile(fileOutput, "mapping.txt", new String[]{"int proguardInt -> a"});
+
+ }
+
+ // test whether proguard.txt has been correctly merged
+ public void testLibProguardConsumerFile() throws Exception {
+ File project = new File(testDir, "libProguardConsumerFiles");
+ File debugFileOutput = new File(project, "build/bundles/debug");
+ File releaseFileOutput = new File(project, "build/bundles/release");
+
+ runGradleTasks(sdkDir, ndkDir, BasePlugin.GRADLE_MIN_VERSION,
+ project, "clean", "build");
+ checkFile(debugFileOutput, "proguard.txt", new String[]{"A"});
+ checkFile(releaseFileOutput, "proguard.txt", new String[]{"A", "B", "C"});
+ }
+
+ public void test3rdPartyTests() throws Exception {
+ // custom because we want to run deviceCheck even without devices, since we use
+ // a fake DeviceProvider that doesn't use a device, but only record the calls made
+ // to the DeviceProvider and the DeviceConnector.
+ runGradleTasks(sdkDir, ndkDir, BasePlugin.GRADLE_MIN_VERSION,
+ new File(testDir, "3rdPartyTests"), "clean", "deviceCheck");
+ }
+
+ private static void checkImageColor(File folder, String fileName, int expectedColor)
+ throws IOException {
+ File f = new File(folder, fileName);
+ assertTrue("File '" + f.getAbsolutePath() + "' does not exist.", f.isFile());
+
+ BufferedImage image = ImageIO.read(f);
+ int rgb = image.getRGB(0, 0);
+ assertEquals(String.format("Expected: 0x%08X, actual: 0x%08X for file %s",
+ expectedColor, rgb, f),
+ expectedColor, rgb);
+ }
+
+ private static void checkFile(File folder, String fileName, String[] expectedContents)
+ throws IOException {
+ File f = new File(folder, fileName);
+ assertTrue("File '" + f.getAbsolutePath() + "' does not exist.", f.isFile());
+
+ String contents = Files.toString(f, Charsets.UTF_8);
+ for (String expectedContent : expectedContents) {
+ assertTrue("File '" + f.getAbsolutePath() + "' does not contain: " + expectedContent,
+ contents.contains(expectedContent));
+ }
+ }
+}
diff --git a/build-system/gradle/src/device-test/groovy/com/android/build/gradle/DeviceTest.java b/build-system/gradle/src/device-test/groovy/com/android/build/gradle/DeviceTest.java
new file mode 100644
index 0000000..0e410f1
--- /dev/null
+++ b/build-system/gradle/src/device-test/groovy/com/android/build/gradle/DeviceTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * DeviceConnector tests.
+ *
+ * This build relies on the {@link BuildTest} to have been run, so that all that there
+ * is left to do is deploy the tested and test apps to the device and run the tests (and gather
+ * the result).
+ *
+ * The dependency on the build tests is ensured by the gradle tasks definition.
+ *
+ * This does not test every projects under tests, instead it's a selection that actually has
+ * tests.
+ *
+ */
+public class DeviceTest extends BuildTest {
+
+ private String projectName;
+ private String gradleVersion;
+
+ private static final String[] sBuiltProjects = new String[] {
+ "api", "assets", "applibtest", "attrOrder", "basic", "dependencies", "flavored",
+ "flavorlib", "flavors", "libProguardJarDep", "libProguardLibDep", "libTestDep", "libsTest",
+ "migrated", "multires", "ndkJniLib", "overlay1", "overlay2", "pkgOverride", "proguard",
+ "proguardLib", "sameNamedLibs"
+ };
+
+ public static Test suite() {
+ TestSuite suite = new TestSuite();
+ suite.setName("DeviceTest");
+
+ for (String gradleVersion : BasePlugin.GRADLE_SUPPORTED_VERSIONS) {
+ if (isIgnoredGradleVersion(gradleVersion)) {
+ continue;
+ }
+ // first the project we build on all available versions of Gradle
+ for (String projectName : sBuiltProjects) {
+ String testName = "check_" + projectName + "_" + gradleVersion;
+
+ DeviceTest test = (DeviceTest) TestSuite.createTest(DeviceTest.class, testName);
+ test.setProjectInfo(projectName, gradleVersion);
+ suite.addTest(test);
+ }
+ }
+
+ return suite;
+ }
+
+ private void setProjectInfo(String projectName, String gradleVersion) {
+ this.projectName = projectName;
+ this.gradleVersion = gradleVersion;
+ }
+
+ @Override
+ protected void runTest() throws Throwable {
+ try {
+ runTasksOnProject(projectName, gradleVersion, "clean", "connectedCheck");
+ } finally {
+ // because runTasksOnProject will throw an exception if the gradle side fails, do this
+ // in the finally block.
+
+ // TODO: Get the test output and copy it in here.
+ }
+ }
+}
\ No newline at end of file
diff --git a/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/AllTestResults.java b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/AllTestResults.java
new file mode 100644
index 0000000..db0873d
--- /dev/null
+++ b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/AllTestResults.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.test.report;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ *
+ * Custom test results based on Gradle's AllTestResults
+ */
+class AllTestResults extends CompositeTestResults {
+ private final Map<String, PackageTestResults> packages = new TreeMap<String, PackageTestResults>();
+
+ public AllTestResults() {
+ super(null);
+ }
+
+ @Override
+ public String getTitle() {
+ return "Test Summary";
+ }
+
+ public Collection<PackageTestResults> getPackages() {
+ return packages.values();
+ }
+
+ @Override
+ public String getName() {
+ return null;
+ }
+
+ public TestResult addTest(String className, String testName, long duration,
+ String device, String project, String flavor) {
+ PackageTestResults packageResults = addPackageForClass(className);
+ TestResult testResult = addTest(
+ packageResults.addTest(className, testName, duration, device, project, flavor));
+
+ addDevice(device, testResult);
+ addVariant(project, flavor, testResult);
+
+ return testResult;
+ }
+
+ public ClassTestResults addTestClass(String className) {
+ return addPackageForClass(className).addClass(className);
+ }
+
+ private PackageTestResults addPackageForClass(String className) {
+ String packageName;
+ int pos = className.lastIndexOf(".");
+ if (pos != -1) {
+ packageName = className.substring(0, pos);
+ } else {
+ packageName = "";
+ }
+ return addPackage(packageName);
+ }
+
+ private PackageTestResults addPackage(String packageName) {
+
+ PackageTestResults packageResults = packages.get(packageName);
+ if (packageResults == null) {
+ packageResults = new PackageTestResults(packageName, this);
+ packages.put(packageName, packageResults);
+ }
+ return packageResults;
+ }
+}
diff --git a/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/ClassPageRenderer.java b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/ClassPageRenderer.java
new file mode 100644
index 0000000..ee31146
--- /dev/null
+++ b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/ClassPageRenderer.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.test.report;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.gradle.api.internal.ErroringAction;
+import org.gradle.api.internal.html.SimpleHtmlWriter;
+import org.gradle.api.internal.tasks.testing.junit.result.TestFailure;
+import org.gradle.reporting.CodePanelRenderer;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.gradle.api.tasks.testing.TestResult.ResultType;
+
+/**
+ * Custom ClassPageRenderer based on Gradle's ClassPageRenderer
+ */
+class ClassPageRenderer extends PageRenderer<ClassTestResults> {
+ private final CodePanelRenderer codePanelRenderer = new CodePanelRenderer();
+
+ ClassPageRenderer(ReportType reportType) {
+ super(reportType);
+ }
+
+ @Override
+ protected String getTitle() {
+ return getModel().getTitle();
+ }
+
+ @Override
+ protected void renderBreadcrumbs(SimpleHtmlWriter htmlWriter) throws IOException {
+ htmlWriter.startElement("div").attribute("class", "breadcrumbs")
+ .startElement("a").attribute("href", "index.html").characters("all").endElement()
+ .characters(" > ")
+ .startElement("a").attribute("href", String.format("%s.html", getResults().getPackageResults().getFilename(reportType))).characters(getResults().getPackageResults().getName()).endElement()
+ .characters(String.format(" > %s", getResults().getSimpleName()))
+ .endElement();
+ }
+
+ private void renderTests(SimpleHtmlWriter htmlWriter) throws IOException {
+ htmlWriter.startElement("table")
+ .startElement("thead")
+ .startElement("tr")
+ .startElement("th").characters("Test").endElement();
+
+ // get all the results per device and per test name
+ Map<String, Map<String, TestResult>> results = getResults().getTestResultsMap();
+
+ // gather all devices.
+ List<String> devices = Lists.newArrayList(results.keySet());
+ Collections.sort(devices);
+
+ for (String device : devices) {
+ htmlWriter.startElement("th").characters(device).endElement();
+ }
+ htmlWriter.endElement().endElement(); // tr/thead
+
+ // gather all tests
+ Set<String> tests = Sets.newHashSet();
+ for (Map<String, TestResult> deviceMap : results.values()) {
+ tests.addAll(deviceMap.keySet());
+ }
+ List<String> sortedTests = Lists.newArrayList(tests);
+ Collections.sort(sortedTests);
+
+ for (String testName : sortedTests) {
+ htmlWriter.startElement("tr").startElement("td").characters(testName).endElement();
+
+ ResultType currentType = ResultType.SKIPPED;
+
+ // loop for all devices to find this test and put its result
+ for (String device : devices) {
+ Map<String, TestResult> deviceMap = results.get(device);
+ TestResult test = deviceMap.get(testName);
+
+ htmlWriter.startElement("td").attribute("class", test.getStatusClass())
+ .characters(String.format("%s (%s)",
+ test.getFormattedResultType(), test.getFormattedDuration()))
+ .endElement();
+
+ currentType = combineResultType(currentType, test.getResultType());
+ }
+
+ // finally based on whether if a single test failed, set the class on the test name.
+//todo td.setAttribute("class", getStatusClass(currentType));
+
+ htmlWriter.endElement(); //tr
+ }
+ htmlWriter.endElement(); // table
+ }
+
+ public static ResultType combineResultType(ResultType currentType, ResultType newType) {
+ switch (currentType) {
+ case SUCCESS:
+ if (newType == ResultType.FAILURE) {
+ return newType;
+ }
+
+ return currentType;
+ case FAILURE:
+ return currentType;
+ case SKIPPED:
+ if (newType != ResultType.SKIPPED) {
+ return newType;
+ }
+ return currentType;
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
+ public String getStatusClass(ResultType resultType) {
+ switch (resultType) {
+ case SUCCESS:
+ return "success";
+ case FAILURE:
+ return "failures";
+ case SKIPPED:
+ return "skipped";
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
+ private static final class TestPercent {
+ int failed;
+ int total;
+ TestPercent(int failed, int total) {
+ this.failed = failed;
+ this.total = total;
+ }
+
+ boolean isFullFailure() {
+ return failed == total;
+ }
+ }
+
+ @Override
+ protected void renderFailures(SimpleHtmlWriter htmlWriter) throws IOException {
+ // get all the results per device and per test name
+ Map<String, Map<String, TestResult>> results = getResults().getTestResultsMap();
+
+ Map<String, TestPercent> testPassPercent = Maps.newHashMap();
+
+ for (TestResult test : getResults().getFailures()) {
+ String testName = test.getName();
+ // compute the display name which will include the name of the device and how many
+ // devices are impact so to not force counting.
+ // If all devices, then we don't display all of them.
+ // (The off chance that all devices fail the test with a different stack trace is slim)
+ TestPercent percent = testPassPercent.get(testName);
+ if (percent != null && percent.isFullFailure()) {
+ continue;
+ }
+
+ if (percent == null) {
+ int failed = 0;
+ int total = 0;
+ for (Map<String, TestResult> deviceMap : results.values()) {
+ ResultType resultType = deviceMap.get(testName).getResultType();
+
+ if (resultType == ResultType.FAILURE) {
+ failed++;
+ }
+
+ if (resultType != ResultType.SKIPPED) {
+ total++;
+ }
+ }
+
+ percent = new TestPercent(failed, total);
+ testPassPercent.put(testName, percent);
+ }
+
+ String name;
+ if (percent.total == 1) {
+ name = testName;
+ } else if (percent.isFullFailure()) {
+ name = testName + " [all devices]";
+ } else {
+ name = String.format("%s [%s] (on %d/%d devices)", testName, test.getDevice(),
+ percent.failed, percent.total);
+ }
+
+ htmlWriter.startElement("div").attribute("class", "test")
+ .startElement("a").attribute("name", test.getId().toString()).characters("").endElement() //browsers dont understand <a name="..."/>
+ .startElement("h3").attribute("class", test.getStatusClass()).characters(name).endElement();
+ for (TestFailure failure : test.getFailures()) {
+ codePanelRenderer.render(failure.getStackTrace(), htmlWriter);
+ }
+ htmlWriter.endElement();
+ }
+ }
+
+ @Override
+ protected void registerTabs() {
+ addFailuresTab();
+ addTab("Tests", new ErroringAction<SimpleHtmlWriter>() {
+ @Override
+ public void doExecute(SimpleHtmlWriter writer) throws IOException {
+ renderTests(writer);
+ }
+ });
+ addDeviceAndVariantTabs();
+ }
+}
diff --git a/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/ClassTestResults.java b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/ClassTestResults.java
new file mode 100644
index 0000000..c7b924f
--- /dev/null
+++ b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/ClassTestResults.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.test.report;
+
+import com.google.common.collect.Maps;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * Custom ClassTestResults based on Gradle's ClassTestResults
+ */
+class ClassTestResults extends CompositeTestResults {
+
+ private final String name;
+ private final PackageTestResults packageResults;
+ private final Set<TestResult> results = new TreeSet<TestResult>();
+ private final StringBuilder standardOutput = new StringBuilder();
+ private final StringBuilder standardError = new StringBuilder();
+
+ public ClassTestResults(String name, PackageTestResults packageResults) {
+ super(packageResults);
+ this.name = name;
+ this.packageResults = packageResults;
+ }
+
+ @Override
+ public String getTitle() {
+ return String.format("Class %s", name);
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ public String getSimpleName() {
+ int pos = name.lastIndexOf(".");
+ if (pos != -1) {
+ return name.substring(pos + 1);
+ }
+ return name;
+ }
+
+ public PackageTestResults getPackageResults() {
+ return packageResults;
+ }
+
+ public Map<String, Map<String, TestResult>> getTestResultsMap() {
+ Map<String, Map<String, TestResult>> map = Maps.newHashMap();
+ for (TestResult result : results) {
+ String device = result.getDevice();
+
+ Map<String, TestResult> deviceMap = map.get(device);
+ if (deviceMap == null) {
+ deviceMap = Maps.newHashMap();
+ map.put(device, deviceMap);
+ }
+
+ deviceMap.put(result.getName(), result);
+ }
+
+ return map;
+ }
+
+ public CharSequence getStandardError() {
+ return standardError;
+ }
+
+ public CharSequence getStandardOutput() {
+ return standardOutput;
+ }
+
+ public TestResult addTest(String testName, long duration,
+ String device, String project, String flavor) {
+ TestResult test = new TestResult(testName, duration, device, project, flavor, this);
+ results.add(test);
+
+ addDevice(device, test);
+ addVariant(project, flavor, test);
+
+ return addTest(test);
+ }
+
+ public void addStandardOutput(String textContent) {
+ standardOutput.append(textContent);
+ }
+
+ public void addStandardError(String textContent) {
+ standardError.append(textContent);
+ }
+}
diff --git a/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/CompositeTestResults.java b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/CompositeTestResults.java
new file mode 100644
index 0000000..aab9cc6
--- /dev/null
+++ b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/CompositeTestResults.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.test.report;
+
+import com.android.builder.BuilderConstants;
+import org.gradle.api.internal.tasks.testing.junit.report.TestResultModel;
+
+import java.math.BigDecimal;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import static org.gradle.api.tasks.testing.TestResult.ResultType;
+
+/**
+ * Custom CompositeTestResults based on Gradle's CompositeTestResults
+ */
+public abstract class CompositeTestResults extends TestResultModel {
+ private final CompositeTestResults parent;
+ private int tests;
+ private final Set<TestResult> failures = new TreeSet<TestResult>();
+ private long duration;
+
+ private final Map<String, DeviceTestResults> devices = new TreeMap<String, DeviceTestResults>();
+ private final Map<String, VariantTestResults> variants = new TreeMap<String, VariantTestResults>();
+
+
+ protected CompositeTestResults(CompositeTestResults parent) {
+ this.parent = parent;
+ }
+
+ public String getFilename(ReportType reportType) {
+ return getName();
+ }
+
+ public abstract String getName();
+
+ public int getTestCount() {
+ return tests;
+ }
+
+ public int getFailureCount() {
+ return failures.size();
+ }
+
+ @Override
+ public long getDuration() {
+ return duration;
+ }
+
+ @Override
+ public String getFormattedDuration() {
+ return getTestCount() == 0 ? "-" : super.getFormattedDuration();
+ }
+
+ public Set<TestResult> getFailures() {
+ return failures;
+ }
+
+ Map<String, DeviceTestResults> getResultsPerDevices() {
+ return devices;
+ }
+
+ Map<String, VariantTestResults> getResultsPerVariants() {
+ return variants;
+ }
+
+ @Override
+ public ResultType getResultType() {
+ return failures.isEmpty() ? ResultType.SUCCESS : ResultType.FAILURE;
+ }
+
+ public String getFormattedSuccessRate() {
+ Number successRate = getSuccessRate();
+ if (successRate == null) {
+ return "-";
+ }
+ return successRate + "%";
+ }
+
+ public Number getSuccessRate() {
+ if (getTestCount() == 0) {
+ return null;
+ }
+
+ BigDecimal tests = BigDecimal.valueOf(getTestCount());
+ BigDecimal successful = BigDecimal.valueOf(getTestCount() - getFailureCount());
+
+ return successful.divide(tests, 2,
+ BigDecimal.ROUND_DOWN).multiply(BigDecimal.valueOf(100)).intValue();
+ }
+
+ protected void failed(TestResult failedTest,
+ String deviceName, String projectName, String flavorName) {
+ failures.add(failedTest);
+ if (parent != null) {
+ parent.failed(failedTest, deviceName, projectName, flavorName);
+ }
+
+ DeviceTestResults deviceResults = devices.get(deviceName);
+ if (deviceResults != null) {
+ deviceResults.failed(failedTest, deviceName, projectName, flavorName);
+ }
+
+ String key = getVariantKey(projectName, flavorName);
+ VariantTestResults variantResults = variants.get(key);
+ if (variantResults != null) {
+ variantResults.failed(failedTest, deviceName, projectName, flavorName);
+ }
+ }
+
+ protected TestResult addTest(TestResult test) {
+ tests++;
+ duration += test.getDuration();
+ return test;
+ }
+
+ protected void addDevice(String deviceName, TestResult testResult) {
+ DeviceTestResults deviceResults = devices.get(deviceName);
+ if (deviceResults == null) {
+ deviceResults = new DeviceTestResults(deviceName, null);
+ devices.put(deviceName, deviceResults);
+ }
+
+ deviceResults.addTest(testResult);
+ }
+
+ protected void addVariant(String projectName, String flavorName, TestResult testResult) {
+ String key = getVariantKey(projectName, flavorName);
+ VariantTestResults variantResults = variants.get(key);
+ if (variantResults == null) {
+ variantResults = new VariantTestResults(key, null);
+ variants.put(key, variantResults);
+ }
+
+ variantResults.addTest(testResult);
+ }
+
+ private static String getVariantKey(String projectName, String flavorName) {
+ if (BuilderConstants.MAIN.equalsIgnoreCase(flavorName)) {
+ return projectName;
+ }
+
+ return projectName + ":" + flavorName;
+ }
+}
diff --git a/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/DeviceTestResults.java b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/DeviceTestResults.java
new file mode 100644
index 0000000..f801e7b
--- /dev/null
+++ b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/DeviceTestResults.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.test.report;
+
+import com.android.annotations.NonNull;
+
+/**
+ * DeviceTestResults to accumulate results per device.
+ */
+class DeviceTestResults extends CompositeTestResults {
+
+ private final String name;
+
+ public DeviceTestResults(@NonNull String name, CompositeTestResults parent) {
+ super(parent);
+ this.name = name;
+ }
+
+ @Override
+ public String getTitle() {
+ return name;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+}
diff --git a/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/OverviewPageRenderer.java b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/OverviewPageRenderer.java
new file mode 100644
index 0000000..f289179
--- /dev/null
+++ b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/OverviewPageRenderer.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.test.report;
+
+import org.gradle.api.internal.ErroringAction;
+import org.gradle.api.internal.html.SimpleHtmlWriter;
+
+import java.io.IOException;
+
+/**
+ * Custom OverviewPageRenderer based on Gradle's OverviewPageRenderer
+ */
+class OverviewPageRenderer extends PageRenderer<AllTestResults> {
+
+ public OverviewPageRenderer(ReportType reportType) {
+ super(reportType);
+ }
+
+ @Override
+ protected void registerTabs() {
+ addFailuresTab();
+ if (!getResults().getPackages().isEmpty()) {
+ addTab("Packages", new ErroringAction<SimpleHtmlWriter>() {
+ @Override
+ protected void doExecute(SimpleHtmlWriter writer) throws IOException {
+ renderPackages(writer);
+ }
+ });
+ }
+ addTab("Classes", new ErroringAction<SimpleHtmlWriter>() {
+ @Override
+ public void doExecute(SimpleHtmlWriter htmlWriter) throws IOException {
+ renderClasses(htmlWriter);
+ }
+ });
+ }
+
+ @Override
+ protected void renderBreadcrumbs(SimpleHtmlWriter htmlWriter) {
+ }
+
+ private void renderPackages(SimpleHtmlWriter htmlWriter) throws IOException {
+ htmlWriter.startElement("table");
+ htmlWriter.startElement("thead");
+ htmlWriter.startElement("tr");
+ htmlWriter.startElement("th").characters("Package").endElement();
+ htmlWriter.startElement("th").characters("Tests").endElement();
+ htmlWriter.startElement("th").characters("Failures").endElement();
+ htmlWriter.startElement("th").characters("Duration").endElement();
+ htmlWriter.startElement("th").characters("Success rate").endElement();
+ htmlWriter.endElement();
+ htmlWriter.endElement();
+ htmlWriter.startElement("tbody");
+ for (PackageTestResults testPackage : getResults().getPackages()) {
+ htmlWriter.startElement("tr");
+ htmlWriter.startElement("td").attribute("class", testPackage.getStatusClass());
+ htmlWriter.startElement("a").attribute("href", String.format("%s.html", testPackage.getFilename(reportType))).characters(testPackage.getName()).endElement();
+ htmlWriter.endElement();
+ htmlWriter.startElement("td").characters(Integer.toString(testPackage.getTestCount())).endElement();
+ htmlWriter.startElement("td").characters(Integer.toString(testPackage.getFailureCount())).endElement();
+ htmlWriter.startElement("td").characters(testPackage.getFormattedDuration()).endElement();
+ htmlWriter.startElement("td").attribute("class", testPackage.getStatusClass()).characters(testPackage.getFormattedSuccessRate()).endElement();
+ htmlWriter.endElement();
+ }
+ htmlWriter.endElement();
+ htmlWriter.endElement();
+ }
+
+ private void renderClasses(SimpleHtmlWriter htmlWriter) throws IOException {
+ htmlWriter.startElement("table");
+ htmlWriter.startElement("thead");
+ htmlWriter.startElement("tr");
+ htmlWriter.startElement("th").characters("Class").endElement();
+ htmlWriter.startElement("th").characters("Tests").endElement();
+ htmlWriter.startElement("th").characters("Failures").endElement();
+ htmlWriter.startElement("th").characters("Duration").endElement();
+ htmlWriter.startElement("th").characters("Success rate").endElement();
+ htmlWriter.endElement();
+ htmlWriter.endElement();
+ htmlWriter.startElement("tbody");
+
+ for (PackageTestResults testPackage : getResults().getPackages()) {
+ for (ClassTestResults testClass : testPackage.getClasses()) {
+ htmlWriter.startElement("tr");
+ htmlWriter.startElement("td").attribute("class", testClass.getStatusClass()).endElement();
+ htmlWriter.startElement("a").attribute("href", String.format("%s.html", testClass.getFilename(reportType))).characters(testClass.getName()).endElement();
+ htmlWriter.startElement("td").characters(Integer.toString(testClass.getTestCount())).endElement();
+ htmlWriter.startElement("td").characters(Integer.toString(testClass.getFailureCount())).endElement();
+ htmlWriter.startElement("td").characters(testClass.getFormattedDuration()).endElement();
+ htmlWriter.startElement("td").attribute("class", testClass.getStatusClass()).characters(testClass.getFormattedSuccessRate()).endElement();
+ htmlWriter.endElement();
+ }
+ }
+ htmlWriter.endElement();
+ htmlWriter.endElement();
+ }
+}
diff --git a/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/PackagePageRenderer.java b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/PackagePageRenderer.java
new file mode 100644
index 0000000..796617f
--- /dev/null
+++ b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/PackagePageRenderer.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.test.report;
+
+import org.gradle.api.internal.ErroringAction;
+import org.gradle.api.internal.html.SimpleHtmlWriter;
+
+import java.io.IOException;
+
+/**
+ * Custom PackagePageRenderer based on Gradle's PackagePageRenderer
+ */
+public class PackagePageRenderer extends PageRenderer<PackageTestResults> {
+
+ public PackagePageRenderer(ReportType reportType) {
+ super(reportType);
+ }
+
+ @Override
+ protected String getTitle() {
+ return getModel().getTitle();
+ }
+
+ @Override
+ protected void renderBreadcrumbs(SimpleHtmlWriter htmlWriter) throws IOException {
+ htmlWriter.startElement("div").attribute("class", "breadcrumbs");
+ htmlWriter.startElement("a").attribute("href", "index.html").characters("all").endElement();
+ htmlWriter.characters(String.format(" > %s", getResults().getName()));
+ htmlWriter.endElement();
+ }
+
+ private void renderClasses(SimpleHtmlWriter htmlWriter) throws IOException {
+ htmlWriter.startElement("table");
+ htmlWriter.startElement("thread");
+ htmlWriter.startElement("tr");
+
+ htmlWriter.startElement("th").characters("Class").endElement();
+ htmlWriter.startElement("th").characters("Tests").endElement();
+ htmlWriter.startElement("th").characters("Failures").endElement();
+ htmlWriter.startElement("th").characters("Duration").endElement();
+ htmlWriter.startElement("th").characters("Success rate").endElement();
+
+ htmlWriter.endElement();
+ htmlWriter.endElement();
+
+ for (ClassTestResults testClass : getResults().getClasses()) {
+ htmlWriter.startElement("tr");
+ htmlWriter.startElement("td").attribute("class", testClass.getStatusClass());
+ htmlWriter.startElement("a").attribute("href", String.format("%s.html", testClass.getFilename(reportType))).characters(testClass.getSimpleName()).endElement();
+ htmlWriter.endElement();
+ htmlWriter.startElement("td").characters(Integer.toString(testClass.getTestCount())).endElement();
+ htmlWriter.startElement("td").characters(Integer.toString(testClass.getFailureCount())).endElement();
+ htmlWriter.startElement("td").characters(testClass.getFormattedDuration()).endElement();
+ htmlWriter.startElement("td").attribute("class", testClass.getStatusClass()).characters(testClass.getFormattedSuccessRate()).endElement();
+ htmlWriter.endElement();
+ }
+ htmlWriter.endElement();
+ }
+
+ @Override
+ protected void registerTabs() {
+ addFailuresTab();
+ addTab("Classes", new ErroringAction<SimpleHtmlWriter>() {
+ @Override
+ public void doExecute(SimpleHtmlWriter htmlWriter) throws IOException {
+ renderClasses(htmlWriter);
+ }
+ });
+ }
+}
diff --git a/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/PackageTestResults.java b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/PackageTestResults.java
new file mode 100644
index 0000000..4f410a8
--- /dev/null
+++ b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/PackageTestResults.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.test.report;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Custom PackageTestResults based on Gradle's PackageTestResults
+ */
+class PackageTestResults extends CompositeTestResults {
+
+ private static final String DEFAULT_PACKAGE = "default-package";
+ private final String name;
+ private final Map<String, ClassTestResults> classes = new TreeMap<String, ClassTestResults>();
+
+ public PackageTestResults(String name, AllTestResults model) {
+ super(model);
+ this.name = name.length() == 0 ? DEFAULT_PACKAGE : name;
+ }
+
+ @Override
+ public String getTitle() {
+ return name.equals(DEFAULT_PACKAGE) ? "Default package" : String.format("Package %s", name);
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ public Collection<ClassTestResults> getClasses() {
+ return classes.values();
+ }
+
+ public TestResult addTest(String className, String testName, long duration,
+ String device, String project, String flavor) {
+ ClassTestResults classResults = addClass(className);
+ TestResult testResult = addTest(
+ classResults.addTest(testName, duration, device, project, flavor));
+
+ addDevice(device, testResult);
+ addVariant(project, flavor, testResult);
+
+ return testResult;
+ }
+
+ public ClassTestResults addClass(String className) {
+ ClassTestResults classResults = classes.get(className);
+ if (classResults == null) {
+ classResults = new ClassTestResults(className, this);
+ classes.put(className, classResults);
+ }
+ return classResults;
+ }
+}
diff --git a/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/PageRenderer.java b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/PageRenderer.java
new file mode 100644
index 0000000..f240cf9
--- /dev/null
+++ b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/PageRenderer.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.test.report;
+
+import org.gradle.api.Action;
+import org.gradle.api.internal.ErroringAction;
+import org.gradle.api.internal.html.SimpleHtmlWriter;
+import org.gradle.reporting.ReportRenderer;
+import org.gradle.reporting.TabbedPageRenderer;
+import org.gradle.reporting.TabsRenderer;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * Custom PageRenderer based on Gradle's PageRenderer
+ */
+abstract class PageRenderer<T extends CompositeTestResults> extends TabbedPageRenderer<T> {
+ private T results;
+ private final TabsRenderer<T> tabsRenderer = new TabsRenderer<T>();
+ protected final ReportType reportType;
+
+ PageRenderer(ReportType reportType) {
+ this.reportType = reportType;
+ }
+
+ protected T getResults() {
+ return results;
+ }
+
+ protected abstract void renderBreadcrumbs(SimpleHtmlWriter htmlWriter) throws IOException;
+
+ protected abstract void registerTabs();
+
+ protected void addTab(String title, final Action<SimpleHtmlWriter> contentRenderer) {
+ tabsRenderer.add(title, new ReportRenderer<T, SimpleHtmlWriter>() {
+ @Override
+ public void render(T model, SimpleHtmlWriter writer) {
+ contentRenderer.execute(writer);
+ }
+ });
+ }
+
+ protected void renderTabs(SimpleHtmlWriter htmlWriter) throws IOException {
+ tabsRenderer.render(getModel(), htmlWriter);
+ }
+
+ protected void addFailuresTab() {
+ if (!results.getFailures().isEmpty()) {
+ addTab("Failed tests", new ErroringAction<SimpleHtmlWriter>() {
+ @Override
+ public void doExecute(SimpleHtmlWriter writer) throws IOException {
+ renderFailures(writer);
+ }
+ });
+ }
+ }
+
+ protected void addDeviceAndVariantTabs() {
+ if (results.getResultsPerDevices().size() > 1) {
+ addTab("Devices", new ErroringAction<SimpleHtmlWriter>() {
+ @Override
+ public void doExecute(SimpleHtmlWriter writer) throws IOException {
+ renderCompositeResults(writer, results.getResultsPerDevices(), "Devices");
+ }
+ });
+
+ }
+
+ if (results.getResultsPerVariants().size() > 1) {
+ addTab("Variants", new ErroringAction<SimpleHtmlWriter>() {
+ @Override
+ public void doExecute(SimpleHtmlWriter writer) throws IOException {
+ renderCompositeResults(writer, results.getResultsPerVariants(), "Variants");
+ }
+ });
+ }
+ }
+
+ protected void renderFailures(SimpleHtmlWriter htmlWriter) throws IOException {
+
+ htmlWriter.startElement("ul").attribute("class", "linkList");
+
+ boolean multiDevices = results.getResultsPerDevices().size() > 1;
+ boolean multiVariants = results.getResultsPerVariants().size() > 1;
+
+ htmlWriter.startElement("table");
+ htmlWriter.startElement("thead");
+
+ htmlWriter.startElement("tr");
+ if (multiDevices) {
+ htmlWriter.startElement("th").characters("Devices").endElement();
+ }
+ if (multiVariants) {
+ if (reportType == ReportType.MULTI_PROJECT) {
+ htmlWriter.startElement("th").characters("Project").endElement();
+ htmlWriter.startElement("th").characters("Flavor").endElement();
+ } else if (reportType == ReportType.MULTI_FLAVOR) {
+ htmlWriter.startElement("th").characters("Flavor").endElement();
+ }
+ }
+ htmlWriter.startElement("th").characters("Class").endElement();
+ htmlWriter.startElement("th").characters("Test").endElement();
+
+ htmlWriter.endElement(); //tr
+ htmlWriter.endElement(); //thead
+
+ for (TestResult test : results.getFailures()) {
+ htmlWriter.startElement("tr");
+
+ if (multiDevices) {
+ htmlWriter.startElement("td").characters(test.getDevice()).endElement();
+ }
+ if (multiVariants) {
+ if (reportType == ReportType.MULTI_PROJECT) {
+ htmlWriter.startElement("td").characters(test.getProject()).endElement();
+ htmlWriter.startElement("td").characters(test.getFlavor()).endElement();
+ } else if (reportType == ReportType.MULTI_FLAVOR) {
+ htmlWriter.startElement("td").characters(test.getFlavor()).endElement();
+ }
+ }
+
+ htmlWriter.startElement("td").attribute("class", test.getStatusClass());
+ htmlWriter.endElement();
+
+ htmlWriter.startElement("td")
+ .startElement("a").attribute("href", String.format("%s.html", test.getClassResults().getFilename(reportType)))
+ .characters(test.getClassResults().getSimpleName()).endElement()
+ .endElement();
+
+ htmlWriter.startElement("td")
+ .startElement("a").attribute("href", String.format("%s.html#s", test.getClassResults().getFilename(reportType), test.getName()))
+ .characters(test.getName()).endElement()
+ .endElement();
+ htmlWriter.endElement(); //tr
+ }
+ htmlWriter.endElement(); //table
+ htmlWriter.endElement(); // ul
+
+ }
+
+ protected void renderCompositeResults(SimpleHtmlWriter htmlWriter,
+ Map<String, ? extends CompositeTestResults> map,
+ String name) throws IOException {
+ htmlWriter.startElement("table");
+ htmlWriter.startElement("thead");
+ htmlWriter.startElement("tr");
+ htmlWriter.startElement("th").characters(name).endElement();
+ htmlWriter.startElement("th").characters("Tests").endElement();
+ htmlWriter.startElement("th").characters("Failures").endElement();
+ htmlWriter.startElement("th").characters("Duration").endElement();
+ htmlWriter.startElement("th").characters("Success rate").endElement();
+ htmlWriter.endElement(); //tr
+ htmlWriter.endElement(); //thead
+
+ for (CompositeTestResults results : map.values()) {
+ htmlWriter.startElement("tr");
+ htmlWriter.startElement("td").attribute("class", results.getStatusClass()).characters(results.getName()).endElement();
+ htmlWriter.startElement("td").characters(Integer.toString(results.getTestCount())).endElement();
+ htmlWriter.startElement("td").characters(Integer.toString(results.getFailureCount())).endElement();
+ htmlWriter.startElement("td").characters(results.getFormattedDuration()).endElement();
+ htmlWriter.startElement("td").characters(results.getFormattedSuccessRate()).endElement();
+ htmlWriter.endElement(); //tr
+ }
+
+ htmlWriter.endElement(); //table
+ }
+
+ @Override
+ protected String getTitle() {
+ return getModel().getTitle();
+ }
+
+ @Override
+ protected String getPageTitle() {
+ return String.format("Test results - %s", getModel().getTitle());
+ }
+
+ @Override
+ protected ReportRenderer<T, SimpleHtmlWriter> getHeaderRenderer() {
+ return new ReportRenderer<T, SimpleHtmlWriter>() {
+ @Override
+ public void render(T model, SimpleHtmlWriter htmlWriter) throws IOException {
+ PageRenderer.this.results = model;
+ renderBreadcrumbs(htmlWriter);
+
+ // summary
+ htmlWriter.startElement("div").attribute("id", "summary");
+ htmlWriter.startElement("table");
+ htmlWriter.startElement("tr");
+ htmlWriter.startElement("td");
+ htmlWriter.startElement("div").attribute("class", "summaryGroup");
+ htmlWriter.startElement("table");
+ htmlWriter.startElement("tr");
+ htmlWriter.startElement("td");
+ htmlWriter.startElement("div").attribute("class", "infoBox").attribute("id", "tests");
+ htmlWriter.startElement("div").attribute("class", "counter").characters(Integer.toString(results.getTestCount())).endElement();
+ htmlWriter.startElement("p").characters("tests").endElement();
+ htmlWriter.endElement();
+ htmlWriter.endElement();
+ htmlWriter.startElement("td");
+ htmlWriter.startElement("div").attribute("class", "infoBox").attribute("id", "failures");
+ htmlWriter.startElement("div").attribute("class", "counter").characters(Integer.toString(results.getFailureCount())).endElement();
+ htmlWriter.startElement("p").characters("failures").endElement();
+ htmlWriter.endElement();
+ htmlWriter.endElement();
+ htmlWriter.startElement("td");
+ htmlWriter.startElement("div").attribute("class", "infoBox").attribute("id", "duration");
+ htmlWriter.startElement("div").attribute("class", "counter").characters(results.getFormattedDuration()).endElement();
+ htmlWriter.startElement("p").characters("duration").endElement();
+ htmlWriter.endElement();
+ htmlWriter.endElement();
+ htmlWriter.endElement();
+ htmlWriter.endElement();
+ htmlWriter.endElement();
+ htmlWriter.endElement();
+ htmlWriter.startElement("td");
+ htmlWriter.startElement("div").attribute("class", String.format("infoBox %s", results.getStatusClass())).attribute("id", "successRate");
+ htmlWriter.startElement("div").attribute("class", "percent").characters(results.getFormattedSuccessRate()).endElement();
+ htmlWriter.startElement("p").characters("successful").endElement();
+ htmlWriter.endElement();
+ htmlWriter.endElement();
+ htmlWriter.endElement();
+ htmlWriter.endElement();
+ htmlWriter.endElement();
+ }
+ };
+ }
+
+ @Override
+ protected ReportRenderer<T, SimpleHtmlWriter> getContentRenderer() {
+ return new ReportRenderer<T, SimpleHtmlWriter>() {
+ @Override
+ public void render(T model, SimpleHtmlWriter htmlWriter) throws IOException {
+ PageRenderer.this.results = model;
+ tabsRenderer.clear();
+ registerTabs();
+ renderTabs(htmlWriter);
+ }
+ };
+ }
+}
diff --git a/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TestReport.java b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TestReport.java
new file mode 100644
index 0000000..57675e5
--- /dev/null
+++ b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TestReport.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.test.report;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.internal.tasks.testing.junit.report.LocaleSafeDecimalFormat;
+import org.gradle.reporting.HtmlReportRenderer;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.math.BigDecimal;
+
+/**
+ * Custom test reporter based on Gradle's DefaultTestReport
+ */
+public class TestReport {
+ private final HtmlReportRenderer htmlRenderer = new HtmlReportRenderer();
+ private final ReportType reportType;
+ private final File resultDir;
+ private final File reportDir;
+
+ public TestReport(ReportType reportType, File resultDir, File reportDir) {
+ this.reportType = reportType;
+ this.resultDir = resultDir;
+ this.reportDir = reportDir;
+ htmlRenderer.requireResource(getClass().getResource("report.js"));
+ htmlRenderer.requireResource(getClass().getResource("base-style.css"));
+ htmlRenderer.requireResource(getClass().getResource("style.css"));
+ }
+
+ public void generateReport() {
+ AllTestResults model = loadModel();
+ generateFiles(model);
+ }
+
+ private AllTestResults loadModel() {
+ AllTestResults model = new AllTestResults();
+ if (resultDir.exists()) {
+ for (File file : resultDir.listFiles()) {
+ if (file.getName().startsWith("TEST-") && file.getName().endsWith(".xml")) {
+ mergeFromFile(file, model);
+ }
+ }
+ }
+ return model;
+ }
+
+ private void mergeFromFile(File file, AllTestResults model) {
+ try {
+ InputStream inputStream = new FileInputStream(file);
+ Document document;
+ try {
+ document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(
+ new InputSource(inputStream));
+ } finally {
+ inputStream.close();
+ }
+
+ String deviceName = null;
+ String projectName = null;
+ String flavorName = null;
+ NodeList propertiesList = document.getElementsByTagName("properties");
+ for (int i = 0; i < propertiesList.getLength(); i++) {
+ Element properties = (Element) propertiesList.item(i);
+ deviceName = properties.getAttribute("device");
+ projectName = properties.getAttribute("project");
+ flavorName = properties.getAttribute("flavor");
+ }
+
+ NodeList testCases = document.getElementsByTagName("testcase");
+ for (int i = 0; i < testCases.getLength(); i++) {
+ Element testCase = (Element) testCases.item(i);
+ String className = testCase.getAttribute("classname");
+ String testName = testCase.getAttribute("name");
+ LocaleSafeDecimalFormat format = new LocaleSafeDecimalFormat();
+ BigDecimal duration = format.parse(testCase.getAttribute("time"));
+ duration = duration.multiply(BigDecimal.valueOf(1000));
+ NodeList failures = testCase.getElementsByTagName("failure");
+ TestResult testResult = model.addTest(className, testName, duration.longValue(),
+ deviceName, projectName, flavorName);
+ for (int j = 0; j < failures.getLength(); j++) {
+ Element failure = (Element) failures.item(j);
+ testResult.addFailure(
+ failure.getAttribute("message"), failure.getTextContent(),
+ deviceName, projectName, flavorName);
+ }
+ }
+ NodeList ignoredTestCases = document.getElementsByTagName("ignored-testcase");
+ for (int i = 0; i < ignoredTestCases.getLength(); i++) {
+ Element testCase = (Element) ignoredTestCases.item(i);
+ String className = testCase.getAttribute("classname");
+ String testName = testCase.getAttribute("name");
+ model.addTest(className, testName, 0, deviceName, projectName, flavorName).ignored();
+ }
+ String suiteClassName = document.getDocumentElement().getAttribute("name");
+ ClassTestResults suiteResults = model.addTestClass(suiteClassName);
+ NodeList stdOutElements = document.getElementsByTagName("system-out");
+ for (int i = 0; i < stdOutElements.getLength(); i++) {
+ suiteResults.addStandardOutput(stdOutElements.item(i).getTextContent());
+ }
+ NodeList stdErrElements = document.getElementsByTagName("system-err");
+ for (int i = 0; i < stdErrElements.getLength(); i++) {
+ suiteResults.addStandardError(stdErrElements.item(i).getTextContent());
+ }
+ } catch (Exception e) {
+ throw new GradleException(String.format("Could not load test results from '%s'.", file), e);
+ }
+ }
+
+ private void generateFiles(AllTestResults model) {
+ try {
+ generatePage(model, new OverviewPageRenderer(reportType), new File(reportDir, "index.html"));
+ for (PackageTestResults packageResults : model.getPackages()) {
+ generatePage(packageResults, new PackagePageRenderer(reportType),
+ new File(reportDir, packageResults.getFilename(reportType) + ".html"));
+ for (ClassTestResults classResults : packageResults.getClasses()) {
+ generatePage(classResults, new ClassPageRenderer(reportType),
+ new File(reportDir, classResults.getFilename(reportType) + ".html"));
+ }
+ }
+ } catch (Exception e) {
+ throw new GradleException(
+ String.format("Could not generate test report to '%s'.", reportDir), e);
+ }
+ }
+
+ private <T extends CompositeTestResults> void generatePage(T model, PageRenderer<T> renderer,
+ File outputFile) throws Exception {
+ htmlRenderer.renderer(renderer).writeTo(model, outputFile);
+ }}
diff --git a/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TestResult.java b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TestResult.java
new file mode 100644
index 0000000..90ecc8a
--- /dev/null
+++ b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/TestResult.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.test.report;
+
+import org.gradle.api.internal.tasks.testing.junit.result.TestFailure;
+import org.gradle.api.internal.tasks.testing.junit.report.TestResultModel;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.gradle.api.tasks.testing.TestResult.ResultType;
+
+/**
+ * Custom test result based on Gradle's TestResult
+ */
+class TestResult extends TestResultModel implements Comparable<TestResult> {
+
+ private final long duration;
+ private final String device;
+ private final String project;
+ private final String flavor;
+ final ClassTestResults classResults;
+ final List<TestFailure> failures = new ArrayList<TestFailure>();
+ final String name;
+ private boolean ignored;
+
+ public TestResult(String name, long duration, String device, String project, String flavor,
+ ClassTestResults classResults) {
+ this.name = name;
+ this.duration = duration;
+ this.device = device;
+ this.project = project;
+ this.flavor = flavor;
+ this.classResults = classResults;
+ }
+
+ public Object getId() {
+ return name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getDevice() {
+ return device;
+ }
+
+ public String getProject() {
+ return project;
+ }
+
+ public String getFlavor() {
+ return flavor;
+ }
+
+ @Override
+ public String getTitle() {
+ return String.format("Test %s", name);
+ }
+
+ @Override
+ public ResultType getResultType() {
+ if (ignored) {
+ return ResultType.SKIPPED;
+ }
+ return failures.isEmpty() ? ResultType.SUCCESS : ResultType.FAILURE;
+ }
+
+ @Override
+ public long getDuration() {
+ return duration;
+ }
+
+ @Override
+ public String getFormattedDuration() {
+ return ignored ? "-" : super.getFormattedDuration();
+ }
+
+ public ClassTestResults getClassResults() {
+ return classResults;
+ }
+
+ public List<TestFailure> getFailures() {
+ return failures;
+ }
+
+ public void addFailure(String message, String stackTrace,
+ String deviceName, String projectName, String flavorName) {
+ classResults.failed(this, deviceName, projectName, flavorName);
+ failures.add(new TestFailure(message, stackTrace, null));
+ }
+
+ public void ignored() {
+ ignored = true;
+ }
+
+ @Override
+ public int compareTo(TestResult testResult) {
+ int diff = classResults.getName().compareTo(testResult.classResults.getName());
+ if (diff != 0) {
+ return diff;
+ }
+
+ diff = name.compareTo(testResult.name);
+ if (diff != 0) {
+ return diff;
+ }
+
+ diff = device.compareTo(testResult.device);
+ if (diff != 0) {
+ return diff;
+ }
+
+ diff = flavor.compareTo(testResult.flavor);
+ if (diff != 0) {
+ return diff;
+ }
+
+ Integer thisIdentity = System.identityHashCode(this);
+ int otherIdentity = System.identityHashCode(testResult);
+ return thisIdentity.compareTo(otherIdentity);
+ }
+}
diff --git a/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/VariantTestResults.java b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/VariantTestResults.java
new file mode 100644
index 0000000..eb1e0b1
--- /dev/null
+++ b/build-system/gradle/src/fromGradle/groovy/com/android/build/gradle/internal/test/report/VariantTestResults.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.test.report;
+
+import com.android.annotations.NonNull;
+
+/**
+ * VariantTestResults to accumulate results per variant
+ */
+class VariantTestResults extends CompositeTestResults {
+
+ private final String name;
+
+ public VariantTestResults(@NonNull String name, CompositeTestResults parent) {
+ super(parent);
+ this.name = name;
+ }
+
+ @Override
+ public String getTitle() {
+ return name;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+}
diff --git a/build-system/gradle/src/fromGradle/resources/com/android/build/gradle/internal/test/report/base-style.css b/build-system/gradle/src/fromGradle/resources/com/android/build/gradle/internal/test/report/base-style.css
new file mode 100644
index 0000000..e09a387
--- /dev/null
+++ b/build-system/gradle/src/fromGradle/resources/com/android/build/gradle/internal/test/report/base-style.css
@@ -0,0 +1,162 @@
+
+body {
+ margin: 0;
+ padding: 0;
+ font-family: sans-serif;
+ font-size: 12pt;
+}
+
+body, a, a:visited {
+ color: #303030;
+}
+
+#content {
+ padding-left: 50px;
+ padding-right: 50px;
+ padding-top: 30px;
+ padding-bottom: 30px;
+}
+
+#content h1 {
+ font-size: 160%;
+ margin-bottom: 10px;
+}
+
+#footer {
+ margin-top: 100px;
+ font-size: 80%;
+ white-space: nowrap;
+}
+
+#footer, #footer a {
+ color: #a0a0a0;
+}
+
+ul {
+ margin-left: 0;
+}
+
+h1, h2, h3 {
+ white-space: nowrap;
+}
+
+h2 {
+ font-size: 120%;
+}
+
+ul.tabLinks {
+ padding-left: 0;
+ padding-top: 10px;
+ padding-bottom: 10px;
+ overflow: auto;
+ min-width: 800px;
+ width: auto !important;
+ width: 800px;
+}
+
+ul.tabLinks li {
+ float: left;
+ height: 100%;
+ list-style: none;
+ padding-left: 10px;
+ padding-right: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ margin-bottom: 0;
+ -moz-border-radius: 7px;
+ border-radius: 7px;
+ margin-right: 25px;
+ border: solid 1px #d4d4d4;
+ background-color: #f0f0f0;
+ behavior: url(css3-pie-1.0beta3.htc);
+}
+
+ul.tabLinks li:hover {
+ background-color: #fafafa;
+}
+
+ul.tabLinks li.selected {
+ background-color: #c5f0f5;
+ border-color: #c5f0f5;
+}
+
+ul.tabLinks a {
+ font-size: 120%;
+ display: block;
+ outline: none;
+ text-decoration: none;
+ margin: 0;
+ padding: 0;
+}
+
+ul.tabLinks li h2 {
+ margin: 0;
+ padding: 0;
+}
+
+div.tab {
+}
+
+div.selected {
+ display: block;
+}
+
+div.deselected {
+ display: none;
+}
+
+div.tab table {
+ min-width: 350px;
+ width: auto !important;
+ width: 350px;
+ border-collapse: collapse;
+}
+
+div.tab th, div.tab table {
+ border-bottom: solid #d0d0d0 1px;
+}
+
+div.tab th {
+ text-align: left;
+ white-space: nowrap;
+ padding-left: 6em;
+}
+
+div.tab th:first-child {
+ padding-left: 0;
+}
+
+div.tab td {
+ white-space: nowrap;
+ padding-left: 6em;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+
+div.tab td:first-child {
+ padding-left: 0;
+}
+
+div.tab td.numeric, div.tab th.numeric {
+ text-align: right;
+}
+
+span.code {
+ display: inline-block;
+ margin-top: 0em;
+ margin-bottom: 1em;
+}
+
+span.code pre {
+ font-size: 11pt;
+ padding-top: 10px;
+ padding-bottom: 10px;
+ padding-left: 10px;
+ padding-right: 10px;
+ margin: 0;
+ background-color: #f7f7f7;
+ border: solid 1px #d0d0d0;
+ min-width: 700px;
+ width: auto !important;
+ width: 700px;
+}
diff --git a/build-system/gradle/src/fromGradle/resources/com/android/build/gradle/internal/test/report/report.js b/build-system/gradle/src/fromGradle/resources/com/android/build/gradle/internal/test/report/report.js
new file mode 100644
index 0000000..a4455e4
--- /dev/null
+++ b/build-system/gradle/src/fromGradle/resources/com/android/build/gradle/internal/test/report/report.js
@@ -0,0 +1,101 @@
+var tabs = new Object();
+
+function initTabs() {
+ var container = document.getElementById('tabs');
+ tabs.tabs = findTabs(container);
+ tabs.titles = findTitles(tabs.tabs);
+ tabs.headers = findHeaders(container);
+ tabs.select = select;
+ tabs.deselectAll = deselectAll;
+ tabs.select(0);
+ return true;
+}
+
+window.onload = initTabs;
+
+function switchTab() {
+ var id = this.id.substr(1);
+ for (var i = 0; i < tabs.tabs.length; i++) {
+ if (tabs.tabs[i].id == id) {
+ tabs.select(i);
+ break;
+ }
+ }
+ return false;
+}
+
+function select(i) {
+ this.deselectAll();
+ changeElementClass(this.tabs[i], 'tab selected');
+ changeElementClass(this.headers[i], 'selected');
+ while (this.headers[i].firstChild) {
+ this.headers[i].removeChild(this.headers[i].firstChild);
+ }
+ var h2 = document.createElement('H2');
+ h2.appendChild(document.createTextNode(this.titles[i]));
+ this.headers[i].appendChild(h2);
+}
+
+function deselectAll() {
+ for (var i = 0; i < this.tabs.length; i++) {
+ changeElementClass(this.tabs[i], 'tab deselected');
+ changeElementClass(this.headers[i], 'deselected');
+ while (this.headers[i].firstChild) {
+ this.headers[i].removeChild(this.headers[i].firstChild);
+ }
+ var a = document.createElement('A');
+ a.setAttribute('id', 'ltab' + i);
+ a.setAttribute('href', '#tab' + i);
+ a.onclick = switchTab;
+ a.appendChild(document.createTextNode(this.titles[i]));
+ this.headers[i].appendChild(a);
+ }
+}
+
+function changeElementClass(element, classValue) {
+ if (element.getAttribute('className')) {
+ /* IE */
+ element.setAttribute('className', classValue)
+ } else {
+ element.setAttribute('class', classValue)
+ }
+}
+
+function findTabs(container) {
+ return findChildElements(container, 'DIV', 'tab');
+}
+
+function findHeaders(container) {
+ var owner = findChildElements(container, 'UL', 'tabLinks');
+ return findChildElements(owner[0], 'LI', null);
+}
+
+function findTitles(tabs) {
+ var titles = new Array();
+ for (var i = 0; i < tabs.length; i++) {
+ var tab = tabs[i];
+ var header = findChildElements(tab, 'H2', null)[0];
+ header.parentNode.removeChild(header);
+ if (header.innerText) {
+ titles.push(header.innerText)
+ } else {
+ titles.push(header.textContent)
+ }
+ }
+ return titles;
+}
+
+function findChildElements(container, name, targetClass) {
+ var elements = new Array();
+ var children = container.childNodes;
+ for (var i = 0; i < children.length; i++) {
+ var child = children.item(i);
+ if (child.nodeType == 1 && child.nodeName == name) {
+ if (targetClass && child.className.indexOf(targetClass) < 0) {
+ continue;
+ }
+ elements.push(child);
+ }
+ }
+ return elements;
+}
diff --git a/build-system/gradle/src/fromGradle/resources/com/android/build/gradle/internal/test/report/style.css b/build-system/gradle/src/fromGradle/resources/com/android/build/gradle/internal/test/report/style.css
new file mode 100644
index 0000000..2440a1f
--- /dev/null
+++ b/build-system/gradle/src/fromGradle/resources/com/android/build/gradle/internal/test/report/style.css
@@ -0,0 +1,81 @@
+
+#summary {
+ margin-top: 30px;
+ margin-bottom: 40px;
+}
+
+#summary table {
+ border-collapse: collapse;
+}
+
+#summary td {
+ vertical-align: top;
+}
+
+.breadcrumbs, .breadcrumbs a {
+ color: #606060;
+}
+
+.infoBox {
+ width: 110px;
+ padding-top: 15px;
+ padding-bottom: 15px;
+ text-align: center;
+}
+
+.infoBox p {
+ margin: 0;
+}
+
+.counter, .percent {
+ font-size: 120%;
+ font-weight: bold;
+ margin-bottom: 8px;
+}
+
+#duration {
+ width: 125px;
+}
+
+#successRate, .summaryGroup {
+ border: solid 2px #d0d0d0;
+ -moz-border-radius: 10px;
+ border-radius: 10px;
+ behavior: url(css3-pie-1.0beta3.htc);
+}
+
+#successRate {
+ width: 140px;
+ margin-left: 35px;
+}
+
+#successRate .percent {
+ font-size: 180%;
+}
+
+.success, .success a {
+ color: #008000;
+}
+
+div.success, #successRate.success {
+ background-color: #bbd9bb;
+ border-color: #008000;
+}
+
+.failures, .failures a {
+ color: #b60808;
+}
+
+div.failures, #successRate.failures {
+ background-color: #ecdada;
+ border-color: #b60808;
+}
+
+ul.linkList {
+ padding-left: 0;
+}
+
+ul.linkList li {
+ list-style: none;
+ margin-bottom: 5px;
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/AppExtension.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/AppExtension.groovy
new file mode 100644
index 0000000..521616b
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/AppExtension.groovy
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle
+
+import com.android.build.gradle.api.ApplicationVariant
+import com.android.builder.DefaultBuildType
+import com.android.builder.DefaultProductFlavor
+import com.android.builder.model.SigningConfig
+import org.gradle.api.Action
+import org.gradle.api.NamedDomainObjectContainer
+import org.gradle.api.internal.DefaultDomainObjectSet
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.internal.reflect.Instantiator
+
+/**
+ * Extension for 'application' project.
+ */
+public class AppExtension extends BaseExtension {
+
+ final NamedDomainObjectContainer<DefaultProductFlavor> productFlavors
+ final NamedDomainObjectContainer<DefaultBuildType> buildTypes
+ final NamedDomainObjectContainer<SigningConfig> signingConfigs
+
+ private final DefaultDomainObjectSet<ApplicationVariant> applicationVariantList =
+ new DefaultDomainObjectSet<ApplicationVariant>(ApplicationVariant.class)
+
+ List<String> flavorGroupList
+ String testBuildType = "debug"
+
+ AppExtension(AppPlugin plugin, ProjectInternal project, Instantiator instantiator,
+ NamedDomainObjectContainer<DefaultBuildType> buildTypes,
+ NamedDomainObjectContainer<DefaultProductFlavor> productFlavors,
+ NamedDomainObjectContainer<SigningConfig> signingConfigs) {
+ super(plugin, project, instantiator)
+ this.buildTypes = buildTypes
+ this.productFlavors = productFlavors
+ this.signingConfigs = signingConfigs
+ }
+
+ void buildTypes(Action<? super NamedDomainObjectContainer<DefaultBuildType>> action) {
+ plugin.checkTasksAlreadyCreated();
+ action.execute(buildTypes)
+ }
+
+ void productFlavors(Action<? super NamedDomainObjectContainer<DefaultProductFlavor>> action) {
+ plugin.checkTasksAlreadyCreated();
+ action.execute(productFlavors)
+ }
+
+ void signingConfigs(Action<? super NamedDomainObjectContainer<SigningConfig>> action) {
+ plugin.checkTasksAlreadyCreated();
+ action.execute(signingConfigs)
+ }
+
+ public void flavorGroups(String... groups) {
+ plugin.checkTasksAlreadyCreated();
+ flavorGroupList = Arrays.asList(groups)
+ }
+
+ public DefaultDomainObjectSet<ApplicationVariant> getApplicationVariants() {
+ return applicationVariantList
+ }
+
+ void addApplicationVariant(ApplicationVariant applicationVariant) {
+ applicationVariantList.add(applicationVariant)
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/AppPlugin.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/AppPlugin.groovy
new file mode 100644
index 0000000..c65544b74
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/AppPlugin.groovy
@@ -0,0 +1,609 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle
+import com.android.annotations.NonNull
+import com.android.annotations.Nullable
+import com.android.build.gradle.api.BaseVariant
+import com.android.build.gradle.internal.BuildTypeData
+import com.android.build.gradle.internal.ConfigurationProvider
+import com.android.build.gradle.internal.ProductFlavorData
+import com.android.build.gradle.internal.api.ApplicationVariantImpl
+import com.android.build.gradle.internal.api.DefaultAndroidSourceSet
+import com.android.build.gradle.internal.api.TestVariantImpl
+import com.android.build.gradle.internal.dependency.VariantDependencies
+import com.android.build.gradle.internal.dsl.BuildTypeDsl
+import com.android.build.gradle.internal.dsl.BuildTypeFactory
+import com.android.build.gradle.internal.dsl.GroupableProductFlavorDsl
+import com.android.build.gradle.internal.dsl.GroupableProductFlavorFactory
+import com.android.build.gradle.internal.dsl.SigningConfigDsl
+import com.android.build.gradle.internal.dsl.SigningConfigFactory
+import com.android.build.gradle.internal.test.PluginHolder
+import com.android.build.gradle.internal.variant.ApplicationVariantData
+import com.android.build.gradle.internal.variant.BaseVariantData
+import com.android.build.gradle.internal.variant.TestVariantData
+import com.android.builder.DefaultBuildType
+import com.android.builder.VariantConfiguration
+import com.android.builder.model.SigningConfig
+import com.google.common.collect.ArrayListMultimap
+import com.google.common.collect.ListMultimap
+import com.google.common.collect.Maps
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.plugins.BasePlugin
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
+
+import javax.inject.Inject
+
+import static com.android.builder.BuilderConstants.DEBUG
+import static com.android.builder.BuilderConstants.INSTRUMENT_TEST
+import static com.android.builder.BuilderConstants.LINT
+import static com.android.builder.BuilderConstants.RELEASE
+import static com.android.builder.BuilderConstants.UI_TEST
+/**
+ * Gradle plugin class for 'application' projects.
+ */
+class AppPlugin extends com.android.build.gradle.BasePlugin implements Plugin<Project> {
+ static PluginHolder pluginHolder;
+
+ final Map<String, BuildTypeData> buildTypes = [:]
+ final Map<String, ProductFlavorData<GroupableProductFlavorDsl>> productFlavors = [:]
+ final Map<String, SigningConfig> signingConfigs = [:]
+
+ AppExtension extension
+
+ @Inject
+ public AppPlugin(Instantiator instantiator, ToolingModelBuilderRegistry registry) {
+ super(instantiator, registry)
+ }
+
+ @Override
+ public AppExtension getExtension() {
+ return extension
+ }
+
+ @Override
+ void apply(Project project) {
+ super.apply(project)
+
+ // This is for testing.
+ if (pluginHolder != null) {
+ pluginHolder.plugin = this;
+ }
+
+ def buildTypeContainer = project.container(DefaultBuildType,
+ new BuildTypeFactory(instantiator, project.fileResolver))
+ def productFlavorContainer = project.container(GroupableProductFlavorDsl,
+ new GroupableProductFlavorFactory(instantiator, project.fileResolver))
+ def signingConfigContainer = project.container(SigningConfig,
+ new SigningConfigFactory(instantiator))
+
+ extension = project.extensions.create('android', AppExtension,
+ this, (ProjectInternal) project, instantiator,
+ buildTypeContainer, productFlavorContainer, signingConfigContainer)
+ setBaseExtension(extension)
+
+ // map the whenObjectAdded callbacks on the containers.
+ signingConfigContainer.whenObjectAdded { SigningConfig signingConfig ->
+ SigningConfigDsl signingConfigDsl = (SigningConfigDsl) signingConfig
+ signingConfigs[signingConfigDsl.name] = signingConfig
+ }
+
+ buildTypeContainer.whenObjectAdded { DefaultBuildType buildType ->
+ ((BuildTypeDsl)buildType).init(signingConfigContainer.getByName(DEBUG))
+ addBuildType(buildType)
+ }
+
+ productFlavorContainer.whenObjectAdded { GroupableProductFlavorDsl productFlavor ->
+ addProductFlavor(productFlavor)
+ }
+
+ // create default Objects, signingConfig first as its used by the BuildTypes.
+ signingConfigContainer.create(DEBUG)
+ buildTypeContainer.create(DEBUG)
+ buildTypeContainer.create(RELEASE)
+
+ // map whenObjectRemoved on the containers to throw an exception.
+ signingConfigContainer.whenObjectRemoved {
+ throw new UnsupportedOperationException("Removing signingConfigs is not supported.")
+ }
+ buildTypeContainer.whenObjectRemoved {
+ throw new UnsupportedOperationException("Removing build types is not supported.")
+ }
+ productFlavorContainer.whenObjectRemoved {
+ throw new UnsupportedOperationException("Removing product flavors is not supported.")
+ }
+ }
+
+ /**
+ * Adds new BuildType, creating a BuildTypeData, and the associated source set,
+ * and adding it to the map.
+ * @param buildType the build type.
+ */
+ private void addBuildType(DefaultBuildType buildType) {
+ String name = buildType.name
+ checkName(name, "BuildType")
+
+ if (productFlavors.containsKey(name)) {
+ throw new RuntimeException("BuildType names cannot collide with ProductFlavor names")
+ }
+
+ def sourceSet = extension.sourceSetsContainer.maybeCreate(name)
+
+ BuildTypeData buildTypeData = new BuildTypeData(buildType, sourceSet, project)
+ project.tasks.assemble.dependsOn buildTypeData.assembleTask
+
+ buildTypes[name] = buildTypeData
+ }
+
+ /**
+ * Adds a new ProductFlavor, creating a ProductFlavorData and associated source sets,
+ * and adding it to the map.
+ *
+ * @param productFlavor the product flavor
+ */
+ private void addProductFlavor(GroupableProductFlavorDsl productFlavor) {
+ String name = productFlavor.name
+ checkName(name, "ProductFlavor")
+
+ if (buildTypes.containsKey(name)) {
+ throw new RuntimeException("ProductFlavor names cannot collide with BuildType names")
+ }
+
+ def mainSourceSet = (DefaultAndroidSourceSet) extension.sourceSetsContainer.maybeCreate(productFlavor.name)
+ String testName = "${INSTRUMENT_TEST}${productFlavor.name.capitalize()}"
+ def testSourceSet = (DefaultAndroidSourceSet) extension.sourceSetsContainer.maybeCreate(testName)
+
+ ProductFlavorData<GroupableProductFlavorDsl> productFlavorData =
+ new ProductFlavorData<GroupableProductFlavorDsl>(
+ productFlavor, mainSourceSet, testSourceSet, project)
+
+ productFlavors[productFlavor.name] = productFlavorData
+ }
+
+ private static void checkName(String name, String displayName) {
+ if (name.startsWith(INSTRUMENT_TEST)) {
+ throw new RuntimeException(
+ "${displayName} names cannot start with '${INSTRUMENT_TEST}'")
+ }
+
+ if (name.startsWith(UI_TEST)) {
+ throw new RuntimeException(
+ "${displayName} names cannot start with '${UI_TEST}'")
+ }
+
+ if (LINT.equals(name)) {
+ throw new RuntimeException("${displayName} names cannot be ${LINT}")
+ }
+ }
+
+ /**
+ * Task creation entry point.
+ */
+ @Override
+ protected void doCreateAndroidTasks() {
+ if (productFlavors.isEmpty()) {
+ createTasksForDefaultBuild()
+ } else {
+ // there'll be more than one test app, so we need a top level assembleTest
+ assembleTest = project.tasks.create("assembleTest")
+ assembleTest.group = BasePlugin.BUILD_GROUP
+ assembleTest.description = "Assembles all the Test applications"
+
+ // check whether we have multi flavor builds
+ if (extension.flavorGroupList == null || extension.flavorGroupList.size() < 2) {
+ productFlavors.values().each { ProductFlavorData productFlavorData ->
+ createTasksForFlavoredBuild(productFlavorData)
+ }
+ } else {
+ // need to group the flavor per group.
+ // First a map of group -> list(ProductFlavor)
+ ArrayListMultimap<String, ProductFlavorData<GroupableProductFlavorDsl>> map = ArrayListMultimap.create();
+ productFlavors.values().each { ProductFlavorData<GroupableProductFlavorDsl> productFlavorData ->
+ def flavor = productFlavorData.productFlavor
+ if (flavor.flavorGroup == null) {
+ throw new RuntimeException(
+ "Flavor ${flavor.name} has no flavor group.")
+ }
+ if (!extension.flavorGroupList.contains(flavor.flavorGroup)) {
+ throw new RuntimeException(
+ "Flavor ${flavor.name} has unknown group ${flavor.flavorGroup}.")
+ }
+
+ map.put(flavor.flavorGroup, productFlavorData)
+ }
+
+ // now we use the flavor groups to generate an ordered array of flavor to use
+ ProductFlavorData[] array = new ProductFlavorData[extension.flavorGroupList.size()]
+ createTasksForMultiFlavoredBuilds(array, 0, map)
+ }
+ }
+
+ // Add a compile lint task
+ createLintCompileTask()
+
+ // create the lint tasks.
+ createLintTasks()
+
+ // create the test tasks.
+ createCheckTasks(!productFlavors.isEmpty(), false /*isLibrary*/)
+
+ // Create the variant API objects after the tasks have been created!
+ createApiObjects()
+ }
+
+ /**
+ * Creates the tasks for multi-flavor builds.
+ *
+ * This recursively fills the array of ProductFlavorData (in the order defined
+ * in extension.flavorGroupList), creating all possible combination.
+ *
+ * @param datas the arrays to fill
+ * @param i the current index to fill
+ * @param map the map of group -> list(ProductFlavor)
+ * @return
+ */
+ private createTasksForMultiFlavoredBuilds(ProductFlavorData[] datas, int i,
+ ListMultimap<String, ? extends ProductFlavorData> map) {
+ if (i == datas.length) {
+ createTasksForFlavoredBuild(datas)
+ return
+ }
+
+ // fill the array at the current index.
+ // get the group name that matches the index we are filling.
+ def group = extension.flavorGroupList.get(i)
+
+ // from our map, get all the possible flavors in that group.
+ def flavorList = map.get(group)
+
+ // loop on all the flavors to add them to the current index and recursively fill the next
+ // indices.
+ for (ProductFlavorData flavor : flavorList) {
+ datas[i] = flavor
+ createTasksForMultiFlavoredBuilds(datas, (int) i + 1, map)
+ }
+ }
+
+ /**
+ * Creates Tasks for non-flavored build. This means assembleDebug, assembleRelease, and other
+ * assemble<Type> are directly building the <type> build instead of all build of the given
+ * <type>.
+ */
+ private createTasksForDefaultBuild() {
+ BuildTypeData testData = buildTypes[extension.testBuildType]
+ if (testData == null) {
+ throw new RuntimeException("Test Build Type '$extension.testBuildType' does not exist.")
+ }
+
+ ApplicationVariantData testedVariantData = null
+
+ ProductFlavorData defaultConfigData = getDefaultConfigData();
+
+ for (BuildTypeData buildTypeData : buildTypes.values()) {
+ def variantConfig = new VariantConfiguration(
+ defaultConfigData.productFlavor,
+ defaultConfigData.sourceSet,
+ buildTypeData.buildType,
+ buildTypeData.sourceSet)
+
+ // create the variant and get its internal storage object.
+ ApplicationVariantData appVariantData = new ApplicationVariantData(variantConfig)
+ VariantDependencies variantDep = VariantDependencies.compute(
+ project, appVariantData.variantConfiguration.fullName,
+ buildTypeData, defaultConfigData.mainProvider)
+ appVariantData.setVariantDependency(variantDep)
+
+ variantDataList.add(appVariantData)
+
+ if (buildTypeData == testData) {
+ testedVariantData = appVariantData
+ }
+ }
+
+ assert testedVariantData != null
+
+ // handle the test variant
+ def testVariantConfig = new VariantConfiguration(
+ defaultConfigData.productFlavor,
+ defaultConfigData.testSourceSet,
+ testData.buildType,
+ null,
+ VariantConfiguration.Type.TEST, testedVariantData.variantConfiguration)
+
+ // create the internal storage for this variant.
+ def testVariantData = new TestVariantData(testVariantConfig, testedVariantData)
+ variantDataList.add(testVariantData)
+ // link the testVariant to the tested variant in the other direction
+ testedVariantData.setTestVariantData(testVariantData);
+
+ // dependencies for the test variant
+ VariantDependencies variantDep = VariantDependencies.compute(
+ project, testVariantData.variantConfiguration.fullName,
+ defaultConfigData.testProvider)
+ testVariantData.setVariantDependency(variantDep)
+
+ // now loop on the VariantDependency and resolve them, and create the tasks
+ // for each variant
+ for (BaseVariantData variantData : variantDataList) {
+ resolveDependencies(variantData.variantDependency)
+ variantData.variantConfiguration.setDependencies(variantData.variantDependency)
+
+ if (variantData instanceof ApplicationVariantData) {
+ createApplicationVariant(
+ (ApplicationVariantData) variantData,
+ buildTypes[variantData.variantConfiguration.buildType.name].assembleTask)
+
+ } else if (variantData instanceof TestVariantData) {
+ testVariantData = (TestVariantData) variantData
+ createTestApkTasks(testVariantData,
+ (BaseVariantData) testVariantData.testedVariantData)
+ }
+ }
+ }
+
+ protected void createApiObjects() {
+ // we always want to have the test/tested objects created at the same time
+ // so that dynamic closure call on add can have referenced objects created.
+ // This means some objects are created before they are processed from the loop,
+ // so we store whether we have processed them or not.
+ Map<BaseVariantData, BaseVariant> map = Maps.newHashMap()
+ for (BaseVariantData variantData : variantDataList) {
+ if (map.get(variantData) != null) {
+ continue
+ }
+
+ if (variantData instanceof ApplicationVariantData) {
+ ApplicationVariantData appVariantData = (ApplicationVariantData) variantData
+ createVariantApiObjects(map, appVariantData, appVariantData.testVariantData)
+
+ } else if (variantData instanceof TestVariantData) {
+ TestVariantData testVariantData = (TestVariantData) variantData
+ createVariantApiObjects(map,
+ (ApplicationVariantData) testVariantData.testedVariantData,
+ testVariantData)
+ }
+ }
+ }
+
+ private void createVariantApiObjects(@NonNull Map<BaseVariantData, BaseVariant> map,
+ @NonNull ApplicationVariantData appVariantData,
+ @Nullable TestVariantData testVariantData) {
+ ApplicationVariantImpl appVariant = instantiator.newInstance(
+ ApplicationVariantImpl.class, appVariantData)
+
+ TestVariantImpl testVariant = null;
+ if (testVariantData != null) {
+ testVariant = instantiator.newInstance(TestVariantImpl.class, testVariantData)
+ }
+
+ if (appVariant != null && testVariant != null) {
+ appVariant.setTestVariant(testVariant)
+ testVariant.setTestedVariant(appVariant)
+ }
+
+ extension.addApplicationVariant(appVariant)
+ map.put(appVariantData, appVariant)
+
+ if (testVariant != null) {
+ extension.addTestVariant(testVariant)
+ map.put(testVariantData, testVariant)
+ }
+ }
+
+ /**
+ * Creates Task for a given flavor. This will create tasks for all build types for the given
+ * flavor.
+ * @param flavorDataList the flavor(s) to build.
+ */
+ private createTasksForFlavoredBuild(ProductFlavorData... flavorDataList) {
+
+ BuildTypeData testData = buildTypes[extension.testBuildType]
+ if (testData == null) {
+ throw new RuntimeException("Test Build Type '$extension.testBuildType' does not exist.")
+ }
+
+ // because this method is called multiple times, we need to keep track
+ // of the variantData only for this call.
+ final List<BaseVariantData> localVariantDataList = []
+
+ ApplicationVariantData testedVariantData = null
+
+ // assembleTask for this flavor(group)
+ def assembleTask = createAssembleTask(flavorDataList)
+ project.tasks.assemble.dependsOn assembleTask
+
+ for (BuildTypeData buildTypeData : buildTypes.values()) {
+ /// add the container of dependencies
+ // the order of the libraries is important. In descending order:
+ // build types, flavors, defaultConfig.
+ List<ConfigurationProvider> variantProviders = []
+ variantProviders.add(buildTypeData)
+
+ VariantConfiguration variantConfig = new VariantConfiguration(
+ extension.defaultConfig,
+ getDefaultConfigData().sourceSet,
+ buildTypeData.buildType,
+ buildTypeData.sourceSet)
+
+ for (ProductFlavorData data : flavorDataList) {
+ String dimensionName = "";
+ if (data.productFlavor instanceof GroupableProductFlavorDsl) {
+ dimensionName = ((GroupableProductFlavorDsl) data.productFlavor).flavorGroup
+ }
+ variantConfig.addProductFlavor(
+ data.productFlavor,
+ data.sourceSet,
+ dimensionName
+ )
+ variantProviders.add(data.mainProvider)
+ }
+
+ // now add the defaultConfig
+ variantProviders.add(defaultConfigData.mainProvider)
+
+ // create the variant and get its internal storage object.
+ ApplicationVariantData appVariantData = new ApplicationVariantData(variantConfig)
+
+ DefaultAndroidSourceSet variantSourceSet = (DefaultAndroidSourceSet) extension.sourceSetsContainer.maybeCreate(variantConfig.fullName)
+ variantConfig.setVariantSourceProvider(variantSourceSet)
+ // TODO: hmm this won't work
+ //variantProviders.add(new ConfigurationProviderImpl(project, variantSourceSet))
+
+ if (flavorDataList.size() > 1) {
+ DefaultAndroidSourceSet multiFlavorSourceSet = (DefaultAndroidSourceSet) extension.sourceSetsContainer.maybeCreate(variantConfig.flavorName)
+ variantConfig.setMultiFlavorSourceProvider(multiFlavorSourceSet)
+ // TODO: hmm this won't work
+ //variantProviders.add(new ConfigurationProviderImpl(project, multiFlavorSourceSet))
+ }
+
+ VariantDependencies variantDep = VariantDependencies.compute(
+ project, appVariantData.variantConfiguration.fullName,
+ variantProviders.toArray(new ConfigurationProvider[variantProviders.size()]))
+ appVariantData.setVariantDependency(variantDep)
+
+ localVariantDataList.add(appVariantData)
+
+ if (buildTypeData == testData) {
+ testedVariantData = appVariantData
+ }
+ }
+
+ assert testedVariantData != null
+
+ // handle test variant
+ VariantConfiguration testVariantConfig = new VariantConfiguration(
+ extension.defaultConfig,
+ getDefaultConfigData().testSourceSet,
+ testData.buildType,
+ null,
+ VariantConfiguration.Type.TEST,
+ testedVariantData.variantConfiguration)
+
+ /// add the container of dependencies
+ // the order of the libraries is important. In descending order:
+ // flavors, defaultConfig. No build type for tests
+ List<ConfigurationProvider> testVariantProviders = []
+
+ for (ProductFlavorData data : flavorDataList) {
+ String dimensionName = "";
+ if (data.productFlavor instanceof GroupableProductFlavorDsl) {
+ dimensionName = ((GroupableProductFlavorDsl) data.productFlavor).flavorGroup
+ }
+ testVariantConfig.addProductFlavor(
+ data.productFlavor,
+ data.testSourceSet,
+ dimensionName)
+ testVariantProviders.add(data.testProvider)
+ }
+
+ // now add the default config
+ testVariantProviders.add(defaultConfigData.testProvider)
+
+ // create the internal storage for this variant.
+ TestVariantData testVariantData = new TestVariantData(testVariantConfig, testedVariantData)
+ localVariantDataList.add(testVariantData)
+ // link the testVariant to the tested variant in the other direction
+ testedVariantData.setTestVariantData(testVariantData);
+
+ // dependencies for the test variant
+ VariantDependencies variantDep = VariantDependencies.compute(
+ project, testVariantData.variantConfiguration.fullName,
+ testVariantProviders.toArray(new ConfigurationProvider[testVariantProviders.size()]))
+ testVariantData.setVariantDependency(variantDep)
+
+ // now loop on the VariantDependency and resolve them, and create the tasks
+ // for each variant
+ for (BaseVariantData variantData : localVariantDataList) {
+ resolveDependencies(variantData.variantDependency)
+ variantData.variantConfiguration.setDependencies(variantData.variantDependency)
+
+ if (variantData instanceof ApplicationVariantData) {
+ BuildTypeData buildTypeData = buildTypes[variantData.variantConfiguration.buildType.name]
+ createApplicationVariant((ApplicationVariantData) variantData, null)
+
+ buildTypeData.assembleTask.dependsOn variantData.assembleTask
+ assembleTask.dependsOn variantData.assembleTask
+
+ } else if (variantData instanceof TestVariantData) {
+ testVariantData = (TestVariantData) variantData
+ createTestApkTasks(testVariantData,
+ (BaseVariantData) testVariantData.testedVariantData)
+ }
+
+ variantDataList.add(variantData)
+ }
+ }
+
+ private Task createAssembleTask(ProductFlavorData[] flavorDataList) {
+ String name = ProductFlavorData.getFlavoredName(flavorDataList, true)
+
+ def assembleTask = project.tasks.create("assemble${name}")
+ assembleTask.description = "Assembles all builds for flavor ${name}"
+ assembleTask.group = "Build"
+
+ return assembleTask
+ }
+
+ /**
+ * Creates an ApplicationVariantData and its tasks for a given variant configuration.
+ * @param variantConfig the non-null variant configuration.
+ * @param assembleTask an optional assembleTask to be used. If null, a new one is created.
+ * @param configDependencies a non null list of dependencies for this variant.
+ * @return
+ */
+ @NonNull
+ private void createApplicationVariant(
+ @NonNull ApplicationVariantData variant,
+ @Nullable Task assembleTask) {
+
+ createAnchorTasks(variant)
+
+ // Add a task to process the manifest(s)
+ createProcessManifestTask(variant, "manifests")
+
+ // Add a task to compile renderscript files.
+ createRenderscriptTask(variant)
+
+ // Add a task to merge the resource folders
+ createMergeResourcesTask(variant, true /*process9Patch*/)
+
+ // Add a task to merge the asset folders
+ createMergeAssetsTask(variant, null /*default location*/, true /*includeDependencies*/)
+
+ // Add a task to create the BuildConfig class
+ createBuildConfigTask(variant)
+
+ // Add a task to generate resource source files
+ createProcessResTask(variant)
+
+ // Add a task to process the java resources
+ createProcessJavaResTask(variant)
+
+ createAidlTask(variant)
+
+ // Add a compile task
+ createCompileTask(variant, null/*testedVariant*/)
+
+ // Add NDK tasks
+ createNdkTasks(variant)
+
+ addPackageTasks(variant, assembleTask)
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/BaseExtension.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/BaseExtension.groovy
new file mode 100644
index 0000000..ba72ffc
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/BaseExtension.groovy
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle
+
+import com.android.SdkConstants
+import com.android.annotations.NonNull
+import com.android.annotations.Nullable
+import com.android.build.gradle.api.AndroidSourceSet
+import com.android.build.gradle.api.BaseVariant
+import com.android.build.gradle.api.TestVariant
+import com.android.build.gradle.internal.CompileOptions
+import com.android.build.gradle.internal.SourceSetSourceProviderWrapper
+import com.android.build.gradle.internal.dsl.AaptOptionsImpl
+import com.android.build.gradle.internal.dsl.AndroidSourceSetFactory
+import com.android.build.gradle.internal.dsl.DexOptionsImpl
+import com.android.build.gradle.internal.dsl.LintOptionsImpl
+import com.android.build.gradle.internal.dsl.ProductFlavorDsl
+import com.android.build.gradle.internal.test.TestOptions
+import com.android.builder.BuilderConstants
+import com.android.builder.DefaultProductFlavor
+import com.android.builder.model.BuildType
+import com.android.builder.model.ProductFlavor
+import com.android.builder.model.SourceProvider
+import com.android.builder.testing.api.DeviceProvider
+import com.android.builder.testing.api.TestServer
+import com.android.sdklib.repository.FullRevision
+import com.android.utils.ILogger
+import com.google.common.collect.Lists
+import org.gradle.api.Action
+import org.gradle.api.NamedDomainObjectContainer
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.ConfigurationContainer
+import org.gradle.api.internal.DefaultDomainObjectSet
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.tasks.SourceSet
+import org.gradle.internal.reflect.Instantiator
+/**
+ * Base android extension for all android plugins.
+ */
+public abstract class BaseExtension {
+
+ private String target
+ private FullRevision buildToolsRevision
+
+ final DefaultProductFlavor defaultConfig
+ final AaptOptionsImpl aaptOptions
+ final LintOptionsImpl lintOptions
+ final DexOptionsImpl dexOptions
+ final TestOptions testOptions
+ final CompileOptions compileOptions
+
+ private final DefaultDomainObjectSet<TestVariant> testVariantList =
+ new DefaultDomainObjectSet<TestVariant>(TestVariant.class)
+
+ private final List<DeviceProvider> deviceProviderList = Lists.newArrayList();
+ private final List<TestServer> testServerList = Lists.newArrayList();
+
+ protected final BasePlugin plugin
+
+
+ /**
+ * The source sets container.
+ */
+ final NamedDomainObjectContainer<AndroidSourceSet> sourceSetsContainer
+
+ BaseExtension(BasePlugin plugin, ProjectInternal project, Instantiator instantiator) {
+ this.plugin = plugin
+
+ defaultConfig = instantiator.newInstance(ProductFlavorDsl.class, BuilderConstants.MAIN,
+ project.fileResolver, instantiator)
+
+ aaptOptions = instantiator.newInstance(AaptOptionsImpl.class)
+ dexOptions = instantiator.newInstance(DexOptionsImpl.class)
+ lintOptions = instantiator.newInstance(LintOptionsImpl.class)
+ testOptions = instantiator.newInstance(TestOptions.class)
+ compileOptions = instantiator.newInstance(CompileOptions.class)
+
+ sourceSetsContainer = project.container(AndroidSourceSet,
+ new AndroidSourceSetFactory(instantiator, project.fileResolver))
+
+ sourceSetsContainer.whenObjectAdded { AndroidSourceSet sourceSet ->
+ ConfigurationContainer configurations = project.getConfigurations()
+
+ Configuration compileConfiguration = configurations.findByName(
+ sourceSet.getCompileConfigurationName())
+ if (compileConfiguration == null) {
+ compileConfiguration = configurations.create(sourceSet.getCompileConfigurationName())
+ }
+ compileConfiguration.setVisible(false);
+ compileConfiguration.setDescription(
+ String.format("Classpath for compiling the %s sources.", sourceSet.getName()))
+
+ Configuration packageConfiguration = configurations.findByName(
+ sourceSet.getPackageConfigurationName())
+ if (packageConfiguration == null) {
+ packageConfiguration = configurations.create(sourceSet.getPackageConfigurationName())
+ }
+ packageConfiguration.setVisible(false)
+ packageConfiguration.extendsFrom(compileConfiguration)
+ packageConfiguration.setDescription(
+ String.format("Classpath packaged with the compiled %s classes.",
+ sourceSet.getName()));
+
+ sourceSet.setRoot(String.format("src/%s", sourceSet.getName()))
+ }
+ }
+
+ void compileSdkVersion(int apiLevel) {
+ this.target = "android-" + apiLevel
+ }
+
+ void setCompileSdkVersion(int apiLevel) {
+ plugin.checkTasksAlreadyCreated();
+ compileSdkVersion(apiLevel)
+ }
+
+ void compileSdkVersion(String target) {
+ plugin.checkTasksAlreadyCreated();
+ this.target = target
+ }
+
+ void setCompileSdkVersion(String target) {
+ plugin.checkTasksAlreadyCreated();
+ compileSdkVersion(target)
+ }
+
+ void buildToolsVersion(String version) {
+ plugin.checkTasksAlreadyCreated();
+ buildToolsRevision = FullRevision.parseRevision(version)
+ }
+
+ void setBuildToolsVersion(String version) {
+ plugin.checkTasksAlreadyCreated();
+ buildToolsVersion(version)
+ }
+
+ void sourceSets(Action<NamedDomainObjectContainer<AndroidSourceSet>> action) {
+ plugin.checkTasksAlreadyCreated();
+ action.execute(sourceSetsContainer)
+ }
+
+ NamedDomainObjectContainer<AndroidSourceSet> getSourceSets() {
+ sourceSetsContainer
+ }
+
+ void defaultConfig(Action<DefaultProductFlavor> action) {
+ plugin.checkTasksAlreadyCreated();
+ action.execute(defaultConfig)
+ }
+
+ void aaptOptions(Action<AaptOptionsImpl> action) {
+ plugin.checkTasksAlreadyCreated();
+ action.execute(aaptOptions)
+ }
+
+ void dexOptions(Action<DexOptionsImpl> action) {
+ plugin.checkTasksAlreadyCreated();
+ action.execute(dexOptions)
+ }
+
+ void lintOptions(Action<LintOptionsImpl> action) {
+ plugin.checkTasksAlreadyCreated();
+ action.execute(lintOptions)
+ }
+
+ void testOptions(Action<TestOptions> action) {
+ plugin.checkTasksAlreadyCreated();
+ action.execute(testOptions)
+ }
+
+ void compileOptions(Action<CompileOptions> action) {
+ plugin.checkTasksAlreadyCreated();
+ action.execute(compileOptions)
+ }
+
+ void deviceProvider(DeviceProvider deviceProvider) {
+ plugin.checkTasksAlreadyCreated();
+ deviceProviderList.add(deviceProvider)
+ }
+
+ @NonNull
+ List<DeviceProvider> getDeviceProviders() {
+ return deviceProviderList
+ }
+
+ void testServer(TestServer testServer) {
+ plugin.checkTasksAlreadyCreated();
+ testServerList.add(testServer)
+ }
+
+ @NonNull
+ List<TestServer> getTestServers() {
+ return testServerList
+ }
+
+ @NonNull
+ public DefaultDomainObjectSet<TestVariant> getTestVariants() {
+ return testVariantList
+ }
+
+ void addTestVariant(TestVariant testVariant) {
+ testVariantList.add(testVariant)
+ }
+
+ public void registerArtifactType(@NonNull String name,
+ boolean isTest,
+ int artifactType) {
+ plugin.registerArtifactType(name, isTest, artifactType)
+ }
+
+ public void registerBuildTypeSourceProvider(
+ @NonNull String name,
+ @NonNull BuildType buildType,
+ @NonNull SourceProvider sourceProvider) {
+ plugin.registerBuildTypeSourceProvider(name, buildType, sourceProvider)
+ }
+
+ public void registerProductFlavorSourceProvider(
+ @NonNull String name,
+ @NonNull ProductFlavor productFlavor,
+ @NonNull SourceProvider sourceProvider) {
+ plugin.registerProductFlavorSourceProvider(name, productFlavor, sourceProvider)
+ }
+
+ public void registerJavaArtifact(
+ @NonNull String name,
+ @NonNull BaseVariant variant,
+ @NonNull String assembleTaskName,
+ @NonNull String javaCompileTaskName,
+ @NonNull File classesFolder,
+ @Nullable SourceProvider sourceProvider) {
+ plugin.registerJavaArtifact(name, variant, assembleTaskName, javaCompileTaskName,
+ classesFolder, sourceProvider)
+ }
+
+ public void registerMultiFlavorSourceProvider(
+ @NonNull String name,
+ @NonNull String flavorName,
+ @NonNull SourceProvider sourceProvider) {
+ plugin.registerMultiFlavorSourceProvider(name, flavorName, sourceProvider)
+ }
+
+ @NonNull
+ public SourceProvider wrapJavaSourceSet(@NonNull SourceSet sourceSet) {
+ return new SourceSetSourceProviderWrapper(sourceSet)
+ }
+
+ public String getCompileSdkVersion() {
+ return target
+ }
+
+ public FullRevision getBuildToolsRevision() {
+ return buildToolsRevision
+ }
+
+ public File getAdbExe() {
+ return plugin.sdkParser.adb
+ }
+
+ public ILogger getLogger() {
+ return plugin.logger
+ }
+
+ public File getDefaultProguardFile(String name) {
+ return new File(plugin.sdkDirectory,
+ SdkConstants.FD_TOOLS + File.separatorChar
+ + SdkConstants.FD_PROGUARD + File.separatorChar
+ + name);
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/BasePlugin.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/BasePlugin.groovy
new file mode 100644
index 0000000..b06ed55
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/BasePlugin.groovy
@@ -0,0 +1,1985 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle
+
+import com.android.annotations.NonNull
+import com.android.annotations.Nullable
+import com.android.build.gradle.api.AndroidSourceSet
+import com.android.build.gradle.api.BaseVariant
+import com.android.build.gradle.internal.BadPluginException
+import com.android.build.gradle.internal.LoggerWrapper
+import com.android.build.gradle.internal.ProductFlavorData
+import com.android.build.gradle.internal.Sdk
+import com.android.build.gradle.internal.api.DefaultAndroidSourceSet
+import com.android.build.gradle.internal.dependency.DependencyChecker
+import com.android.build.gradle.internal.dependency.LibraryDependencyImpl
+import com.android.build.gradle.internal.dependency.ManifestDependencyImpl
+import com.android.build.gradle.internal.dependency.SymbolFileProviderImpl
+import com.android.build.gradle.internal.dependency.VariantDependencies
+import com.android.build.gradle.internal.dsl.SigningConfigDsl
+import com.android.build.gradle.internal.model.ArtifactMetaDataImpl
+import com.android.build.gradle.internal.model.JavaArtifactImpl
+import com.android.build.gradle.internal.model.ModelBuilder
+import com.android.build.gradle.internal.tasks.AndroidReportTask
+import com.android.build.gradle.internal.tasks.DependencyReportTask
+import com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestLibraryTask
+import com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask
+import com.android.build.gradle.internal.tasks.InstallTask
+import com.android.build.gradle.internal.tasks.OutputFileTask
+import com.android.build.gradle.internal.tasks.PrepareDependenciesTask
+import com.android.build.gradle.internal.tasks.PrepareLibraryTask
+import com.android.build.gradle.internal.tasks.SigningReportTask
+import com.android.build.gradle.internal.tasks.TestServerTask
+import com.android.build.gradle.internal.tasks.UninstallTask
+import com.android.build.gradle.internal.tasks.ValidateSigningTask
+import com.android.build.gradle.internal.test.report.ReportType
+import com.android.build.gradle.internal.variant.ApkVariantData
+import com.android.build.gradle.internal.variant.ApplicationVariantData
+import com.android.build.gradle.internal.variant.BaseVariantData
+import com.android.build.gradle.internal.variant.DefaultSourceProviderContainer
+import com.android.build.gradle.internal.variant.LibraryVariantData
+import com.android.build.gradle.internal.variant.TestVariantData
+import com.android.build.gradle.internal.variant.TestedVariantData
+import com.android.build.gradle.tasks.AidlCompile
+import com.android.build.gradle.tasks.Dex
+import com.android.build.gradle.tasks.GenerateBuildConfig
+import com.android.build.gradle.tasks.Lint
+import com.android.build.gradle.tasks.MergeAssets
+import com.android.build.gradle.tasks.MergeResources
+import com.android.build.gradle.tasks.NdkCompile
+import com.android.build.gradle.tasks.PackageApplication
+import com.android.build.gradle.tasks.PreDex
+import com.android.build.gradle.tasks.ProcessAndroidResources
+import com.android.build.gradle.tasks.ProcessAppManifest
+import com.android.build.gradle.tasks.ProcessTestManifest
+import com.android.build.gradle.tasks.RenderscriptCompile
+import com.android.build.gradle.tasks.ZipAlign
+import com.android.builder.AndroidBuilder
+import com.android.builder.DefaultProductFlavor
+import com.android.builder.SdkParser
+import com.android.builder.VariantConfiguration
+import com.android.builder.dependency.JarDependency
+import com.android.builder.dependency.LibraryDependency
+import com.android.builder.model.AndroidArtifact
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.ArtifactMetaData
+import com.android.builder.model.BuildType
+import com.android.builder.model.JavaArtifact
+import com.android.builder.model.ProductFlavor
+import com.android.builder.model.SigningConfig
+import com.android.builder.model.SourceProvider
+import com.android.builder.model.SourceProviderContainer
+import com.android.builder.testing.ConnectedDeviceProvider
+import com.android.builder.testing.api.DeviceProvider
+import com.android.builder.testing.api.TestServer
+import com.android.utils.ILogger
+import com.google.common.collect.ArrayListMultimap
+import com.google.common.collect.ListMultimap
+import com.google.common.collect.Lists
+import com.google.common.collect.Maps
+import com.google.common.collect.Multimap
+import com.google.common.collect.Sets
+import org.gradle.api.DefaultTask
+import org.gradle.api.GradleException
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.ModuleVersionIdentifier
+import org.gradle.api.artifacts.ProjectDependency
+import org.gradle.api.artifacts.ResolvedArtifact
+import org.gradle.api.artifacts.SelfResolvingDependency
+import org.gradle.api.artifacts.result.DependencyResult
+import org.gradle.api.artifacts.result.ResolvedDependencyResult
+import org.gradle.api.artifacts.result.ResolvedModuleVersionResult
+import org.gradle.api.artifacts.result.UnresolvedDependencyResult
+import org.gradle.api.logging.LogLevel
+import org.gradle.api.plugins.JavaBasePlugin
+import org.gradle.api.plugins.JavaPlugin
+import org.gradle.api.specs.Specs
+import org.gradle.api.tasks.Copy
+import org.gradle.api.tasks.compile.JavaCompile
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.language.jvm.tasks.ProcessResources
+import org.gradle.tooling.BuildException
+import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
+import org.gradle.util.GUtil
+import proguard.gradle.ProGuardTask
+
+import java.util.jar.Attributes
+import java.util.jar.Manifest
+
+import static com.android.builder.BuilderConstants.CONNECTED
+import static com.android.builder.BuilderConstants.DEVICE
+import static com.android.builder.BuilderConstants.EXT_LIB_ARCHIVE
+import static com.android.builder.BuilderConstants.FD_FLAVORS
+import static com.android.builder.BuilderConstants.FD_FLAVORS_ALL
+import static com.android.builder.BuilderConstants.FD_INSTRUMENT_RESULTS
+import static com.android.builder.BuilderConstants.FD_INSTRUMENT_TESTS
+import static com.android.builder.BuilderConstants.FD_REPORTS
+import static com.android.builder.BuilderConstants.INSTRUMENT_TEST
+import static java.io.File.separator
+
+/**
+ * Base class for all Android plugins
+ */
+public abstract class BasePlugin {
+ protected final static String DIR_BUNDLES = "bundles";
+
+ public static final String GRADLE_MIN_VERSION = "1.9"
+ public static final String[] GRADLE_SUPPORTED_VERSIONS = [ GRADLE_MIN_VERSION ]
+
+ public static final String INSTALL_GROUP = "Install"
+
+ public static File TEST_SDK_DIR;
+
+ protected Instantiator instantiator
+ private ToolingModelBuilderRegistry registry
+
+ private final Map<Object, AndroidBuilder> builders = Maps.newIdentityHashMap()
+
+ final List<BaseVariantData> variantDataList = []
+ final Map<LibraryDependencyImpl, PrepareLibraryTask> prepareTaskMap = [:]
+ final Map<SigningConfig, ValidateSigningTask> validateSigningTaskMap = [:]
+
+ protected Project project
+ private LoggerWrapper loggerWrapper
+ private Sdk sdk
+ private String creator
+
+ private boolean hasCreatedTasks = false
+
+ private ProductFlavorData<DefaultProductFlavor> defaultConfigData
+ private final Collection<String> unresolvedDependencies = Sets.newHashSet();
+
+ protected DefaultAndroidSourceSet mainSourceSet
+ protected DefaultAndroidSourceSet testSourceSet
+
+ protected Task mainPreBuild
+ protected Task uninstallAll
+ protected Task assembleTest
+ protected Task deviceCheck
+ protected Task connectedCheck
+ protected Task lint
+ protected Task lintCompile
+
+ protected BasePlugin(Instantiator instantiator, ToolingModelBuilderRegistry registry) {
+ this.instantiator = instantiator
+ this.registry = registry
+ String pluginVersion = getLocalVersion()
+ if (pluginVersion != null) {
+ creator = "Android Gradle " + pluginVersion
+ } else {
+ creator = "Android Gradle"
+ }
+ }
+
+ public abstract BaseExtension getExtension()
+ protected abstract void doCreateAndroidTasks()
+
+ protected void apply(Project project) {
+ this.project = project
+
+ checkGradleVersion()
+ sdk = new Sdk(project, logger)
+
+ project.apply plugin: JavaBasePlugin
+
+ // Register a builder for the custom tooling model
+ registry.register(new ModelBuilder());
+
+ project.tasks.assemble.description =
+ "Assembles all variants of all applications and secondary packages."
+
+ uninstallAll = project.tasks.create("uninstallAll")
+ uninstallAll.description = "Uninstall all applications."
+ uninstallAll.group = INSTALL_GROUP
+
+ deviceCheck = project.tasks.create("deviceCheck")
+ deviceCheck.description = "Runs all device checks using Device Providers and Test Servers."
+ deviceCheck.group = JavaBasePlugin.VERIFICATION_GROUP
+
+ connectedCheck = project.tasks.create("connectedCheck")
+ connectedCheck.description = "Runs all device checks on currently connected devices."
+ connectedCheck.group = JavaBasePlugin.VERIFICATION_GROUP
+
+ mainPreBuild = project.tasks.create("preBuild")
+
+ lint = project.tasks.create("lint", Lint)
+ lint.description = "Runs lint on all variants."
+ lint.group = JavaBasePlugin.VERIFICATION_GROUP
+ lint.setPlugin(this)
+ int count = variantDataList.size()
+ for (int i = 0 ; i < count ; i++) {
+ final BaseVariantData baseVariantData = variantDataList.get(i)
+ if (isLintVariant(baseVariantData)) {
+ lint.dependsOn baseVariantData.javaCompileTask
+ }
+ }
+ project.tasks.check.dependsOn lint
+
+ project.afterEvaluate {
+ createAndroidTasks(false)
+ }
+ }
+
+ protected void setBaseExtension(@NonNull BaseExtension extension) {
+ sdk.setExtension(extension)
+ mainSourceSet = (DefaultAndroidSourceSet) extension.sourceSets.create(extension.defaultConfig.name)
+ testSourceSet = (DefaultAndroidSourceSet) extension.sourceSets.create(INSTRUMENT_TEST)
+
+ defaultConfigData = new ProductFlavorData<DefaultProductFlavor>(
+ extension.defaultConfig, mainSourceSet,
+ testSourceSet, project)
+ }
+
+ private void checkGradleVersion() {
+ boolean foundMatch = false
+ for (String version : GRADLE_SUPPORTED_VERSIONS) {
+ if (project.getGradle().gradleVersion.startsWith(version)) {
+ foundMatch = true
+ break
+ }
+ }
+
+ if (!foundMatch) {
+ File file = new File("gradle" + separator + "wrapper" + separator +
+ "gradle-wrapper.properties");
+ throw new BuildException(
+ String.format(
+ "Gradle version %s is required. Current version is %s. " +
+ "If using the gradle wrapper, try editing the distributionUrl in %s " +
+ "to gradle-%s-all.zip",
+ GRADLE_MIN_VERSION, project.getGradle().gradleVersion, file.getAbsolutePath(),
+ GRADLE_MIN_VERSION), null);
+
+ }
+ }
+
+ final void createAndroidTasks(boolean force) {
+ // get current plugins and look for the default Java plugin.
+ if (project.plugins.hasPlugin(JavaPlugin.class)) {
+ throw new BadPluginException(
+ "The 'java' plugin has been applied, but it is not compatible with the Android plugins.")
+ }
+
+ // don't do anything if the project was not initialized.
+ // Unless TEST_SDK_DIR is set in which case this is unit tests and we don't return.
+ // This is because project don't get evaluated in the unit test setup.
+ // See AppPluginDslTest
+ if (!force && (!project.state.executed || project.state.failure != null) && TEST_SDK_DIR == null) {
+ return
+ }
+
+ if (hasCreatedTasks) {
+ return
+ }
+ hasCreatedTasks = true
+
+ doCreateAndroidTasks()
+ createReportTasks()
+ }
+
+ void checkTasksAlreadyCreated() {
+ if (hasCreatedTasks) {
+ throw new GradleException(
+ "Android tasks have already been created.\n" +
+ "This happens when calling android.applicationVariants,\n" +
+ "android.libraryVariants or android.testVariants.\n" +
+ "Once these methods are called, it is not possible to\n" +
+ "continue configuring the model.")
+ }
+ }
+
+
+ ProductFlavorData getDefaultConfigData() {
+ return defaultConfigData
+ }
+
+ Collection<String> getUnresolvedDependencies() {
+ return unresolvedDependencies
+ }
+
+ SdkParser getSdkParser() {
+ return sdk.parser
+ }
+
+ SdkParser getLoadedSdkParser() {
+ return sdk.loadParser()
+ }
+
+ File getSdkDirectory() {
+ return sdk.sdkDirectory
+ }
+
+ File getNdkDirectory() {
+ return sdk.ndkDirectory
+ }
+
+ ILogger getLogger() {
+ if (loggerWrapper == null) {
+ loggerWrapper = new LoggerWrapper(project.logger)
+ }
+
+ return loggerWrapper
+ }
+
+ boolean isVerbose() {
+ return project.logger.isEnabled(LogLevel.DEBUG)
+ }
+
+ AndroidBuilder getAndroidBuilder(BaseVariantData variantData) {
+ AndroidBuilder androidBuilder = builders.get(variantData)
+
+ if (androidBuilder == null) {
+ SdkParser parser = getLoadedSdkParser()
+ androidBuilder = new AndroidBuilder(parser, creator, logger, verbose)
+ if (this instanceof LibraryPlugin) {
+ androidBuilder.setBuildingLibrary(true);
+ }
+
+ builders.put(variantData, androidBuilder)
+ }
+
+ return androidBuilder
+ }
+
+
+ protected String getRuntimeJars() {
+ return runtimeJarList.join(File.pathSeparator)
+ }
+
+ public List<String> getRuntimeJarList() {
+ SdkParser sdkParser = getLoadedSdkParser()
+ return AndroidBuilder.getBootClasspath(sdkParser);
+ }
+
+ protected void createProcessManifestTask(BaseVariantData variantData,
+ String manifestOurDir) {
+ def processManifestTask = project.tasks.create(
+ "process${variantData.variantConfiguration.fullName.capitalize()}Manifest",
+ ProcessAppManifest)
+ variantData.processManifestTask = processManifestTask
+ processManifestTask.dependsOn variantData.prepareDependenciesTask
+
+ processManifestTask.plugin = this
+ processManifestTask.variant = variantData
+
+ VariantConfiguration config = variantData.variantConfiguration
+ ProductFlavor mergedFlavor = config.mergedFlavor
+
+ processManifestTask.conventionMapping.mainManifest = {
+ config.mainManifest
+ }
+ processManifestTask.conventionMapping.manifestOverlays = {
+ config.manifestOverlays
+ }
+ processManifestTask.conventionMapping.packageNameOverride = {
+ config.packageOverride
+ }
+ processManifestTask.conventionMapping.versionName = {
+ config.versionName
+ }
+ processManifestTask.conventionMapping.libraries = {
+ getManifestDependencies(config.directLibraries)
+ }
+ processManifestTask.conventionMapping.versionCode = {
+ config.versionCode
+ }
+ processManifestTask.conventionMapping.minSdkVersion = {
+ mergedFlavor.minSdkVersion
+ }
+ processManifestTask.conventionMapping.targetSdkVersion = {
+ mergedFlavor.targetSdkVersion
+ }
+ processManifestTask.conventionMapping.manifestOutputFile = {
+ project.file(
+ "$project.buildDir/${manifestOurDir}/${variantData.variantConfiguration.dirName}/AndroidManifest.xml")
+ }
+ }
+
+ protected void createProcessTestManifestTask(BaseVariantData variantData,
+ String manifestOurDir) {
+ def processTestManifestTask = project.tasks.create(
+ "process${variantData.variantConfiguration.fullName.capitalize()}Manifest",
+ ProcessTestManifest)
+ variantData.processManifestTask = processTestManifestTask
+ processTestManifestTask.dependsOn variantData.prepareDependenciesTask
+
+ processTestManifestTask.plugin = this
+ processTestManifestTask.variant = variantData
+
+ VariantConfiguration config = variantData.variantConfiguration
+
+ processTestManifestTask.conventionMapping.testPackageName = {
+ config.packageName
+ }
+ processTestManifestTask.conventionMapping.minSdkVersion = {
+ config.minSdkVersion
+ }
+ processTestManifestTask.conventionMapping.targetSdkVersion = {
+ config.targetSdkVersion
+ }
+ processTestManifestTask.conventionMapping.testedPackageName = {
+ config.testedPackageName
+ }
+ processTestManifestTask.conventionMapping.instrumentationRunner = {
+ config.instrumentationRunner
+ }
+ processTestManifestTask.conventionMapping.handleProfiling = {
+ config.handleProfiling
+ }
+ processTestManifestTask.conventionMapping.functionalTest = {
+ config.functionalTest
+ }
+ processTestManifestTask.conventionMapping.libraries = {
+ getManifestDependencies(config.directLibraries)
+ }
+ processTestManifestTask.conventionMapping.manifestOutputFile = {
+ project.file(
+ "$project.buildDir/${manifestOurDir}/${variantData.variantConfiguration.dirName}/AndroidManifest.xml")
+ }
+ }
+
+ protected void createRenderscriptTask(BaseVariantData variantData) {
+ VariantConfiguration config = variantData.variantConfiguration
+
+ def renderscriptTask = project.tasks.create(
+ "compile${variantData.variantConfiguration.fullName.capitalize()}Renderscript",
+ RenderscriptCompile)
+ variantData.renderscriptCompileTask = renderscriptTask
+
+ ProductFlavor mergedFlavor = config.mergedFlavor
+ boolean ndkMode = mergedFlavor.renderscriptNdkMode
+
+ // only put this dependency if rs will generate Java code
+ if (!ndkMode) {
+ variantData.sourceGenTask.dependsOn renderscriptTask
+ }
+
+ renderscriptTask.dependsOn variantData.prepareDependenciesTask
+ renderscriptTask.plugin = this
+ renderscriptTask.variant = variantData
+
+ renderscriptTask.targetApi = mergedFlavor.renderscriptTargetApi
+ renderscriptTask.supportMode = mergedFlavor.renderscriptSupportMode
+ renderscriptTask.ndkMode = ndkMode
+ renderscriptTask.debugBuild = config.buildType.renderscriptDebugBuild
+ renderscriptTask.optimLevel = config.buildType.renderscriptOptimLevel
+
+ renderscriptTask.conventionMapping.sourceDirs = { config.renderscriptSourceList }
+ renderscriptTask.conventionMapping.importDirs = { config.renderscriptImports }
+
+ renderscriptTask.conventionMapping.sourceOutputDir = {
+ project.file("$project.buildDir/source/rs/${variantData.variantConfiguration.dirName}")
+ }
+ renderscriptTask.conventionMapping.resOutputDir = {
+ project.file("$project.buildDir/res/rs/${variantData.variantConfiguration.dirName}")
+ }
+ renderscriptTask.conventionMapping.objOutputDir = {
+ project.file("$project.buildDir/rs/${variantData.variantConfiguration.dirName}/obj")
+ }
+ renderscriptTask.conventionMapping.libOutputDir = {
+ project.file("$project.buildDir/rs/${variantData.variantConfiguration.dirName}/lib")
+ }
+ renderscriptTask.conventionMapping.ndkConfig = { config.ndkConfig }
+ }
+
+ protected void createMergeResourcesTask(@NonNull BaseVariantData variantData,
+ final boolean process9Patch) {
+ MergeResources mergeResourcesTask = basicCreateMergeResourcesTask(
+ variantData,
+ "merge",
+ "$project.buildDir/res/all/${variantData.variantConfiguration.dirName}",
+ true /*includeDependencies*/,
+ process9Patch)
+ variantData.mergeResourcesTask = mergeResourcesTask
+ }
+
+ protected MergeResources basicCreateMergeResourcesTask(
+ @NonNull BaseVariantData variantData,
+ @NonNull String taskNamePrefix,
+ @NonNull String outputLocation,
+ final boolean includeDependencies,
+ final boolean process9Patch) {
+ MergeResources mergeResourcesTask = project.tasks.create(
+ "$taskNamePrefix${variantData.variantConfiguration.fullName.capitalize()}Resources",
+ MergeResources)
+
+ mergeResourcesTask.dependsOn variantData.prepareDependenciesTask, variantData.renderscriptCompileTask
+ mergeResourcesTask.plugin = this
+ mergeResourcesTask.variant = variantData
+ mergeResourcesTask.incrementalFolder = project.file(
+ "$project.buildDir/incremental/${taskNamePrefix}Resources/${variantData.variantConfiguration.dirName}")
+
+ mergeResourcesTask.process9Patch = process9Patch
+
+ mergeResourcesTask.conventionMapping.inputResourceSets = {
+ variantData.variantConfiguration.getResourceSets(
+ variantData.renderscriptCompileTask.getResOutputDir(),
+ includeDependencies)
+ }
+
+ mergeResourcesTask.conventionMapping.outputDir = { project.file(outputLocation) }
+
+ return mergeResourcesTask
+ }
+
+ protected void createMergeAssetsTask(@NonNull BaseVariantData variantData,
+ @Nullable String outputLocation,
+ final boolean includeDependencies) {
+ if (outputLocation == null) {
+ outputLocation = "$project.buildDir/assets/${variantData.variantConfiguration.dirName}"
+ }
+
+ def mergeAssetsTask = project.tasks.create(
+ "merge${variantData.variantConfiguration.fullName.capitalize()}Assets",
+ MergeAssets)
+ variantData.mergeAssetsTask = mergeAssetsTask
+
+ mergeAssetsTask.dependsOn variantData.prepareDependenciesTask
+ mergeAssetsTask.plugin = this
+ mergeAssetsTask.variant = variantData
+ mergeAssetsTask.incrementalFolder =
+ project.file("$project.buildDir/incremental/mergeAssets/${variantData.variantConfiguration.dirName}")
+
+ mergeAssetsTask.conventionMapping.inputAssetSets = {
+ variantData.variantConfiguration.getAssetSets(includeDependencies)
+ }
+ mergeAssetsTask.conventionMapping.outputDir = { project.file(outputLocation) }
+ }
+
+ protected void createBuildConfigTask(BaseVariantData variantData) {
+ def generateBuildConfigTask = project.tasks.create(
+ "generate${variantData.variantConfiguration.fullName.capitalize()}BuildConfig",
+ GenerateBuildConfig)
+ variantData.generateBuildConfigTask = generateBuildConfigTask
+
+ VariantConfiguration variantConfiguration = variantData.variantConfiguration
+
+ variantData.sourceGenTask.dependsOn generateBuildConfigTask
+ if (variantConfiguration.type == VariantConfiguration.Type.TEST) {
+ // in case of a test project, the manifest is generated so we need to depend
+ // on its creation.
+ generateBuildConfigTask.dependsOn variantData.processManifestTask
+ }
+
+ generateBuildConfigTask.plugin = this
+ generateBuildConfigTask.variant = variantData
+
+ generateBuildConfigTask.conventionMapping.buildConfigPackageName = {
+ variantConfiguration.originalPackageName
+ }
+
+ generateBuildConfigTask.conventionMapping.appPackageName = {
+ variantConfiguration.packageName
+ }
+
+ generateBuildConfigTask.conventionMapping.versionName = {
+ variantConfiguration.versionName
+ }
+
+ generateBuildConfigTask.conventionMapping.versionCode = {
+ variantConfiguration.versionCode
+ }
+
+ generateBuildConfigTask.conventionMapping.debuggable = {
+ variantConfiguration.buildType.isDebuggable()
+ }
+
+ generateBuildConfigTask.conventionMapping.buildTypeName = {
+ variantConfiguration.buildType.name
+ }
+
+ generateBuildConfigTask.conventionMapping.flavorName = {
+ variantConfiguration.flavorName
+ }
+
+ generateBuildConfigTask.conventionMapping.flavorNamesWithDimensionNames = {
+ variantConfiguration.flavorNamesWithDimensionNames
+ }
+
+ generateBuildConfigTask.conventionMapping.items = {
+ variantConfiguration.buildConfigItems
+ }
+
+ generateBuildConfigTask.conventionMapping.sourceOutputDir = {
+ project.file("$project.buildDir/source/buildConfig/${variantData.variantConfiguration.dirName}")
+ }
+ }
+
+ protected void createProcessResTask(BaseVariantData variantData) {
+ createProcessResTask(variantData, "$project.buildDir/symbols/${variantData.variantConfiguration.dirName}")
+ }
+
+ protected void createProcessResTask(BaseVariantData variantData, final String symbolLocation) {
+ def processResources = project.tasks.create(
+ "process${variantData.variantConfiguration.fullName.capitalize()}Resources",
+ ProcessAndroidResources)
+ variantData.processResourcesTask = processResources
+
+ variantData.sourceGenTask.dependsOn processResources
+ processResources.dependsOn variantData.processManifestTask, variantData.mergeResourcesTask, variantData.mergeAssetsTask
+
+ processResources.plugin = this
+ processResources.variant = variantData
+
+ VariantConfiguration variantConfiguration = variantData.variantConfiguration
+
+ processResources.conventionMapping.manifestFile = {
+ variantData.processManifestTask.manifestOutputFile
+ }
+
+ processResources.conventionMapping.resDir = {
+ variantData.mergeResourcesTask.outputDir
+ }
+
+ processResources.conventionMapping.assetsDir = {
+ variantData.mergeAssetsTask.outputDir
+ }
+
+ processResources.conventionMapping.libraries = {
+ getTextSymbolDependencies(variantConfiguration.allLibraries)
+ }
+ processResources.conventionMapping.packageForR = {
+ variantConfiguration.originalPackageName
+ }
+
+ // TODO: unify with generateBuilderConfig, compileAidl, and library packaging somehow?
+ processResources.conventionMapping.sourceOutputDir = {
+ project.file("$project.buildDir/source/r/${variantData.variantConfiguration.dirName}")
+ }
+ processResources.conventionMapping.textSymbolOutputDir = {
+ project.file(symbolLocation)
+ }
+ processResources.conventionMapping.packageOutputFile = {
+ project.file(
+ "$project.buildDir/libs/${project.archivesBaseName}-${variantData.variantConfiguration.baseName}.ap_")
+ }
+ if (variantConfiguration.buildType.runProguard) {
+ processResources.conventionMapping.proguardOutputFile = {
+ project.file("$project.buildDir/proguard/${variantData.variantConfiguration.dirName}/aapt_rules.txt")
+ }
+ }
+
+ processResources.conventionMapping.type = { variantConfiguration.type }
+ processResources.conventionMapping.debuggable = { variantConfiguration.buildType.debuggable }
+ processResources.conventionMapping.aaptOptions = { extension.aaptOptions }
+ processResources.conventionMapping.resourceConfigs = { variantConfiguration.mergedFlavor.resourceConfigurations }
+ }
+
+ protected void createProcessJavaResTask(BaseVariantData variantData) {
+ VariantConfiguration variantConfiguration = variantData.variantConfiguration
+
+ Copy processResources = project.tasks.create(
+ "process${variantData.variantConfiguration.fullName.capitalize()}JavaRes",
+ ProcessResources);
+ variantData.processJavaResourcesTask = processResources
+
+ // set the input
+ processResources.from(((AndroidSourceSet) variantConfiguration.defaultSourceSet).resources)
+
+ if (variantConfiguration.type != VariantConfiguration.Type.TEST) {
+ processResources.from(
+ ((AndroidSourceSet) variantConfiguration.buildTypeSourceSet).resources)
+ }
+ if (variantConfiguration.hasFlavors()) {
+ for (SourceProvider flavorSourceSet : variantConfiguration.flavorSourceProviders) {
+ processResources.from(((AndroidSourceSet) flavorSourceSet).resources)
+ }
+ }
+
+ processResources.conventionMapping.destinationDir = {
+ project.file("$project.buildDir/javaResources/${variantData.variantConfiguration.dirName}")
+ }
+ }
+
+ protected void createAidlTask(BaseVariantData variantData) {
+ VariantConfiguration variantConfiguration = variantData.variantConfiguration
+
+ def compileTask = project.tasks.create(
+ "compile${variantData.variantConfiguration.fullName.capitalize()}Aidl",
+ AidlCompile)
+ variantData.aidlCompileTask = compileTask
+
+ variantData.sourceGenTask.dependsOn compileTask
+ variantData.aidlCompileTask.dependsOn variantData.prepareDependenciesTask
+
+ compileTask.plugin = this
+ compileTask.variant = variantData
+ compileTask.incrementalFolder =
+ project.file("$project.buildDir/incremental/aidl/${variantData.variantConfiguration.dirName}")
+
+ compileTask.conventionMapping.sourceDirs = { variantConfiguration.aidlSourceList }
+ compileTask.conventionMapping.importDirs = { variantConfiguration.aidlImports }
+
+ compileTask.conventionMapping.sourceOutputDir = {
+ project.file("$project.buildDir/source/aidl/${variantData.variantConfiguration.dirName}")
+ }
+ }
+
+ protected void createCompileTask(BaseVariantData variantData,
+ BaseVariantData testedVariantData) {
+ def compileTask = project.tasks.create(
+ "compile${variantData.variantConfiguration.fullName.capitalize()}Java",
+ JavaCompile)
+ variantData.javaCompileTask = compileTask
+ compileTask.dependsOn variantData.sourceGenTask, variantData.variantDependency.compileConfiguration
+
+ VariantConfiguration config = variantData.variantConfiguration
+
+ List<Object> sourceList = Lists.newArrayList()
+ sourceList.add(((AndroidSourceSet) config.defaultSourceSet).java)
+ sourceList.add({ variantData.processResourcesTask.sourceOutputDir })
+ sourceList.add({ variantData.generateBuildConfigTask.sourceOutputDir })
+ sourceList.add({ variantData.aidlCompileTask.sourceOutputDir })
+ if (!config.mergedFlavor.renderscriptNdkMode) {
+ sourceList.add({ variantData.renderscriptCompileTask.sourceOutputDir })
+ }
+
+ if (config.getType() != VariantConfiguration.Type.TEST) {
+ sourceList.add(((AndroidSourceSet) config.buildTypeSourceSet).java)
+ }
+ if (config.hasFlavors()) {
+ for (SourceProvider flavorSourceProvider : config.flavorSourceProviders) {
+ sourceList.add(((AndroidSourceSet) flavorSourceProvider).java)
+ }
+ }
+ compileTask.source = sourceList.toArray()
+
+ // if the tested variant is an app, add its classpath. For the libraries,
+ // it's done automatically since the classpath includes the library output as a normal
+ // dependency.
+ if (testedVariantData instanceof ApplicationVariantData) {
+ compileTask.conventionMapping.classpath = {
+ project.files(getAndroidBuilder(variantData).getCompileClasspath(config)) + testedVariantData.javaCompileTask.classpath + testedVariantData.javaCompileTask.outputs.files
+ }
+ } else {
+ compileTask.conventionMapping.classpath = {
+ project.files(getAndroidBuilder(variantData).getCompileClasspath(config))
+ }
+ }
+
+ // TODO - dependency information for the compile classpath is being lost.
+ // Add a temporary approximation
+ compileTask.dependsOn project.configurations.compile.buildDependencies
+
+ compileTask.conventionMapping.destinationDir = {
+ project.file("$project.buildDir/classes/${variantData.variantConfiguration.dirName}")
+ }
+ compileTask.conventionMapping.dependencyCacheDir = {
+ project.file("$project.buildDir/dependency-cache/${variantData.variantConfiguration.dirName}")
+ }
+
+ // set source/target compatibility
+ compileTask.conventionMapping.sourceCompatibility = {
+ extension.compileOptions.sourceCompatibility.toString()
+ }
+ compileTask.conventionMapping.targetCompatibility = {
+ extension.compileOptions.targetCompatibility.toString()
+ }
+ compileTask.options.encoding = extension.compileOptions.encoding
+
+ // setup the boot classpath just before the task actually runs since this will
+ // force the sdk to be parsed.
+ compileTask.doFirst {
+ compileTask.options.bootClasspath = getRuntimeJars()
+ }
+ }
+
+ protected void createNdkTasks(@NonNull BaseVariantData variantData) {
+ createNdkTasks(
+ variantData,
+ { project.file("$project.buildDir/ndk/${variantData.variantConfiguration.dirName}/lib") }
+ )
+ }
+
+ protected void createNdkTasks(@NonNull BaseVariantData variantData,
+ @NonNull Closure<File> soFolderClosure) {
+ NdkCompile ndkCompile = project.tasks.create(
+ "compile${variantData.variantConfiguration.fullName.capitalize()}Ndk",
+ NdkCompile)
+
+ ndkCompile.plugin = this
+ ndkCompile.variant = variantData
+ variantData.ndkCompileTask = ndkCompile
+
+ VariantConfiguration variantConfig = variantData.variantConfiguration
+
+ if (variantConfig.mergedFlavor.renderscriptNdkMode) {
+ ndkCompile.ndkRenderScriptMode = true
+ ndkCompile.dependsOn variantData.renderscriptCompileTask
+ } else {
+ ndkCompile.ndkRenderScriptMode = false
+ }
+
+ ndkCompile.conventionMapping.sourceFolders = {
+ List<File> sourceList = variantConfig.jniSourceList
+ if (variantConfig.mergedFlavor.renderscriptNdkMode) {
+ sourceList.add(variantData.renderscriptCompileTask.sourceOutputDir)
+ }
+
+ return sourceList
+ }
+
+ ndkCompile.conventionMapping.generatedMakefile = {
+ project.file("$project.buildDir/ndk/${variantData.variantConfiguration.dirName}/Android.mk")
+ }
+
+ ndkCompile.conventionMapping.ndkConfig = { variantConfig.ndkConfig }
+
+ ndkCompile.conventionMapping.debuggable = {
+ variantConfig.buildType.jniDebugBuild
+ }
+
+ ndkCompile.conventionMapping.objFolder = {
+ project.file("$project.buildDir/ndk/${variantData.variantConfiguration.dirName}/obj")
+ }
+ ndkCompile.conventionMapping.soFolder = soFolderClosure
+ }
+
+ /**
+ * Creates the tasks to build the test apk.
+ *
+ * @param variant the test variant
+ * @param testedVariant the tested variant
+ * @param configDependencies the list of config dependencies
+ */
+ protected void createTestApkTasks(@NonNull TestVariantData variantData,
+ @NonNull BaseVariantData testedVariantData) {
+ // The test app is signed with the same info as the tested app so there's no need
+ // to test both.
+ if (!variantData.isSigned()) {
+ throw new GradleException(
+ "Tested Variant '${testedVariantData.variantConfiguration.fullName}' is not configured to create a signed APK.")
+ }
+
+ createAnchorTasks(variantData)
+
+ // Add a task to process the manifest
+ createProcessTestManifestTask(variantData, "manifests")
+
+ // Add a task to compile renderscript files.
+ createRenderscriptTask(variantData)
+
+ // Add a task to merge the resource folders
+ createMergeResourcesTask(variantData, true /*process9Patch*/)
+
+ // Add a task to merge the assets folders
+ createMergeAssetsTask(variantData, null /*default location*/, true /*includeDependencies*/)
+
+ if (testedVariantData.variantConfiguration.type == VariantConfiguration.Type.LIBRARY) {
+ // in this case the tested library must be fully built before test can be built!
+ if (testedVariantData.assembleTask != null) {
+ variantData.processManifestTask.dependsOn testedVariantData.assembleTask
+ variantData.mergeResourcesTask.dependsOn testedVariantData.assembleTask
+ }
+ }
+
+ // Add a task to create the BuildConfig class
+ createBuildConfigTask(variantData)
+
+ // Add a task to generate resource source files
+ createProcessResTask(variantData)
+
+ // process java resources
+ createProcessJavaResTask(variantData)
+
+ createAidlTask(variantData)
+
+ // Add a task to compile the test application
+ createCompileTask(variantData, testedVariantData)
+
+ // Add NDK tasks
+ createNdkTasks(variantData)
+
+ addPackageTasks(variantData, null)
+
+ if (assembleTest != null) {
+ assembleTest.dependsOn variantData.assembleTask
+ }
+ }
+
+ // TODO - should compile src/lint/java from src/lint/java and jar it into build/lint/lint.jar
+ protected void createLintCompileTask() {
+ lintCompile = project.tasks.create("compileLint", Task)
+ File outputDir = new File("$project.buildDir/lint")
+
+ lintCompile.doFirst{
+ // create the directory for lint output if it does not exist.
+ if (!outputDir.exists()) {
+ boolean mkdirs = outputDir.mkdirs();
+ if (!mkdirs) {
+ throw new GradleException("Unable to create lint output directory.")
+ }
+ }
+ }
+ }
+
+ /** Is the given variant relevant for lint? */
+ private static boolean isLintVariant(@NonNull BaseVariantData baseVariantData) {
+ // Only create lint targets for variants like debug and release, not debugTest
+ VariantConfiguration config = baseVariantData.variantConfiguration
+ return config.getType() != VariantConfiguration.Type.TEST;
+ }
+
+ // Add tasks for running lint on individual variants. We've already added a
+ // lint task earlier which runs on all variants.
+ protected void createLintTasks() {
+ int count = variantDataList.size()
+ for (int i = 0 ; i < count ; i++) {
+ final BaseVariantData baseVariantData = variantDataList.get(i)
+ if (!isLintVariant(baseVariantData)) {
+ continue;
+ }
+
+ String variantName = baseVariantData.variantConfiguration.fullName
+ def capitalizedVariantName = variantName.capitalize()
+ Task lintCheck = project.tasks.create("lint" + capitalizedVariantName, Lint)
+ lintCheck.dependsOn baseVariantData.javaCompileTask, lintCompile
+ // Note that we don't do "lint.dependsOn lintCheck"; the "lint" target will
+ // on its own run through all variants (and compare results), it doesn't delegate
+ // to the individual tasks (since it needs to coordinate data collection and
+ // reporting)
+ lintCheck.setPlugin(this)
+ lintCheck.setVariantName(variantName)
+ lintCheck.description = "Runs lint on the " + capitalizedVariantName + " build"
+ lintCheck.group = JavaBasePlugin.VERIFICATION_GROUP
+ }
+ }
+
+ protected void createCheckTasks(boolean hasFlavors, boolean isLibraryTest) {
+ List<AndroidReportTask> reportTasks = Lists.newArrayListWithExpectedSize(2)
+
+ List<DeviceProvider> providers = extension.deviceProviders
+ List<TestServer> servers = extension.testServers
+
+ Task mainConnectedTask = connectedCheck
+ String connectedRootName = "${CONNECTED}${INSTRUMENT_TEST.capitalize()}"
+ // if more than one flavor, create a report aggregator task and make this the parent
+ // task for all new connected tasks.
+ if (hasFlavors) {
+ mainConnectedTask = project.tasks.create(connectedRootName, AndroidReportTask)
+ mainConnectedTask.group = JavaBasePlugin.VERIFICATION_GROUP
+ mainConnectedTask.description = "Installs and runs instrumentation tests for all flavors on connected devices."
+ mainConnectedTask.reportType = ReportType.MULTI_FLAVOR
+ connectedCheck.dependsOn mainConnectedTask
+
+ mainConnectedTask.conventionMapping.resultsDir = {
+ String rootLocation = extension.testOptions.resultsDir != null ?
+ extension.testOptions.resultsDir : "$project.buildDir/$FD_INSTRUMENT_RESULTS"
+
+ project.file("$rootLocation/connected/$FD_FLAVORS_ALL")
+ }
+ mainConnectedTask.conventionMapping.reportsDir = {
+ String rootLocation = extension.testOptions.reportDir != null ?
+ extension.testOptions.reportDir :
+ "$project.buildDir/$FD_REPORTS/$FD_INSTRUMENT_TESTS"
+
+ project.file("$rootLocation/connected/$FD_FLAVORS_ALL")
+ }
+
+ reportTasks.add(mainConnectedTask)
+ }
+
+ Task mainProviderTask = deviceCheck
+ // if more than one provider tasks, either because of several flavors, or because of
+ // more than one providers, then create an aggregate report tasks for all of them.
+ if (providers.size() > 1 || hasFlavors) {
+ mainProviderTask = project.tasks.create("${DEVICE}${INSTRUMENT_TEST.capitalize()}",
+ AndroidReportTask)
+ mainProviderTask.group = JavaBasePlugin.VERIFICATION_GROUP
+ mainProviderTask.description = "Installs and runs instrumentation tests using all Device Providers."
+ mainProviderTask.reportType = ReportType.MULTI_FLAVOR
+ deviceCheck.dependsOn mainProviderTask
+
+ mainProviderTask.conventionMapping.resultsDir = {
+ String rootLocation = extension.testOptions.resultsDir != null ?
+ extension.testOptions.resultsDir : "$project.buildDir/$FD_INSTRUMENT_RESULTS"
+
+ project.file("$rootLocation/devices/$FD_FLAVORS_ALL")
+ }
+ mainProviderTask.conventionMapping.reportsDir = {
+ String rootLocation = extension.testOptions.reportDir != null ?
+ extension.testOptions.reportDir :
+ "$project.buildDir/$FD_REPORTS/$FD_INSTRUMENT_TESTS"
+
+ project.file("$rootLocation/devices/$FD_FLAVORS_ALL")
+ }
+
+ reportTasks.add(mainProviderTask)
+ }
+
+ // now look for the testedvariant and create the check tasks for them.
+ // don't use an auto loop as we can't reuse baseVariantData or the closure lower
+ // gets broken.
+ int count = variantDataList.size();
+ for (int i = 0 ; i < count ; i++) {
+ final BaseVariantData baseVariantData = variantDataList.get(i);
+ if (baseVariantData instanceof TestedVariantData) {
+ final TestVariantData testVariantData = ((TestedVariantData) baseVariantData).testVariantData
+ if (testVariantData == null) {
+ continue
+ }
+
+ // create the check tasks for this test
+
+ // first the connected one.
+ def connectedTask = createDeviceProviderInstrumentTestTask(
+ hasFlavors ?
+ "${connectedRootName}${baseVariantData.variantConfiguration.fullName.capitalize()}" : connectedRootName,
+ "Installs and runs the tests for Build '${baseVariantData.variantConfiguration.fullName}' on connected devices.",
+ isLibraryTest ?
+ DeviceProviderInstrumentTestLibraryTask :
+ DeviceProviderInstrumentTestTask,
+ testVariantData,
+ baseVariantData,
+ new ConnectedDeviceProvider(getSdkParser()),
+ CONNECTED
+ )
+
+ mainConnectedTask.dependsOn connectedTask
+ testVariantData.connectedTestTask = connectedTask
+
+ // now the providers.
+ for (DeviceProvider deviceProvider : providers) {
+ DefaultTask providerTask = createDeviceProviderInstrumentTestTask(
+ hasFlavors ?
+ "${deviceProvider.name}${INSTRUMENT_TEST.capitalize()}${baseVariantData.variantConfiguration.fullName.capitalize()}" :
+ "${deviceProvider.name}${INSTRUMENT_TEST.capitalize()}",
+ "Installs and runs the tests for Build '${baseVariantData.variantConfiguration.fullName}' using Provider '${deviceProvider.name.capitalize()}'.",
+ isLibraryTest ?
+ DeviceProviderInstrumentTestLibraryTask :
+ DeviceProviderInstrumentTestTask,
+ testVariantData,
+ baseVariantData,
+ deviceProvider,
+ "$DEVICE/$deviceProvider.name"
+ )
+
+ mainProviderTask.dependsOn providerTask
+ testVariantData.providerTestTaskList.add(providerTask)
+
+ if (!deviceProvider.isConfigured()) {
+ providerTask.enabled = false;
+ }
+ }
+
+ // now the test servers
+ // don't use an auto loop as it'll break the closure inside.
+ for (TestServer testServer : servers) {
+ DefaultTask serverTask = project.tasks.create(
+ hasFlavors ?
+ "${testServer.name}${"upload".capitalize()}${baseVariantData.variantConfiguration.fullName}" :
+ "${testServer.name}${"upload".capitalize()}",
+ TestServerTask)
+ serverTask.description = "Uploads APKs for Build '${baseVariantData.variantConfiguration.fullName}' to Test Server '${testServer.name.capitalize()}'."
+ serverTask.group = JavaBasePlugin.VERIFICATION_GROUP
+ serverTask.dependsOn testVariantData.assembleTask, baseVariantData.assembleTask
+
+ serverTask.testServer = testServer
+
+ serverTask.conventionMapping.testApk = { testVariantData.outputFile }
+ if (!(baseVariantData instanceof LibraryVariantData)) {
+ serverTask.conventionMapping.testedApk = { baseVariantData.outputFile }
+ }
+
+ serverTask.conventionMapping.variantName = { baseVariantData.variantConfiguration.fullName }
+
+ deviceCheck.dependsOn serverTask
+
+ if (!testServer.isConfigured()) {
+ serverTask.enabled = false;
+ }
+ }
+ }
+ }
+
+ // If gradle is launched with --continue, we want to run all tests and generate an
+ // aggregate report (to help with the fact that we may have several build variants, or
+ // or several device providers).
+ // To do that, the report tasks must run even if one of their dependent tasks (flavor
+ // or specific provider tasks) fails, when --continue is used, and the report task is
+ // meant to run (== is in the task graph).
+ // To do this, we make the children tasks ignore their errors (ie they won't fail and
+ // stop the build).
+ if (!reportTasks.isEmpty() && project.gradle.startParameter.continueOnFailure) {
+ project.gradle.taskGraph.whenReady { taskGraph ->
+ for (AndroidReportTask reportTask : reportTasks) {
+ if (taskGraph.hasTask(reportTask)) {
+ reportTask.setWillRun()
+ }
+ }
+ }
+ }
+ }
+
+ private DeviceProviderInstrumentTestTask createDeviceProviderInstrumentTestTask(
+ @NonNull String taskName,
+ @NonNull String description,
+ @NonNull Class<? extends DeviceProviderInstrumentTestTask> taskClass,
+ @NonNull TestVariantData variantData,
+ @NonNull BaseVariantData testedVariantData,
+ @NonNull DeviceProvider deviceProvider,
+ @NonNull String subFolder) {
+
+ def testTask = project.tasks.create(taskName, taskClass)
+ testTask.description = description
+ testTask.group = JavaBasePlugin.VERIFICATION_GROUP
+ testTask.dependsOn testedVariantData.assembleTask, variantData.assembleTask
+
+ testTask.plugin = this
+ testTask.variant = variantData
+ testTask.flavorName = variantData.variantConfiguration.flavorName.capitalize()
+ testTask.deviceProvider = deviceProvider
+
+ testTask.conventionMapping.testApp = { variantData.outputFile }
+ if (testedVariantData.variantConfiguration.type != VariantConfiguration.Type.LIBRARY) {
+ testTask.conventionMapping.testedApp = { testedVariantData.outputFile }
+ }
+
+ testTask.conventionMapping.resultsDir = {
+ String rootLocation = extension.testOptions.resultsDir != null ?
+ extension.testOptions.resultsDir :
+ "$project.buildDir/$FD_INSTRUMENT_RESULTS"
+
+ String flavorFolder = variantData.variantConfiguration.flavorName
+ if (!flavorFolder.isEmpty()) {
+ flavorFolder = "$FD_FLAVORS/" + flavorFolder
+ }
+
+ project.file("$rootLocation/$subFolder/$flavorFolder")
+ }
+ testTask.conventionMapping.reportsDir = {
+ String rootLocation = extension.testOptions.reportDir != null ?
+ extension.testOptions.reportDir :
+ "$project.buildDir/$FD_REPORTS/$FD_INSTRUMENT_TESTS"
+
+ String flavorFolder = variantData.variantConfiguration.flavorName
+ if (!flavorFolder.isEmpty()) {
+ flavorFolder = "$FD_FLAVORS/" + flavorFolder
+ }
+
+ project.file("$rootLocation/$subFolder/$flavorFolder")
+ }
+
+ return testTask
+ }
+
+ /**
+ * Creates the packaging tasks for the given Variant.
+ * @param variantData the variant data.
+ * @param assembleTask an optional assembleTask to be used. If null a new one is created. The
+ * assembleTask is always set in the Variant.
+ */
+ protected void addPackageTasks(@NonNull ApkVariantData variantData,
+ @Nullable Task assembleTask) {
+ VariantConfiguration variantConfig = variantData.variantConfiguration
+
+ boolean runProguard = variantConfig.buildType.runProguard &&
+ (variantConfig.type != VariantConfiguration.Type.TEST ||
+ (variantConfig.type == VariantConfiguration.Type.TEST &&
+ variantConfig.testedConfig.type != VariantConfiguration.Type.LIBRARY))
+
+ // common dex task configuration
+ String dexTaskName = "dex${variantData.variantConfiguration.fullName.capitalize()}"
+ Dex dexTask = project.tasks.create(dexTaskName, Dex)
+ variantData.dexTask = dexTask
+
+ dexTask.plugin = this
+ dexTask.variant = variantData
+
+ dexTask.conventionMapping.outputFile = {
+ project.file(
+ "${project.buildDir}/libs/${project.archivesBaseName}-${variantData.variantConfiguration.baseName}.dex")
+ }
+ dexTask.dexOptions = extension.dexOptions
+
+ if (runProguard) {
+
+ // first proguard task.
+ BaseVariantData testedVariantData = variantData instanceof TestVariantData ? variantData.testedVariantData : null as BaseVariantData
+ File outFile = createProguardTasks(variantData, testedVariantData)
+
+ // then dexing task
+ dexTask.dependsOn variantData.proguardTask
+ dexTask.conventionMapping.inputFiles = { project.files(outFile) }
+ dexTask.conventionMapping.preDexedLibraries = { Collections.emptyList() }
+
+ } else {
+
+ // if required, pre-dexing task.
+ PreDex preDexTask = null;
+ boolean runPreDex = extension.dexOptions.preDexLibraries
+ if (runPreDex) {
+ def preDexTaskName = "preDex${variantData.variantConfiguration.fullName.capitalize()}"
+ preDexTask = project.tasks.create(preDexTaskName, PreDex)
+
+ preDexTask.dependsOn variantData.javaCompileTask
+ preDexTask.plugin = this
+ preDexTask.dexOptions = extension.dexOptions
+
+ preDexTask.conventionMapping.inputFiles = {
+ project.files(getAndroidBuilder(variantData).getPackagedJars(variantConfig))
+ }
+ preDexTask.conventionMapping.outputFolder = {
+ project.file(
+ "${project.buildDir}/pre-dexed/${variantData.variantConfiguration.dirName}")
+ }
+ }
+
+ // then dexing task
+ dexTask.dependsOn variantData.javaCompileTask
+ if (runPreDex) {
+ dexTask.dependsOn preDexTask
+ }
+
+ dexTask.conventionMapping.inputFiles = { variantData.javaCompileTask.outputs.files }
+ if (runPreDex) {
+ dexTask.conventionMapping.preDexedLibraries = {
+ project.fileTree(preDexTask.outputFolder).files
+ }
+ } else {
+ dexTask.conventionMapping.preDexedLibraries = {
+ project.files(getAndroidBuilder(variantData).getPackagedJars(variantConfig))
+ }
+ }
+ }
+
+ // Add a task to generate application package
+ def packageApp = project.tasks.create(
+ "package${variantData.variantConfiguration.fullName.capitalize()}",
+ PackageApplication)
+ variantData.packageApplicationTask = packageApp
+ packageApp.dependsOn variantData.processResourcesTask, dexTask, variantData.processJavaResourcesTask, variantData.ndkCompileTask
+
+ packageApp.plugin = this
+ packageApp.variant = variantData
+
+ packageApp.conventionMapping.resourceFile = {
+ variantData.processResourcesTask.packageOutputFile
+ }
+ packageApp.conventionMapping.dexFile = { dexTask.outputFile }
+ packageApp.conventionMapping.packagedJars = { getAndroidBuilder(variantData).getPackagedJars(variantConfig) }
+ packageApp.conventionMapping.javaResourceDir = {
+ getOptionalDir(variantData.processJavaResourcesTask.destinationDir)
+ }
+ packageApp.conventionMapping.jniFolders = {
+ // for now only the project's compilation output.
+ Set<File> set = Sets.newHashSet()
+ set.addAll(variantData.ndkCompileTask.soFolder)
+ set.addAll(variantData.renderscriptCompileTask.libOutputDir)
+ set.addAll(variantConfig.libraryJniFolders)
+
+ if (variantConfig.mergedFlavor.renderscriptSupportMode) {
+ File rsLibs = getAndroidBuilder(variantData).getSupportNativeLibFolder()
+ if (rsLibs.isDirectory()) {
+ set.add(rsLibs);
+ }
+ }
+
+ return set
+ }
+ packageApp.conventionMapping.abiFilters = { variantConfig.supportedAbis }
+ packageApp.conventionMapping.jniDebugBuild = { variantConfig.buildType.jniDebugBuild }
+
+ SigningConfigDsl sc = (SigningConfigDsl) variantConfig.signingConfig
+ packageApp.conventionMapping.signingConfig = { sc }
+ if (sc != null) {
+ ValidateSigningTask validateSigningTask = validateSigningTaskMap.get(sc)
+ if (validateSigningTask == null) {
+ validateSigningTask = project.tasks.create("validate${sc.name.capitalize()}Signing",
+ ValidateSigningTask)
+ validateSigningTask.plugin = this
+ validateSigningTask.signingConfig = sc
+
+ validateSigningTaskMap.put(sc, validateSigningTask)
+ }
+
+ packageApp.dependsOn validateSigningTask
+ }
+
+ def signedApk = variantData.isSigned()
+ def apkName = signedApk ?
+ "${project.archivesBaseName}-${variantData.variantConfiguration.baseName}-unaligned.apk" :
+ "${project.archivesBaseName}-${variantData.variantConfiguration.baseName}-unsigned.apk"
+
+ packageApp.conventionMapping.outputFile = {
+ project.file("$project.buildDir/apk/${apkName}")
+ }
+
+ Task appTask = packageApp
+ OutputFileTask outputFileTask = packageApp
+
+ if (signedApk) {
+ if (variantData.zipAlign) {
+ // Add a task to zip align application package
+ def zipAlignTask = project.tasks.create(
+ "zipalign${variantData.variantConfiguration.fullName.capitalize()}",
+ ZipAlign)
+ variantData.zipAlignTask = zipAlignTask
+
+ zipAlignTask.dependsOn packageApp
+ zipAlignTask.conventionMapping.inputFile = { packageApp.outputFile }
+ zipAlignTask.conventionMapping.outputFile = {
+ project.file(
+ "$project.buildDir/apk/${project.archivesBaseName}-${variantData.variantConfiguration.baseName}.apk")
+ }
+ zipAlignTask.conventionMapping.zipAlignExe = { getSdkParser().zipAlign }
+
+ appTask = zipAlignTask
+ outputFileTask = zipAlignTask
+ variantData.outputFile = project.file(
+ "$project.buildDir/apk/${project.archivesBaseName}-${variantData.variantConfiguration.baseName}.apk")
+ }
+
+ // Add a task to install the application package
+ def installTask = project.tasks.create(
+ "install${variantData.variantConfiguration.fullName.capitalize()}",
+ InstallTask)
+ installTask.description = "Installs the " + variantData.description
+ installTask.group = INSTALL_GROUP
+ installTask.dependsOn appTask
+ installTask.conventionMapping.packageFile = { outputFileTask.outputFile }
+ installTask.conventionMapping.adbExe = { getSdkParser().adb }
+
+ variantData.installTask = installTask
+ }
+
+ // Add an assemble task
+ if (assembleTask == null) {
+ assembleTask = project.tasks.create("assemble${variantData.variantConfiguration.fullName.capitalize()}")
+ assembleTask.description = "Assembles the " + variantData.description
+ assembleTask.group = org.gradle.api.plugins.BasePlugin.BUILD_GROUP
+ }
+ assembleTask.dependsOn appTask
+ variantData.assembleTask = assembleTask
+
+ variantData.outputFile = { outputFileTask.outputFile }
+
+ // add an uninstall task
+ def uninstallTask = project.tasks.create(
+ "uninstall${variantData.variantConfiguration.fullName.capitalize()}",
+ UninstallTask)
+ uninstallTask.description = "Uninstalls the " + variantData.description
+ uninstallTask.group = INSTALL_GROUP
+ uninstallTask.variant = variantData
+ uninstallTask.conventionMapping.adbExe = { getSdkParser().adb }
+
+ variantData.uninstallTask = uninstallTask
+ uninstallAll.dependsOn uninstallTask
+ }
+
+ /**
+ * Creates the proguarding task for the given Variant.
+ * @param variantData the variant data.
+ * @param testedVariantData optional. variant data representing the tested variant, null if the
+ * variant is not a test variant
+ * @return outFile file outputted by proguard
+ */
+ @NonNull
+ protected File createProguardTasks(@NonNull BaseVariantData variantData,
+ @Nullable BaseVariantData testedVariantData) {
+ VariantConfiguration variantConfig = variantData.variantConfiguration
+
+ def proguardTask = project.tasks.create(
+ "proguard${variantData.variantConfiguration.fullName.capitalize()}",
+ ProGuardTask)
+ proguardTask.dependsOn variantData.javaCompileTask
+ if (testedVariantData != null) {
+ proguardTask.dependsOn testedVariantData.proguardTask
+ }
+
+ variantData.proguardTask = proguardTask
+
+ // --- Output File ---
+
+ File outFile;
+ if (variantData instanceof LibraryVariantData) {
+ outFile = project.file(
+ "${project.buildDir}/$DIR_BUNDLES/${variantData.variantConfiguration.dirName}/classes.jar")
+ } else {
+ outFile = project.file(
+ "${project.buildDir}/classes-proguard/${variantData.variantConfiguration.dirName}/classes.jar")
+ }
+
+ // --- Proguard Config ---
+
+ if (testedVariantData != null) {
+ // don't remove any code in tested app
+ proguardTask.dontshrink()
+ proguardTask.keepnames("class * extends junit.framework.TestCase")
+ proguardTask.keepclassmembers("class * extends junit.framework.TestCase {\n" +
+ " void test*(...);\n" +
+ "}")
+
+ // input the mapping from the tested app so that we can deal with obfuscated code
+ proguardTask.applymapping("${project.buildDir}/proguard/${testedVariantData.variantConfiguration.dirName}/mapping.txt")
+
+ // for tested app, we only care about their aapt config since the base
+ // configs are the same files anyway.
+ proguardTask.configuration(testedVariantData.processResourcesTask.proguardOutputFile)
+ }
+
+ // all the config files coming from build type, product flavors.
+ List<Object> proguardFiles = variantConfig.getProguardFiles(true /*includeLibs*/)
+ for (Object proguardFile : proguardFiles) {
+ proguardTask.configuration(proguardFile)
+ }
+
+ // also the config file output by aapt
+ proguardTask.configuration(variantData.processResourcesTask.proguardOutputFile)
+
+ // --- InJars / LibraryJars ---
+
+ List<File> packagedJars = getAndroidBuilder(variantData).getPackagedJars(variantConfig)
+
+ if (variantData instanceof LibraryVariantData) {
+ String packageName = variantConfig.getPackageFromManifest()
+ if (packageName == null) {
+ throw new BuildException("Failed to read manifest", null)
+ }
+ packageName = packageName.replace('.', '/');
+
+ // injar: the compilation output
+ // exclude R files and such from output
+ String exclude = '!' + packageName + "/R.class"
+ exclude += (', !' + packageName + "/R\$*.class")
+ exclude += (', !' + packageName + "/Manifest.class")
+ exclude += (', !' + packageName + "/Manifest\$*.class")
+ exclude += (', !' + packageName + "/BuildConfig.class")
+ proguardTask.injars(variantData.javaCompileTask.destinationDir, filter: exclude)
+
+ // include R files and such for compilation
+ String include = exclude.replace('!', '')
+ proguardTask.libraryjars(variantData.javaCompileTask.destinationDir, filter: include)
+
+ // injar: the local dependencies, filter out local jars from packagedJars
+ Object[] jars = LibraryPlugin.getLocalJarFileList(variantData.variantDependency)
+ for (Object inJar : jars) {
+ if (packagedJars.contains(inJar)) {
+ packagedJars.remove(inJar);
+ }
+ proguardTask.injars((File) inJar, filter: '!META-INF/MANIFEST.MF')
+ }
+
+ // libjar: the library dependencies
+ for (File libJar : packagedJars) {
+ proguardTask.libraryjars(libJar, filter: '!META-INF/MANIFEST.MF')
+ }
+
+ // ensure local jars keep their package names
+ proguardTask.keeppackagenames()
+ } else {
+ // injar: the compilation output
+ proguardTask.injars(variantData.javaCompileTask.destinationDir)
+
+ // injar: the dependencies
+ for (File inJar : packagedJars) {
+ proguardTask.injars(inJar, filter: '!META-INF/MANIFEST.MF')
+ }
+ }
+
+ // libraryJars: the runtime jars
+ for (String runtimeJar : getRuntimeJarList()) {
+ proguardTask.libraryjars(runtimeJar)
+ }
+
+ if (testedVariantData != null) {
+ // input the tested app as library
+ proguardTask.libraryjars(testedVariantData.javaCompileTask.destinationDir)
+ // including its dependencies
+ List<File> testedPackagedJars = getAndroidBuilder(testedVariantData).getPackagedJars(testedVariantData.variantConfiguration)
+ for (File inJar : testedPackagedJars) {
+ proguardTask.libraryjars(inJar, filter: '!META-INF/MANIFEST.MF')
+ }
+ }
+
+ // --- Out files ---
+
+ proguardTask.outjars(outFile)
+
+ proguardTask.dump("${project.buildDir}/proguard/${variantData.variantConfiguration.dirName}/dump.txt")
+ proguardTask.printseeds(
+ "${project.buildDir}/proguard/${variantData.variantConfiguration.dirName}/seeds.txt")
+ proguardTask.printusage(
+ "${project.buildDir}/proguard/${variantData.variantConfiguration.dirName}/usage.txt")
+ proguardTask.printmapping(
+ "${project.buildDir}/proguard/${variantData.variantConfiguration.dirName}/mapping.txt")
+
+ return outFile
+ }
+
+ private void createReportTasks() {
+ def dependencyReportTask = project.tasks.create("androidDependencies", DependencyReportTask)
+ dependencyReportTask.setDescription("Displays the Android dependencies of the project")
+ dependencyReportTask.setVariants(variantDataList)
+ dependencyReportTask.setGroup("Android")
+
+ def signingReportTask = project.tasks.create("signingReport", SigningReportTask)
+ signingReportTask.setDescription("Displays the signing info for each variant")
+ signingReportTask.setVariants(variantDataList)
+ signingReportTask.setGroup("Android")
+ }
+
+ protected void createAnchorTasks(@NonNull BaseVariantData variantData) {
+ variantData.preBuildTask = project.tasks.create(
+ "pre${variantData.variantConfiguration.fullName.capitalize()}Build")
+ variantData.preBuildTask.dependsOn mainPreBuild
+
+ def prepareDependenciesTask = project.tasks.create(
+ "prepare${variantData.variantConfiguration.fullName.capitalize()}Dependencies",
+ PrepareDependenciesTask)
+
+ variantData.prepareDependenciesTask = prepareDependenciesTask
+ prepareDependenciesTask.dependsOn variantData.preBuildTask
+
+ prepareDependenciesTask.plugin = this
+ prepareDependenciesTask.variant = variantData
+
+ // for all libraries required by the configurations of this variant, make this task
+ // depend on all the tasks preparing these libraries.
+ VariantDependencies configurationDependencies = variantData.variantDependency
+ prepareDependenciesTask.addChecker(configurationDependencies.checker)
+
+ for (LibraryDependencyImpl lib : configurationDependencies.libraries) {
+ addDependencyToPrepareTask(variantData, prepareDependenciesTask, lib)
+ }
+
+ // also create sourceGenTask
+ variantData.sourceGenTask = project.tasks.create(
+ "generate${variantData.variantConfiguration.fullName.capitalize()}Sources")
+ }
+
+
+ private final Map<String, ArtifactMetaData> extraArtifactMap = Maps.newHashMap()
+ private final ListMultimap<String, AndroidArtifact> extraAndroidArtifacts = ArrayListMultimap.create()
+ private final ListMultimap<String, JavaArtifact> extraJavaArtifacts = ArrayListMultimap.create()
+ private final ListMultimap<String, SourceProviderContainer> extraVariantSourceProviders = ArrayListMultimap.create()
+ private final ListMultimap<String, SourceProviderContainer> extraBuildTypeSourceProviders = ArrayListMultimap.create()
+ private final ListMultimap<String, SourceProviderContainer> extraProductFlavorSourceProviders = ArrayListMultimap.create()
+ private final ListMultimap<String, SourceProviderContainer> extraMultiFlavorSourceProviders = ArrayListMultimap.create()
+
+
+ public Collection<ArtifactMetaData> getExtraArtifacts() {
+ return extraArtifactMap.values()
+ }
+
+ public Collection<AndroidArtifact> getExtraAndroidArtifacts(@NonNull String variantName) {
+ return extraAndroidArtifacts.get(variantName)
+ }
+
+ public Collection<JavaArtifact> getExtraJavaArtifacts(@NonNull String variantName) {
+ return extraJavaArtifacts.get(variantName)
+ }
+
+ public Collection<SourceProviderContainer> getExtraVariantSourceProviders(@NonNull String variantName) {
+ return extraVariantSourceProviders.get(variantName)
+ }
+
+ public Collection<SourceProviderContainer> getExtraFlavorSourceProviders(@NonNull String flavorName) {
+ return extraProductFlavorSourceProviders.get(flavorName)
+ }
+
+ public Collection<SourceProviderContainer> getExtraBuildTypeSourceProviders(@NonNull String buildTypeName) {
+ return extraBuildTypeSourceProviders.get(buildTypeName)
+ }
+
+ public void registerArtifactType(@NonNull String name,
+ boolean isTest,
+ int artifactType) {
+
+ if (extraArtifactMap.get(name) != null) {
+ throw new IllegalArgumentException("Artifact with name $name already registered.")
+ }
+
+ extraArtifactMap.put(name, new ArtifactMetaDataImpl(name, isTest, artifactType))
+ }
+
+ public void registerBuildTypeSourceProvider(@NonNull String name,
+ @NonNull BuildType buildType,
+ @NonNull SourceProvider sourceProvider) {
+ if (extraArtifactMap.get(name) == null) {
+ throw new IllegalArgumentException(
+ "Artifact with name $name is not yet registered. Use registerArtifactType()")
+ }
+
+ extraBuildTypeSourceProviders.put(buildType.name,
+ new DefaultSourceProviderContainer(name, sourceProvider))
+
+ }
+
+ public void registerProductFlavorSourceProvider(@NonNull String name,
+ @NonNull ProductFlavor productFlavor,
+ @NonNull SourceProvider sourceProvider) {
+ if (extraArtifactMap.get(name) == null) {
+ throw new IllegalArgumentException(
+ "Artifact with name $name is not yet registered. Use registerArtifactType()")
+ }
+
+ extraProductFlavorSourceProviders.put(productFlavor.name,
+ new DefaultSourceProviderContainer(name, sourceProvider))
+
+ }
+
+ public void registerMultiFlavorSourceProvider(@NonNull String name,
+ @NonNull String flavorName,
+ @NonNull SourceProvider sourceProvider) {
+ if (extraArtifactMap.get(name) == null) {
+ throw new IllegalArgumentException(
+ "Artifact with name $name is not yet registered. Use registerArtifactType()")
+ }
+
+ extraMultiFlavorSourceProviders.put(flavorName,
+ new DefaultSourceProviderContainer(name, sourceProvider))
+ }
+
+ public void registerJavaArtifact(
+ @NonNull String name,
+ @NonNull BaseVariant variant,
+ @NonNull String assembleTaskName,
+ @NonNull String javaCompileTaskName,
+ @NonNull File classesFolder,
+ @Nullable SourceProvider sourceProvider) {
+ ArtifactMetaData artifactMetaData = extraArtifactMap.get(name)
+ if (artifactMetaData == null) {
+ throw new IllegalArgumentException(
+ "Artifact with name $name is not yet registered. Use registerArtifactType()")
+ }
+ if (artifactMetaData.type != ArtifactMetaData.TYPE_JAVA) {
+ throw new IllegalArgumentException(
+ "Artifact with name $name is not of type JAVA")
+ }
+
+ JavaArtifact artifact = new JavaArtifactImpl(
+ name, assembleTaskName, javaCompileTaskName, classesFolder,
+ null, sourceProvider, null)
+ extraJavaArtifacts.put(variant.name, artifact)
+ }
+
+ //----------------------------------------------------------------------------------------------
+ //------------------------------ START DEPENDENCY STUFF ----------------------------------------
+ //----------------------------------------------------------------------------------------------
+
+ def addDependencyToPrepareTask(@NonNull BaseVariantData variantData,
+ @NonNull PrepareDependenciesTask prepareDependenciesTask,
+ @NonNull LibraryDependencyImpl lib) {
+ def prepareLibTask = prepareTaskMap.get(lib)
+ if (prepareLibTask != null) {
+ prepareDependenciesTask.dependsOn prepareLibTask
+ prepareLibTask.dependsOn variantData.preBuildTask
+ }
+
+ for (LibraryDependencyImpl childLib : lib.dependencies) {
+ addDependencyToPrepareTask(variantData, prepareDependenciesTask, childLib)
+ }
+ }
+
+ def resolveDependencies(VariantDependencies variantDeps) {
+ Map<ModuleVersionIdentifier, List<LibraryDependencyImpl>> modules = [:]
+ Map<ModuleVersionIdentifier, List<ResolvedArtifact>> artifacts = [:]
+ Multimap<LibraryDependency, VariantDependencies> reverseMap = ArrayListMultimap.create()
+
+ resolveDependencyForConfig(variantDeps, modules, artifacts, reverseMap)
+
+ modules.values().each { List list ->
+
+ if (!list.isEmpty()) {
+ // get the first item only
+ LibraryDependencyImpl androidDependency = (LibraryDependencyImpl) list.get(0)
+
+ String bundleName = GUtil.toCamelCase(androidDependency.name.replaceAll("\\:", " "))
+
+ def prepareLibraryTask = prepareTaskMap.get(androidDependency)
+ if (prepareLibraryTask == null) {
+ prepareLibraryTask = project.tasks.create("prepare${bundleName}Library",
+ PrepareLibraryTask)
+ prepareLibraryTask.description = "Prepare ${androidDependency.name}"
+ prepareLibraryTask.bundle = androidDependency.bundle
+ prepareLibraryTask.explodedDir = androidDependency.bundleFolder
+
+ prepareTaskMap.put(androidDependency, prepareLibraryTask)
+ }
+
+ // Use the reverse map to find all the configurations that included this android
+ // library so that we can make sure they are built.
+ List<VariantDependencies> configDepList = reverseMap.get(androidDependency)
+ if (configDepList != null && !configDepList.isEmpty()) {
+ for (VariantDependencies configDependencies: configDepList) {
+ prepareLibraryTask.dependsOn configDependencies.compileConfiguration.buildDependencies
+ }
+ }
+ }
+ }
+ }
+
+ def resolveDependencyForConfig(
+ VariantDependencies variantDeps,
+ Map<ModuleVersionIdentifier, List<LibraryDependencyImpl>> modules,
+ Map<ModuleVersionIdentifier, List<ResolvedArtifact>> artifacts,
+ Multimap<LibraryDependency, VariantDependencies> reverseMap) {
+
+ Configuration compileClasspath = variantDeps.compileConfiguration
+
+ // TODO - shouldn't need to do this - fix this in Gradle
+ ensureConfigured(compileClasspath)
+
+ variantDeps.checker = new DependencyChecker(variantDeps, logger)
+
+ Set<String> currentUnresolvedDependencies = Sets.newHashSet()
+
+ // TODO - defer downloading until required -- This is hard to do as we need the info to build the variant config.
+ List<LibraryDependencyImpl> bundles = []
+ List<JarDependency> jars = []
+ List<JarDependency> localJars = []
+ collectArtifacts(compileClasspath, artifacts)
+ def dependencies = compileClasspath.incoming.resolutionResult.root.dependencies
+ dependencies.each { DependencyResult dep ->
+ if (dep instanceof ResolvedDependencyResult) {
+ addDependency(dep.selected, variantDeps, bundles, jars, modules, artifacts, reverseMap)
+ } else if (dep instanceof UnresolvedDependencyResult) {
+ def attempted = dep.attempted;
+ if (attempted != null) {
+ currentUnresolvedDependencies.add(attempted.toString())
+ }
+ }
+ }
+
+ // also need to process local jar files, as they are not processed by the
+ // resolvedConfiguration result. This only includes the local jar files for this project.
+ compileClasspath.allDependencies.each { dep ->
+ if (dep instanceof SelfResolvingDependency &&
+ !(dep instanceof ProjectDependency)) {
+ Set<File> files = ((SelfResolvingDependency) dep).resolve()
+ for (File f : files) {
+ // TODO: support compile only dependencies.
+ localJars << new JarDependency(f)
+ }
+ }
+ }
+
+ // handle package dependencies. We'll refuse aar libs only in package but not
+ // in compile and remove all dependencies already in compile to get package-only jar
+ // files.
+ Configuration packageClasspath = variantDeps.packageConfiguration
+
+ if (!compileClasspath.resolvedConfiguration.hasError()) {
+ Set<File> compileFiles = compileClasspath.files
+ Set<File> packageFiles = packageClasspath.files
+
+ for (File f : packageFiles) {
+ if (compileFiles.contains(f)) {
+ continue
+ }
+
+ if (f.getName().toLowerCase().endsWith(".jar")) {
+ jars.add(new JarDependency(f, false /*compiled*/, true /*packaged*/))
+ } else {
+ throw new RuntimeException("Package-only dependency '" +
+ f.absolutePath +
+ "' is not supported")
+ }
+ }
+ } else if (!currentUnresolvedDependencies.isEmpty()) {
+ unresolvedDependencies.addAll(currentUnresolvedDependencies)
+ }
+
+ variantDeps.addLibraries(bundles)
+ variantDeps.addJars(jars)
+ variantDeps.addLocalJars(localJars)
+
+ // TODO - filter bundles out of source set classpath
+
+ configureBuild(variantDeps)
+ }
+
+ def ensureConfigured(Configuration config) {
+ config.allDependencies.withType(ProjectDependency).each { dep ->
+ project.evaluationDependsOn(dep.dependencyProject.path)
+ ensureConfigured(dep.projectConfiguration)
+ }
+ }
+
+ static def collectArtifacts(Configuration configuration, Map<ModuleVersionIdentifier,
+ List<ResolvedArtifact>> artifacts) {
+ boolean buildModelOnly = Boolean.getBoolean(AndroidProject.BUILD_MODEL_ONLY_SYSTEM_PROPERTY);
+ def allArtifacts
+ if (buildModelOnly) {
+ allArtifacts = configuration.resolvedConfiguration.lenientConfiguration.getArtifacts(Specs.satisfyAll())
+ } else {
+ allArtifacts = configuration.resolvedConfiguration.resolvedArtifacts
+ }
+
+ allArtifacts.each { ResolvedArtifact artifact ->
+ def id = artifact.moduleVersion.id
+ List<ResolvedArtifact> moduleArtifacts = artifacts[id]
+ if (moduleArtifacts == null) {
+ moduleArtifacts = []
+ artifacts[id] = moduleArtifacts
+ }
+ moduleArtifacts << artifact
+ }
+ }
+
+ def addDependency(ResolvedModuleVersionResult moduleVersion,
+ VariantDependencies configDependencies,
+ Collection<LibraryDependencyImpl> bundles,
+ List<JarDependency> jars,
+ Map<ModuleVersionIdentifier, List<LibraryDependencyImpl>> modules,
+ Map<ModuleVersionIdentifier, List<ResolvedArtifact>> artifacts,
+ Multimap<LibraryDependency, VariantDependencies> reverseMap) {
+ def id = moduleVersion.id
+ if (configDependencies.checker.excluded(id)) {
+ return
+ }
+
+ List<LibraryDependencyImpl> bundlesForThisModule = modules[id]
+ if (bundlesForThisModule == null) {
+ bundlesForThisModule = []
+ modules[id] = bundlesForThisModule
+
+ def nestedBundles = []
+ def dependencies = moduleVersion.dependencies
+ dependencies.each { DependencyResult dep ->
+ if (dep instanceof ResolvedDependencyResult) {
+ addDependency(dep.selected, configDependencies, nestedBundles,
+ jars, modules, artifacts, reverseMap)
+ }
+ }
+
+ def moduleArtifacts = artifacts[id]
+ moduleArtifacts?.each { artifact ->
+ if (artifact.type == EXT_LIB_ARCHIVE) {
+ String bundleName = GUtil.toCamelCase(id.group + " " + id.name + " " + id.version)
+ def explodedDir = project.file(
+ "$project.buildDir/exploded-bundles/${bundleName}.aar")
+ LibraryDependencyImpl adep = new LibraryDependencyImpl(
+ artifact.file, explodedDir, nestedBundles,
+ id.group + ":" + id.name + ":" + id.version)
+ bundlesForThisModule << adep
+ reverseMap.put(adep, configDependencies)
+ } else {
+ // TODO: support compile only dependencies.
+ jars << new JarDependency(artifact.file)
+ }
+ }
+
+ if (bundlesForThisModule.empty && !nestedBundles.empty) {
+ throw new GradleException("Module version $id depends on libraries but is not a library itself")
+ }
+ } else {
+ for (LibraryDependency adep : bundlesForThisModule) {
+ reverseMap.put(adep, configDependencies)
+ }
+ }
+
+ bundles.addAll(bundlesForThisModule)
+ }
+
+ private void configureBuild(VariantDependencies configurationDependencies) {
+ addDependsOnTaskInOtherProjects(
+ project.getTasks().getByName(JavaBasePlugin.BUILD_NEEDED_TASK_NAME), true,
+ JavaBasePlugin.BUILD_NEEDED_TASK_NAME, "compile");
+ addDependsOnTaskInOtherProjects(
+ project.getTasks().getByName(JavaBasePlugin.BUILD_DEPENDENTS_TASK_NAME), false,
+ JavaBasePlugin.BUILD_DEPENDENTS_TASK_NAME, "compile");
+ }
+
+ /**
+ * Adds a dependency on tasks with the specified name in other projects. The other projects
+ * are determined from project lib dependencies using the specified configuration name.
+ * These may be projects this project depends on or projects that depend on this project
+ * based on the useDependOn argument.
+ *
+ * @param task Task to add dependencies to
+ * @param useDependedOn if true, add tasks from projects this project depends on, otherwise
+ * use projects that depend on this one.
+ * @param otherProjectTaskName name of task in other projects
+ * @param configurationName name of configuration to use to find the other projects
+ */
+ private static void addDependsOnTaskInOtherProjects(final Task task, boolean useDependedOn,
+ String otherProjectTaskName,
+ String configurationName) {
+ Project project = task.getProject();
+ final Configuration configuration = project.getConfigurations().getByName(
+ configurationName);
+ task.dependsOn(configuration.getTaskDependencyFromProjectDependency(
+ useDependedOn, otherProjectTaskName));
+ }
+
+ //----------------------------------------------------------------------------------------------
+ //------------------------------- END DEPENDENCY STUFF -----------------------------------------
+ //----------------------------------------------------------------------------------------------
+
+ protected static File getOptionalDir(File dir) {
+ if (dir.isDirectory()) {
+ return dir
+ }
+
+ return null
+ }
+
+ @NonNull
+ protected List<ManifestDependencyImpl> getManifestDependencies(
+ List<LibraryDependency> libraries) {
+
+ List<ManifestDependencyImpl> list = Lists.newArrayListWithCapacity(libraries.size())
+
+ for (LibraryDependency lib : libraries) {
+ // get the dependencies
+ List<ManifestDependencyImpl> children = getManifestDependencies(lib.dependencies)
+ list.add(new ManifestDependencyImpl(lib.manifest, children))
+ }
+
+ return list
+ }
+
+ @NonNull
+ protected static List<SymbolFileProviderImpl> getTextSymbolDependencies(
+ List<LibraryDependency> libraries) {
+
+ List<SymbolFileProviderImpl> list = Lists.newArrayListWithCapacity(libraries.size())
+
+ for (LibraryDependency lib : libraries) {
+ list.add(new SymbolFileProviderImpl(lib.manifest, lib.symbolFile))
+ }
+
+ return list
+ }
+
+ private static String getLocalVersion() {
+ try {
+ Class clazz = BasePlugin.class
+ String className = clazz.getSimpleName() + ".class"
+ String classPath = clazz.getResource(className).toString()
+ if (!classPath.startsWith("jar")) {
+ // Class not from JAR, unlikely
+ return null
+ }
+ String manifestPath = classPath.substring(0, classPath.lastIndexOf("!") + 1) +
+ "/META-INF/MANIFEST.MF";
+ Manifest manifest = new Manifest(new URL(manifestPath).openStream());
+ Attributes attr = manifest.getMainAttributes();
+ return attr.getValue("Plugin-Version");
+ } catch (Throwable t) {
+ return null;
+ }
+ }
+
+ public Project getProject() {
+ return project
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/LibraryExtension.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/LibraryExtension.groovy
new file mode 100644
index 0000000..e1cf6a3
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/LibraryExtension.groovy
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle
+
+import com.android.build.gradle.api.LibraryVariant
+import com.android.build.gradle.internal.dsl.BuildTypeDsl
+import com.android.build.gradle.internal.dsl.SigningConfigDsl
+import com.android.builder.BuilderConstants
+import com.android.builder.DefaultBuildType
+import com.android.builder.model.SigningConfig
+import org.gradle.api.Action
+import org.gradle.api.internal.DefaultDomainObjectSet
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.internal.reflect.Instantiator
+
+/**
+ * Extension for 'library' project.
+ */
+public class LibraryExtension extends BaseExtension {
+
+ final DefaultBuildType debug
+ final DefaultBuildType release
+ final SigningConfig debugSigningConfig
+
+ private final DefaultDomainObjectSet<LibraryVariant> libraryVariantList =
+ new DefaultDomainObjectSet<LibraryVariant>(LibraryVariant.class)
+
+ LibraryExtension(BasePlugin plugin, ProjectInternal project, Instantiator instantiator) {
+ super(plugin, project, instantiator)
+
+ debugSigningConfig = instantiator.newInstance(SigningConfigDsl.class,
+ BuilderConstants.DEBUG)
+ debugSigningConfig.initDebug()
+
+ debug = instantiator.newInstance(BuildTypeDsl.class,
+ BuilderConstants.DEBUG, project.fileResolver, instantiator)
+ debug.init(debugSigningConfig)
+
+ release = instantiator.newInstance(BuildTypeDsl.class,
+ BuilderConstants.RELEASE, project.fileResolver, instantiator)
+ release.init(null)
+ }
+
+ void debug(Action<DefaultBuildType> action) {
+ action.execute(debug);
+ }
+
+ void release(Action<DefaultBuildType> action) {
+ action.execute(release);
+ }
+
+ void debugSigningConfig(Action<SigningConfig> action) {
+ action.execute(debugSigningConfig)
+ }
+
+ public DefaultDomainObjectSet<LibraryVariant> getLibraryVariants() {
+ return libraryVariantList
+ }
+
+ void addLibraryVariant(LibraryVariant libraryVariant) {
+ libraryVariantList.add(libraryVariant)
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/LibraryPlugin.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/LibraryPlugin.groovy
new file mode 100644
index 0000000..96f4654
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/LibraryPlugin.groovy
@@ -0,0 +1,451 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle
+import com.android.SdkConstants
+import com.android.annotations.NonNull
+import com.android.annotations.Nullable
+import com.android.build.gradle.api.AndroidSourceSet
+import com.android.build.gradle.api.BaseVariant
+import com.android.build.gradle.internal.BuildTypeData
+import com.android.build.gradle.internal.ProductFlavorData
+import com.android.build.gradle.internal.api.DefaultAndroidSourceSet
+import com.android.build.gradle.internal.api.LibraryVariantImpl
+import com.android.build.gradle.internal.api.TestVariantImpl
+import com.android.build.gradle.internal.dependency.VariantDependencies
+import com.android.build.gradle.internal.tasks.MergeFileTask
+import com.android.build.gradle.internal.variant.BaseVariantData
+import com.android.build.gradle.internal.variant.LibraryVariantData
+import com.android.build.gradle.internal.variant.TestVariantData
+import com.android.build.gradle.tasks.MergeResources
+import com.android.builder.BuilderConstants
+import com.android.builder.DefaultBuildType
+import com.android.builder.VariantConfiguration
+import com.android.builder.dependency.DependencyContainer
+import com.android.builder.dependency.JarDependency
+import com.android.builder.dependency.LibraryBundle
+import com.android.builder.dependency.LibraryDependency
+import com.android.builder.dependency.ManifestDependency
+import com.android.builder.model.AndroidLibrary
+import com.google.common.collect.Maps
+import com.google.common.collect.Sets
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.plugins.MavenPlugin
+import org.gradle.api.tasks.Copy
+import org.gradle.api.tasks.Sync
+import org.gradle.api.tasks.bundling.Jar
+import org.gradle.api.tasks.bundling.Zip
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.tooling.BuildException
+import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
+
+import javax.inject.Inject
+/**
+ * Gradle plugin class for 'library' projects.
+ */
+public class LibraryPlugin extends BasePlugin implements Plugin<Project> {
+
+ LibraryExtension extension
+ BuildTypeData debugBuildTypeData
+ BuildTypeData releaseBuildTypeData
+
+ @Inject
+ public LibraryPlugin(Instantiator instantiator, ToolingModelBuilderRegistry registry) {
+ super(instantiator, registry)
+ }
+
+ @Override
+ public LibraryExtension getExtension() {
+ return extension
+ }
+
+ @Override
+ void apply(Project project) {
+ super.apply(project)
+
+ extension = project.extensions.create('android', LibraryExtension,
+ this, (ProjectInternal) project, instantiator)
+ setBaseExtension(extension);
+
+ // create the source sets for the build type.
+ // the ones for the main product flavors are handled by the base plugin.
+ DefaultAndroidSourceSet debugSourceSet =
+ (DefaultAndroidSourceSet) extension.sourceSetsContainer.maybeCreate(BuilderConstants.DEBUG)
+ DefaultAndroidSourceSet releaseSourceSet =
+ (DefaultAndroidSourceSet) extension.sourceSetsContainer.maybeCreate(BuilderConstants.RELEASE)
+
+ debugBuildTypeData = new BuildTypeData(extension.debug, debugSourceSet, project)
+ releaseBuildTypeData = new BuildTypeData(extension.release, releaseSourceSet, project)
+ project.tasks.assemble.dependsOn debugBuildTypeData.assembleTask
+ project.tasks.assemble.dependsOn releaseBuildTypeData.assembleTask
+
+ createConfigurations(releaseSourceSet)
+ }
+
+ void createConfigurations(AndroidSourceSet releaseSourceSet) {
+ // The library artifact is published for the "default" configuration so we make
+ // sure "default" extends from the actual configuration used for building.
+ project.configurations["default"].extendsFrom(
+ project.configurations[mainSourceSet.getPackageConfigurationName()])
+ project.configurations["default"].extendsFrom(
+ project.configurations[releaseSourceSet.getPackageConfigurationName()])
+
+ project.plugins.withType(MavenPlugin) {
+ project.conf2ScopeMappings.addMapping(300,
+ project.configurations[mainSourceSet.getCompileConfigurationName()],
+ "compile")
+ project.conf2ScopeMappings.addMapping(300,
+ project.configurations[releaseSourceSet.getCompileConfigurationName()],
+ "compile")
+ // TODO -- figure out the package configuration
+// project.conf2ScopeMappings.addMapping(300,
+// project.configurations[mainSourceSet.getPackageConfigurationName()],
+// "runtime")
+// project.conf2ScopeMappings.addMapping(300,
+// project.configurations[releaseSourceSet.getPackageConfigurationName()],
+// "runtime")
+ }
+ }
+
+ @Override
+ protected void doCreateAndroidTasks() {
+ ProductFlavorData defaultConfigData = getDefaultConfigData()
+ VariantDependencies variantDep
+
+ LibraryVariantData debugVariantData = createLibVariant(defaultConfigData,
+ debugBuildTypeData)
+ LibraryVariantData releaseVariantData = createLibVariant(defaultConfigData,
+ releaseBuildTypeData)
+
+ // Add a compile lint task before library is bundled
+ createLintCompileTask()
+
+ // Need to create the tasks for these before doing the test variant as it
+ // references the debug variant and its output
+ createLibraryVariant(debugVariantData, false)
+ createLibraryVariant(releaseVariantData, true)
+
+ VariantConfiguration testVariantConfig = new VariantConfiguration(
+ defaultConfigData.productFlavor,
+ defaultConfigData.testSourceSet,
+ debugBuildTypeData.buildType,
+ null,
+ VariantConfiguration.Type.TEST,
+ debugVariantData.variantConfiguration)
+
+ TestVariantData testVariantData = new TestVariantData(testVariantConfig, debugVariantData)
+ // link the testVariant to the tested variant in the other direction
+ debugVariantData.setTestVariantData(testVariantData);
+
+ // dependencies for the test variant
+ variantDep = VariantDependencies.compute(project,
+ testVariantData.variantConfiguration.fullName,
+ defaultConfigData.testProvider, debugVariantData.variantDependency)
+ testVariantData.setVariantDependency(variantDep)
+
+ variantDataList.add(testVariantData)
+
+ createTestVariant(testVariantData, debugVariantData)
+
+ // create the lint tasks.
+ createLintTasks()
+
+ // create the test tasks.
+ createCheckTasks(false /*hasFlavors*/, true /*isLibrary*/)
+
+ // Create the variant API objects after the tasks have been created!
+ createApiObjects()
+ }
+
+ protected LibraryVariantData createLibVariant(@NonNull ProductFlavorData configData,
+ @NonNull BuildTypeData buildTypeData) {
+ VariantConfiguration variantConfig = new VariantConfiguration(
+ configData.productFlavor,
+ configData.sourceSet,
+ buildTypeData.buildType,
+ buildTypeData.sourceSet,
+ VariantConfiguration.Type.LIBRARY)
+
+ LibraryVariantData variantData = new LibraryVariantData(variantConfig)
+
+ VariantDependencies debugVariantDep = VariantDependencies.compute(
+ project, variantData.variantConfiguration.fullName,
+ buildTypeData, configData.mainProvider)
+ variantData.setVariantDependency(debugVariantDep)
+
+ variantDataList.add(variantData)
+
+ return variantData
+ }
+
+ private void createLibraryVariant(@NonNull LibraryVariantData variantData,
+ boolean publishArtifact) {
+ resolveDependencies(variantData.variantDependency)
+ variantData.variantConfiguration.setDependencies(variantData.variantDependency)
+
+ VariantConfiguration variantConfig = variantData.variantConfiguration
+ DefaultBuildType buildType = variantConfig.buildType
+
+ createAnchorTasks(variantData)
+
+ // Add a task to process the manifest(s)
+ createProcessManifestTask(variantData, DIR_BUNDLES)
+
+ // Add a task to compile renderscript files.
+ createRenderscriptTask(variantData)
+
+ // Add a task to merge the resource folders, including the libraries, in order to
+ // generate the R.txt file with all the symbols, including the ones from the dependencies.
+ createMergeResourcesTask(variantData, false /*process9Patch*/)
+
+ // Create another merge task to only merge the resources from this libraries and not
+ // the dependencies. This is what gets packaged in the aar.
+ MergeResources packageRes = basicCreateMergeResourcesTask(variantData,
+ "package",
+ "$project.buildDir/$DIR_BUNDLES/${variantData.variantConfiguration.dirName}/res",
+ false /*includeDependencies*/,
+ false /*process9Patch*/)
+
+ // Add a task to merge the assets folders
+ createMergeAssetsTask(variantData,
+ "$project.buildDir/$DIR_BUNDLES/${variantData.variantConfiguration.dirName}/assets",
+ false /*includeDependencies*/)
+
+ // Add a task to create the BuildConfig class
+ createBuildConfigTask(variantData)
+
+ // Add a task to generate resource source files, directing the location
+ // of the r.txt file to be directly in the bundle.
+ createProcessResTask(variantData,
+ "$project.buildDir/$DIR_BUNDLES/${variantData.variantConfiguration.dirName}")
+
+ // process java resources
+ createProcessJavaResTask(variantData)
+
+ createAidlTask(variantData)
+
+ // Add a compile task
+ createCompileTask(variantData, null/*testedVariant*/)
+
+ // Add NDK tasks
+ createNdkTasks(
+ variantData,
+ { project.file("$project.buildDir/$DIR_BUNDLES/${variantData.variantConfiguration.dirName}/jni") });
+
+ // package the aidl files into the bundle folder
+ Sync packageAidl = project.tasks.create(
+ "package${variantData.variantConfiguration.fullName.capitalize()}Aidl",
+ Sync)
+ // packageAidl from 3 sources. the order is important to make sure the override works well.
+ packageAidl.from(variantConfig.aidlSourceList).include("**/*.aidl")
+ packageAidl.into(project.file(
+ "$project.buildDir/$DIR_BUNDLES/${variantData.variantConfiguration.dirName}/$SdkConstants.FD_AIDL"))
+
+ // package the renderscript header files files into the bundle folder
+ Sync packageRenderscript = project.tasks.create(
+ "package${variantData.variantConfiguration.fullName.capitalize()}Renderscript",
+ Sync)
+ // package from 3 sources. the order is important to make sure the override works well.
+ packageRenderscript.from(variantConfig.renderscriptSourceList).include("**/*.rsh")
+ packageRenderscript.into(project.file(
+ "$project.buildDir/$DIR_BUNDLES/${variantData.variantConfiguration.dirName}/$SdkConstants.FD_RENDERSCRIPT"))
+
+ // merge consumer proguard files from different build types and flavors
+ MergeFileTask mergeProGuardFileTask = project.tasks.create(
+ "merge${variantData.variantConfiguration.fullName.capitalize()}ProguardFiles",
+ MergeFileTask)
+ mergeProGuardFileTask.conventionMapping.inputFiles = {
+ project.files(variantConfig.getConsumerProguardFiles()).files }
+ mergeProGuardFileTask.conventionMapping.outputFile = {
+ project.file(
+ "$project.buildDir/$DIR_BUNDLES/${variantData.variantConfiguration.dirName}/$LibraryBundle.FN_PROGUARD_TXT")
+ }
+
+ // copy lint.jar into the bundle folder
+ Copy lintCopy = project.tasks.create(
+ "copy${variantData.variantConfiguration.fullName.capitalize()}Lint",
+ Copy)
+ lintCopy.dependsOn lintCompile
+ lintCopy.from("$project.buildDir/lint/lint.jar")
+ lintCopy.into("$project.buildDir/$DIR_BUNDLES/$variantData.variantConfiguration.dirName")
+
+ Zip bundle = project.tasks.create(
+ "bundle${variantData.variantConfiguration.fullName.capitalize()}",
+ Zip)
+
+ if (variantConfig.buildType.runProguard) {
+ // run proguard on output of compile task
+ createProguardTasks(variantData, null)
+
+ // hack since bundle can't depend on variantData.proguardTask
+ mergeProGuardFileTask.dependsOn variantData.proguardTask
+
+ bundle.dependsOn packageRes, packageAidl, packageRenderscript, mergeProGuardFileTask, lintCopy, variantData.ndkCompileTask
+ } else {
+ Sync packageLocalJar = project.tasks.create(
+ "package${variantData.variantConfiguration.fullName.capitalize()}LocalJar",
+ Sync)
+ packageLocalJar.from(getLocalJarFileList(variantData.variantDependency))
+ packageLocalJar.into(project.file(
+ "$project.buildDir/$DIR_BUNDLES/${variantData.variantConfiguration.dirName}/$SdkConstants.LIBS_FOLDER"))
+
+ // jar the classes.
+ Jar jar = project.tasks.create("package${buildType.name.capitalize()}Jar", Jar);
+ jar.dependsOn variantData.javaCompileTask, variantData.processJavaResourcesTask
+ jar.from(variantData.javaCompileTask.outputs);
+ jar.from(variantData.processJavaResourcesTask.destinationDir)
+
+ jar.destinationDir = project.file(
+ "$project.buildDir/$DIR_BUNDLES/${variantData.variantConfiguration.dirName}")
+ jar.archiveName = "classes.jar"
+
+ String packageName = variantConfig.getPackageFromManifest()
+ if (packageName == null) {
+ throw new BuildException("Failed to read manifest", null)
+ }
+ packageName = packageName.replace('.', '/');
+
+ jar.exclude(packageName + "/R.class")
+ jar.exclude(packageName + "/R\$*.class")
+ jar.exclude(packageName + "/Manifest.class")
+ jar.exclude(packageName + "/Manifest\$*.class")
+ jar.exclude(packageName + "/BuildConfig.class")
+
+ bundle.dependsOn packageRes, jar, packageAidl, packageRenderscript, packageLocalJar,
+ mergeProGuardFileTask, lintCopy, variantData.ndkCompileTask
+ }
+
+ bundle.setDescription("Assembles a bundle containing the library in ${variantData.variantConfiguration.fullName.capitalize()}.");
+ bundle.destinationDir = project.file("$project.buildDir/libs")
+ bundle.extension = BuilderConstants.EXT_LIB_ARCHIVE
+ if (variantData.variantConfiguration.baseName != BuilderConstants.RELEASE) {
+ bundle.classifier = variantData.variantConfiguration.baseName
+ }
+ bundle.from(project.file("$project.buildDir/$DIR_BUNDLES/${variantData.variantConfiguration.dirName}"))
+
+ variantData.packageLibTask = bundle
+ variantData.outputFile = bundle.archivePath
+
+ if (publishArtifact) {
+ // add the artifact that will be published
+ project.artifacts.add("default", bundle)
+ releaseBuildTypeData.assembleTask.dependsOn bundle
+ } else {
+ debugBuildTypeData.assembleTask.dependsOn bundle
+ }
+
+ variantData.assembleTask = bundle
+
+ // configure the variant to be testable.
+ variantConfig.output = new LibraryBundle(
+ bundle.archivePath,
+ project.file("$project.buildDir/$DIR_BUNDLES/${variantData.variantConfiguration.dirName}"),
+ variantData.getName()) {
+
+ @Nullable
+ @Override
+ String getProject() {
+ return LibraryPlugin.this.project.path
+ }
+
+ @NonNull
+ @Override
+ List<LibraryDependency> getDependencies() {
+ return variantConfig.directLibraries
+ }
+
+ @NonNull
+ @Override
+ List<? extends AndroidLibrary> getLibraryDependencies() {
+ return variantConfig.directLibraries
+ }
+
+ @NonNull
+ @Override
+ List<ManifestDependency> getManifestDependencies() {
+ return variantConfig.directLibraries
+ }
+ };
+ }
+
+ static Object[] getLocalJarFileList(DependencyContainer dependencyContainer) {
+ Set<File> files = Sets.newHashSet()
+ for (JarDependency jarDependency : dependencyContainer.localDependencies) {
+ files.add(jarDependency.jarFile)
+ }
+
+ return files.toArray()
+ }
+
+ private void createTestVariant(@NonNull TestVariantData testVariantData,
+ @NonNull LibraryVariantData testedVariantData) {
+
+ resolveDependencies(testVariantData.variantDependency)
+ testVariantData.variantConfiguration.setDependencies(testVariantData.variantDependency)
+
+ createTestApkTasks(testVariantData, testedVariantData)
+ }
+
+ protected void createApiObjects() {
+ // we always want to have the test/tested objects created at the same time
+ // so that dynamic closure call on add can have referenced objects created.
+ // This means some objects are created before they are processed from the loop,
+ // so we store whether we have processed them or not.
+ Map<BaseVariantData, BaseVariant> map = Maps.newHashMap()
+ for (BaseVariantData variantData : variantDataList) {
+ if (map.get(variantData) != null) {
+ continue
+ }
+
+ if (variantData instanceof LibraryVariantData) {
+ LibraryVariantData libVariantData = (LibraryVariantData) variantData
+ createVariantApiObjects(map, libVariantData, libVariantData.testVariantData)
+
+ } else if (variantData instanceof TestVariantData) {
+ TestVariantData testVariantData = (TestVariantData) variantData
+ createVariantApiObjects(map,
+ (LibraryVariantData) testVariantData.testedVariantData,
+ testVariantData)
+ }
+ }
+ }
+
+ private void createVariantApiObjects(@NonNull Map<BaseVariantData, BaseVariant> map,
+ @NonNull LibraryVariantData libVariantData,
+ @Nullable TestVariantData testVariantData) {
+ LibraryVariantImpl libVariant = instantiator.newInstance(
+ LibraryVariantImpl.class, libVariantData)
+
+ TestVariantImpl testVariant = null;
+ if (testVariantData != null) {
+ testVariant = instantiator.newInstance(TestVariantImpl.class, testVariantData)
+ }
+
+ if (libVariant != null && testVariant != null) {
+ libVariant.setTestVariant(testVariant)
+ testVariant.setTestedVariant(libVariant)
+ }
+
+ extension.addLibraryVariant(libVariant)
+ map.put(libVariantData, libVariant)
+
+ if (testVariant != null) {
+ extension.addTestVariant(testVariant)
+ map.put(testVariantData, testVariant)
+ }
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/ReportingPlugin.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/ReportingPlugin.groovy
new file mode 100644
index 0000000..e9ddf50
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/ReportingPlugin.groovy
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle
+import com.android.build.gradle.internal.tasks.AndroidReportTask
+import com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestLibraryTask
+import com.android.build.gradle.internal.test.TestOptions
+import com.android.build.gradle.internal.test.report.ReportType
+import org.gradle.api.Project
+import org.gradle.api.plugins.JavaBasePlugin
+import org.gradle.api.tasks.TaskCollection
+
+import static com.android.builder.BuilderConstants.REPORTS
+import static com.android.builder.BuilderConstants.FD_INSTRUMENT_RESULTS
+import static com.android.builder.BuilderConstants.INSTRUMENTATION_TESTS
+/**
+ * Gradle plugin class for 'reporting' projects.
+ *
+ * This is mostly used to aggregate reports from subprojects.
+ *
+ */
+class ReportingPlugin implements org.gradle.api.Plugin<Project> {
+
+ private TestOptions extension
+
+ @Override
+ void apply(Project project) {
+ // make sure this project depends on the evaluation of all sub projects so that
+ // it's evaluated last.
+ project.evaluationDependsOnChildren()
+
+ extension = project.extensions.create('android', TestOptions)
+
+ AndroidReportTask mergeReportsTask = project.tasks.create("mergeAndroidReports",
+ AndroidReportTask)
+ mergeReportsTask.group = JavaBasePlugin.VERIFICATION_GROUP
+ mergeReportsTask.description = "Merges all the Android test reports from the sub projects."
+ mergeReportsTask.reportType = ReportType.MULTI_PROJECT
+
+ mergeReportsTask.conventionMapping.resultsDir = {
+ String location = extension.resultsDir != null ?
+ extension.resultsDir : "$project.buildDir/$FD_INSTRUMENT_RESULTS"
+
+ project.file(location)
+ }
+ mergeReportsTask.conventionMapping.reportsDir = {
+ String location = extension.reportDir != null ?
+ extension.reportDir : "$project.buildDir/$REPORTS/$INSTRUMENTATION_TESTS"
+
+ project.file(location)
+ }
+
+ // gather the subprojects
+ project.afterEvaluate {
+ project.subprojects.each { p ->
+ TaskCollection<AndroidReportTask> tasks = p.tasks.withType(AndroidReportTask)
+ for (AndroidReportTask task : tasks) {
+ mergeReportsTask.addTask(task)
+ }
+ TaskCollection<DeviceProviderInstrumentTestLibraryTask> tasks2 = p.tasks.withType(DeviceProviderInstrumentTestLibraryTask)
+ for (DeviceProviderInstrumentTestLibraryTask task : tasks2) {
+ mergeReportsTask.addTask(task)
+ }
+ }
+ }
+
+ // If gradle is launched with --continue, we want to run all tests and generate an
+ // aggregate report (to help with the fact that we may have several build variants).
+ // To do that, the "mergeAndroidReports" task (which does the aggregation) must always
+ // run even if one of its dependent task (all the testFlavor tasks) fails, so we make
+ // them ignore their error.
+ // We cannot do that always: in case the test task is not going to run, we do want the
+ // individual testFlavor tasks to fail.
+ if (mergeReportsTask != null && project.gradle.startParameter.continueOnFailure) {
+ project.gradle.taskGraph.whenReady { taskGraph ->
+ if (taskGraph.hasTask(mergeReportsTask)) {
+ mergeReportsTask.setWillRun()
+ }
+ }
+ }
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/api/AndroidSourceDirectorySet.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/api/AndroidSourceDirectorySet.java
new file mode 100644
index 0000000..1c37913
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/api/AndroidSourceDirectorySet.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.api;
+
+import com.android.annotations.NonNull;
+
+import java.io.File;
+import java.util.Set;
+
+/**
+ * An AndroidSourceDirectorySet represents a lit of directory input for an Android project.
+ */
+public interface AndroidSourceDirectorySet {
+
+ /**
+ * A concise name for the source directory (typically used to identify it in a collection).
+ */
+ @NonNull
+ String getName();
+
+ /**
+ * Adds the given source directory to this set.
+ *
+ * @param srcDir The source directory. This is evaluated as for
+ * {@link org.gradle.api.Project#file(Object)}
+ * @return this
+ */
+ @NonNull
+ AndroidSourceDirectorySet srcDir(Object srcDir);
+
+ /**
+ * Adds the given source directories to this set.
+ *
+ * @param srcDirs The source directories. These are evaluated as for
+ * {@link org.gradle.api.Project#files(Object...)}
+ * @return this
+ */
+ @NonNull
+ AndroidSourceDirectorySet srcDirs(Object... srcDirs);
+
+ /**
+ * Sets the source directories for this set.
+ *
+ * @param srcDirs The source directories. These are evaluated as for
+ * {@link org.gradle.api.Project#files(Object...)}
+ * @return this
+ */
+ @NonNull
+ AndroidSourceDirectorySet setSrcDirs(Iterable<?> srcDirs);
+
+ /**
+ * Returns the resolved directories.
+ * @return a non null set of File objects.
+ */
+ @NonNull
+ Set<File> getSrcDirs();
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/api/AndroidSourceFile.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/api/AndroidSourceFile.java
new file mode 100644
index 0000000..b6bc97a
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/api/AndroidSourceFile.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.api;
+
+import java.io.File;
+
+/**
+ * An AndroidSourceFile represents a single file input for an Android project.
+ */
+public interface AndroidSourceFile {
+
+ /**
+ * A concise name for the source directory (typically used to identify it in a collection).
+ */
+ String getName();
+
+ /**
+ * Returns the file.
+ * @return the file input.
+ */
+ File getSrcFile();
+
+ /**
+ * Sets the location of the file.
+ *
+ * @param srcPath The source directory. This is evaluated as for
+ * {@link org.gradle.api.Project#file(Object)}
+ * @return the AndroidSourceFile object
+ */
+ AndroidSourceFile srcFile(Object srcPath);
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/api/AndroidSourceSet.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/api/AndroidSourceSet.groovy
new file mode 100644
index 0000000..72f58d7
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/api/AndroidSourceSet.groovy
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.api
+
+import com.android.annotations.NonNull
+import org.gradle.api.file.SourceDirectorySet
+
+/**
+ * A {@code AndroidSourceSet} represents a logical group of Java, aidl, renderscript source
+ * as well as Android and non-Android resources.
+ */
+public interface AndroidSourceSet {
+
+ /**
+ * Returns the name of this source set.
+ *
+ * @return The name. Never returns null.
+ */
+ @NonNull
+ String getName();
+
+ /**
+ * Returns the Java resources which are to be copied into the javaResources output directory.
+ *
+ * @return the java resources. Never returns null.
+ */
+ @NonNull
+ SourceDirectorySet getResources();
+
+ /**
+ * Configures the Java resources for this set.
+ *
+ * <p>The given closure is used to configure the {@link SourceDirectorySet} which contains the
+ * java resources.
+ *
+ * @param configureClosure The closure to use to configure the javaResources.
+ * @return this
+ */
+ @NonNull
+ AndroidSourceSet resources(Closure configureClosure);
+
+ /**
+ * Returns the Java source which is to be compiled by the Java compiler into the class output
+ * directory.
+ *
+ * @return the Java source. Never returns null.
+ */
+ @NonNull
+ SourceDirectorySet getJava();
+
+ /**
+ * Configures the Java source for this set.
+ *
+ * <p>The given closure is used to configure the {@link SourceDirectorySet} which contains the
+ * Java source.
+ *
+ * @param configureClosure The closure to use to configure the Java source.
+ * @return this
+ */
+ @NonNull
+ AndroidSourceSet java(Closure configureClosure);
+
+ /**
+ * All Java source files for this source set. This includes, for example, source which is
+ * directly compiled, and source which is indirectly compiled through joint compilation.
+ *
+ * @return the Java source. Never returns null.
+ */
+ @NonNull
+ SourceDirectorySet getAllJava();
+
+ /**
+ * All source files for this source set.
+ *
+ * @return the source. Never returns null.
+ */
+ @NonNull
+ SourceDirectorySet getAllSource();
+
+ /**
+ * Returns the name of the compile configuration for this source set.
+ * @return The configuration name
+ */
+ @NonNull
+ String getCompileConfigurationName();
+
+ /**
+ * Returns the name of the runtime configuration for this source set.
+ * @return The runtime configuration name
+ */
+ @NonNull
+ String getPackageConfigurationName();
+
+ /**
+ * The Android Manifest file for this source set.
+ *
+ * @return the manifest. Never returns null.
+ */
+ @NonNull
+ AndroidSourceFile getManifest();
+
+ /**
+ * Configures the location of the Android Manifest for this set.
+ *
+ * <p>The given closure is used to configure the {@link AndroidSourceFile} which contains the
+ * manifest.
+ *
+ * @param configureClosure The closure to use to configure the Android Manifest.
+ * @return this
+ */
+ @NonNull
+ AndroidSourceSet manifest(Closure configureClosure);
+
+ /**
+ * The Android Resources directory for this source set.
+ *
+ * @return the resources. Never returns null.
+ */
+ @NonNull
+ AndroidSourceDirectorySet getRes();
+
+ /**
+ * Configures the location of the Android Resources for this set.
+ *
+ * <p>The given closure is used to configure the {@link AndroidSourceDirectorySet}
+ * which contains the resources.
+ *
+ * @param configureClosure The closure to use to configure the Resources.
+ * @return this
+ */
+ @NonNull
+ AndroidSourceSet res(Closure configureClosure);
+
+ /**
+ * The Android Assets directory for this source set.
+ *
+ * @return the assets. Never returns null.
+ */
+ @NonNull
+ AndroidSourceDirectorySet getAssets();
+
+ /**
+ * Configures the location of the Android Assets for this set.
+ *
+ * <p>The given closure is used to configure the {@link AndroidSourceDirectorySet}
+ * which contains the assets.
+ *
+ * @param configureClosure The closure to use to configure the Assets.
+ * @return this
+ */
+ @NonNull
+ AndroidSourceSet assets(Closure configureClosure);
+
+ /**
+ * The Android AIDL source directory for this source set.
+ *
+ * @return the source. Never returns null.
+ */
+ @NonNull
+ AndroidSourceDirectorySet getAidl();
+
+ /**
+ * Configures the location of the Android AIDL source for this set.
+ *
+ * <p>The given closure is used to configure the {@link AndroidSourceDirectorySet}
+ * which contains the AIDL source.
+ *
+ * @param configureClosure The closure to use to configure the AIDL source.
+ * @return this
+ */
+ @NonNull
+ AndroidSourceSet aidl(Closure configureClosure);
+
+ /**
+ * The Android Renderscript source directory for this source set.
+ *
+ * @return the source. Never returns null.
+ */
+ @NonNull
+ AndroidSourceDirectorySet getRenderscript();
+
+ /**
+ * Configures the location of the Android Renderscript source for this set.
+ *
+ * <p>The given closure is used to configure the {@link AndroidSourceDirectorySet}
+ * which contains the Renderscript source.
+ *
+ * @param configureClosure The closure to use to configure the Renderscript source.
+ * @return this
+ */
+ @NonNull
+ AndroidSourceSet renderscript(Closure configureClosure);
+
+ /**
+ * The Android JNI source directory for this source set.
+ *
+ * @return the source. Never returns null.
+ */
+ @NonNull
+ AndroidSourceDirectorySet getJni();
+
+ /**
+ * Configures the location of the Android JNI source for this set.
+ *
+ * <p>The given closure is used to configure the {@link AndroidSourceDirectorySet}
+ * which contains the JNI source.
+ *
+ * @param configureClosure The closure to use to configure the JNI source.
+ * @return this
+ */
+ @NonNull
+ AndroidSourceSet jni(Closure configureClosure);
+
+ /**
+ * Sets the root of the source sets to a given path.
+ *
+ * All entries of the source set are located under this root directory.
+ *
+ * @param path the root directory.
+ * @return this
+ */
+ @NonNull
+ AndroidSourceSet setRoot(String path);
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/api/ApkVariant.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/api/ApkVariant.java
new file mode 100644
index 0000000..404625e
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/api/ApkVariant.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.tasks.Dex;
+import com.android.build.gradle.tasks.PackageApplication;
+import com.android.build.gradle.tasks.ZipAlign;
+import com.android.builder.DefaultProductFlavor;
+import com.android.builder.model.SigningConfig;
+import org.gradle.api.DefaultTask;
+
+import java.util.List;
+
+/**
+ * A Build variant and all its public data.
+ */
+public interface ApkVariant extends BaseVariant {
+
+
+ /**
+ * Returns the list of {@link com.android.builder.DefaultProductFlavor} for this build variant.
+ *
+ * This is always non-null but could be empty.
+ */
+ @NonNull
+ List<DefaultProductFlavor> getProductFlavors();
+
+ /**
+ * Returns a {@link com.android.builder.DefaultProductFlavor} that represents the merging
+ * of the default config and the flavors of this build variant.
+ */
+ @NonNull
+ DefaultProductFlavor getMergedFlavor();
+
+ /**
+ * Returns the {@link SigningConfig} for this build variant,
+ * if one has been specified.
+ */
+ @Nullable
+ SigningConfig getSigningConfig();
+
+ /**
+ * Returns true if this variant has the information it needs to create a signed APK.
+ */
+ boolean isSigningReady();
+
+ /**
+ * Returns the Dex task.
+ */
+ @Nullable
+ Dex getDex();
+
+ /**
+ * Returns the APK packaging task.
+ */
+ @Nullable
+ PackageApplication getPackageApplication();
+
+ /**
+ * Returns the Zip align task.
+ */
+ @Nullable
+ ZipAlign getZipAlign();
+
+ /**
+ * Returns the installation task.
+ *
+ * Even for variant for regular project, this can be null if the app cannot be signed.
+ */
+ @Nullable
+ DefaultTask getInstall();
+
+ /**
+ * Returns the uinstallation task.
+ *
+ * For non-library project this is always true even if the APK is not created because
+ * signing isn't setup.
+ */
+ @Nullable
+ DefaultTask getUninstall();
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/api/ApplicationVariant.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/api/ApplicationVariant.java
new file mode 100644
index 0000000..d5caf5b
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/api/ApplicationVariant.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.api;
+
+import com.android.annotations.Nullable;
+
+/**
+ * A Build variant and all its public data.
+ */
+public interface ApplicationVariant extends ApkVariant {
+
+ /**
+ * Returns the build variant that will test this build variant.
+ */
+ @Nullable
+ TestVariant getTestVariant();
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/api/BaseVariant.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/api/BaseVariant.java
new file mode 100644
index 0000000..4357456
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/api/BaseVariant.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.tasks.AidlCompile;
+import com.android.build.gradle.tasks.GenerateBuildConfig;
+import com.android.build.gradle.tasks.MergeAssets;
+import com.android.build.gradle.tasks.MergeResources;
+import com.android.build.gradle.tasks.ProcessAndroidResources;
+import com.android.build.gradle.tasks.ProcessManifest;
+import com.android.build.gradle.tasks.RenderscriptCompile;
+import com.android.builder.DefaultBuildType;
+import com.android.builder.DefaultProductFlavor;
+import org.gradle.api.Task;
+import org.gradle.api.tasks.Copy;
+import org.gradle.api.tasks.compile.JavaCompile;
+
+import java.io.File;
+import java.util.Collection;
+
+/**
+ * A Build variant and all its public data. This is the base class for items common to apps,
+ * test apps, and libraries
+ */
+public interface BaseVariant {
+
+ /**
+ * Returns the name of the variant. Guaranteed to be unique.
+ */
+ @NonNull
+ String getName();
+
+ /**
+ * Returns a description for the build variant.
+ */
+ @NonNull
+ String getDescription();
+
+ /**
+ * Returns a subfolder name for the variant. Guaranteed to be unique.
+ *
+ * This is usually a mix of build type and flavor(s) (if applicable).
+ * For instance this could be:
+ * "debug"
+ * "debug/myflavor"
+ * "release/Flavor1Flavor2"
+ */
+ @NonNull
+ String getDirName();
+
+ /**
+ * Returns the base name for the output of the variant. Guaranteed to be unique.
+ */
+ @NonNull
+ String getBaseName();
+
+ /**
+ * Returns the flavor name of the variant. This is a concatenation of all the
+ * applied flavors
+ * @return the name of the flavors, or an empty string if there is not flavors.
+ */
+ @NonNull
+ String getFlavorName();
+
+ /**
+ * Returns the {@link com.android.builder.DefaultBuildType} for this build variant.
+ */
+ @NonNull
+ DefaultBuildType getBuildType();
+
+ /**
+ * Returns a {@link com.android.builder.DefaultProductFlavor} that represents the merging
+ * of the default config and the flavors of this build variant.
+ */
+ @NonNull
+ DefaultProductFlavor getConfig();
+
+ /**
+ * Returns the output file for this build variants. Depending on the configuration, this could
+ * be an apk (regular and test project) or a bundled library (library project).
+ *
+ * If it's an apk, it could be signed, or not; zip-aligned, or not.
+ */
+ @NonNull
+ File getOutputFile();
+
+ void setOutputFile(@NonNull File outputFile);
+
+ /**
+ * Returns the Manifest processing task.
+ */
+ @NonNull
+ ProcessManifest getProcessManifest();
+
+ /**
+ * Returns the AIDL compilation task.
+ */
+ @NonNull
+ AidlCompile getAidlCompile();
+
+ /**
+ * Returns the Renderscript compilation task.
+ */
+ @NonNull
+ RenderscriptCompile getRenderscriptCompile();
+
+ /**
+ * Returns the resource merging task.
+ */
+ @Nullable
+ MergeResources getMergeResources();
+
+ /**
+ * Returns the asset merging task.
+ */
+ @Nullable
+ MergeAssets getMergeAssets();
+
+ /**
+ * Returns the Android Resources processing task.
+ */
+ @NonNull
+ ProcessAndroidResources getProcessResources();
+
+ /**
+ * Returns the BuildConfig generation task.
+ */
+ @Nullable
+ GenerateBuildConfig getGenerateBuildConfig();
+
+ /**
+ * Returns the Java Compilation task.
+ */
+ @NonNull
+ JavaCompile getJavaCompile();
+
+ /**
+ * Returns the Java resource processing task.
+ */
+ @NonNull
+ Copy getProcessJavaResources();
+
+ /**
+ * Returns the assemble task.
+ */
+ @Nullable
+ Task getAssemble();
+
+ /**
+ * Adds new Java source folders to the model.
+ *
+ * These source folders will not be used for the default build
+ * system, but will be passed along the default Java source folders
+ * to whoever queries the model.
+ *
+ * @param sourceFolders the source folders where the generated source code is.
+ */
+ void addJavaSourceFoldersToModel(@NonNull File... sourceFolders);
+
+ /**
+ * Adds new Java source folders to the model.
+ *
+ * These source folders will not be used for the default build
+ * system, but will be passed along the default Java source folders
+ * to whoever queries the model.
+ *
+ * @param sourceFolders the source folders where the generated source code is.
+ */
+ void addJavaSourceFoldersToModel(@NonNull Collection<File> sourceFolders);
+
+ /**
+ * Adds to the variant a task that generates Java source code.
+ *
+ * This will make the compileJava task depend on this task and add the
+ * new source folders as compilation inputs.
+ *
+ * The new source folders are also added to the model.
+ *
+ * @param task the task
+ * @param sourceFolders the source folders where the generated source code is.
+ */
+ void registerJavaGeneratingTask(@NonNull Task task, @NonNull File... sourceFolders);
+
+ /**
+ * Adds to the variant a task that generates Java source code.
+ *
+ * This will make the compileJava task depend on this task and add the
+ * new source folders as compilation inputs.
+ *
+ * The new source folders are also added to the model.
+ *
+ * @param task the task
+ * @param sourceFolders the source folders where the generated source code is.
+ */
+ void registerJavaGeneratingTask(@NonNull Task task, @NonNull Collection<File> sourceFolders);
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/api/LibraryVariant.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/api/LibraryVariant.java
new file mode 100644
index 0000000..8db077d
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/api/LibraryVariant.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.api;
+
+import com.android.annotations.Nullable;
+import org.gradle.api.tasks.bundling.Zip;
+
+/**
+ * A Build variant and all its public data.
+ */
+public interface LibraryVariant extends BaseVariant {
+
+ /**
+ * Returns the build variant that will test this build variant.
+ *
+ * Will return null if this build variant is a test build already.
+ */
+ @Nullable
+ TestVariant getTestVariant();
+
+ /**
+ * Returns the Library AAR packaging task.
+ */
+ @Nullable
+ Zip getPackageLibrary();
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/api/TestVariant.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/api/TestVariant.java
new file mode 100644
index 0000000..12a59f5
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/api/TestVariant.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import org.gradle.api.DefaultTask;
+
+import java.util.List;
+
+/**
+ * A Build variant and all its public data.
+ */
+public interface TestVariant extends ApkVariant {
+
+ /**
+ * Returns the build variant that is tested by this variant.
+ */
+ @NonNull
+ BaseVariant getTestedVariant();
+
+ /**
+ * Returns the task to run the tests.
+ * Only valid for test project.
+ */
+ @Nullable
+ DefaultTask getConnectedInstrumentTest();
+
+ /**
+ * Returns the task to run the tests.
+ * Only valid for test project.
+ */
+ @NonNull
+ List<? extends DefaultTask> getProviderInstrumentTests();
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/AndroidAsciiReportRenderer.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/AndroidAsciiReportRenderer.java
new file mode 100644
index 0000000..6704129
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/AndroidAsciiReportRenderer.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.builder.dependency.JarDependency;
+import com.android.builder.dependency.LibraryBundle;
+import com.android.builder.dependency.LibraryDependency;
+import org.gradle.api.Action;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.tasks.diagnostics.internal.TextReportRenderer;
+import org.gradle.internal.graph.GraphRenderer;
+import org.gradle.logging.StyledTextOutput;
+import org.gradle.util.GUtil;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+
+import static org.gradle.logging.StyledTextOutput.Style.Description;
+import static org.gradle.logging.StyledTextOutput.Style.Identifier;
+import static org.gradle.logging.StyledTextOutput.Style.Info;
+
+/**
+ * android version of the AsciiReportRenderer that outputs Android Library dependencies.
+ */
+public class AndroidAsciiReportRenderer extends TextReportRenderer {
+ private boolean hasConfigs;
+ private boolean hasCyclicDependencies;
+ private GraphRenderer renderer;
+
+ @Override
+ public void startProject(Project project) {
+ super.startProject(project);
+ hasConfigs = false;
+ hasCyclicDependencies = false;
+ }
+
+ @Override
+ public void completeProject(Project project) {
+ if (!hasConfigs) {
+ getTextOutput().withStyle(Info).println("No dependencies");
+ }
+ super.completeProject(project);
+ }
+
+ public void startVariant(final BaseVariantData variantData) {
+ if (hasConfigs) {
+ getTextOutput().println();
+ }
+ hasConfigs = true;
+ renderer = new GraphRenderer(getTextOutput());
+ renderer.visit(new Action<StyledTextOutput>() {
+ @Override
+ public void execute(StyledTextOutput styledTextOutput) {
+ getTextOutput().withStyle(Identifier).text(
+ variantData.getVariantConfiguration().getFullName());
+ getTextOutput().withStyle(Description).text("");
+ }
+ }, true);
+ }
+
+ private String getDescription(Configuration configuration) {
+ return GUtil.isTrue(
+ configuration.getDescription()) ? " - " + configuration.getDescription() : "";
+ }
+
+ public void completeConfiguration(BaseVariantData variantData) {}
+
+ public void render(BaseVariantData variantData) throws IOException {
+ List<LibraryDependency> libraries =
+ variantData.getVariantConfiguration().getDirectLibraries();
+
+ renderNow(libraries, variantData.getVariantDependency().getLocalDependencies());
+ }
+
+ void renderNow(@NonNull List<LibraryDependency> libraries,
+ @Nullable List<JarDependency> localJars) {
+ if (libraries.isEmpty() && (localJars == null || localJars.isEmpty())) {
+ getTextOutput().withStyle(Info).text("No dependencies");
+ getTextOutput().println();
+ return;
+ }
+
+ renderChildren(libraries, localJars);
+ }
+
+ @Override
+ public void complete() throws IOException {
+ if (hasCyclicDependencies) {
+ getTextOutput().withStyle(Info).println(
+ "\n(*) - dependencies omitted (listed previously)");
+ }
+
+ super.complete();
+ }
+
+ private void render(final LibraryDependency lib, boolean lastChild) {
+ renderer.visit(new Action<StyledTextOutput>() {
+ @Override
+ public void execute(StyledTextOutput styledTextOutput) {
+ getTextOutput().text(((LibraryBundle)lib).getName());
+ }
+ }, lastChild);
+
+ renderChildren(lib.getDependencies(), lib.getLocalDependencies());
+ }
+
+ private void render(final JarDependency jar, boolean lastChild) {
+ renderer.visit(new Action<StyledTextOutput>() {
+ @Override
+ public void execute(StyledTextOutput styledTextOutput) {
+ getTextOutput().text("LOCAL: " + jar.getJarFile().getName());
+ }
+ }, lastChild);
+ }
+
+ private void renderChildren(@NonNull List<LibraryDependency> libraries,
+ @Nullable Collection<JarDependency> localJars) {
+ renderer.startChildren();
+ if (localJars != null) {
+ final boolean emptyChildren = libraries.isEmpty();
+ final int count = localJars.size();
+
+ int i = 0;
+ for (JarDependency jarDependency : localJars) {
+ render(jarDependency, emptyChildren && i == count - 1);
+ i++;
+ }
+ }
+
+ final int count = libraries.size();
+ for (int i = 0; i < count; i++) {
+ LibraryDependency lib = libraries.get(i);
+ render(lib, i == count - 1);
+ }
+ renderer.completeChildren();
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/BadPluginException.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/BadPluginException.java
new file mode 100644
index 0000000..4ad6fc3
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/BadPluginException.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal;
+
+import com.android.annotations.NonNull;
+import org.gradle.api.GradleException;
+
+public class BadPluginException extends GradleException {
+
+ public BadPluginException(@NonNull String message) {
+ super(message);
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/BuildTypeData.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/BuildTypeData.groovy
new file mode 100644
index 0000000..4b11232
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/BuildTypeData.groovy
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal
+import com.android.annotations.NonNull
+import com.android.build.gradle.internal.api.DefaultAndroidSourceSet
+import com.android.builder.DefaultBuildType
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.artifacts.Configuration
+/**
+ * Class containing a BuildType and associated data (Sourceset for instance).
+ */
+class BuildTypeData implements ConfigurationProvider {
+
+ final DefaultBuildType buildType
+ final DefaultAndroidSourceSet sourceSet
+ private final Project project
+
+ final Task assembleTask
+
+ BuildTypeData(DefaultBuildType buildType, DefaultAndroidSourceSet sourceSet, Project project) {
+
+ this.buildType = buildType
+ this.sourceSet = sourceSet
+ this.project = project
+
+ assembleTask = project.tasks.create("assemble${buildType.name.capitalize()}")
+ assembleTask.description = "Assembles all ${buildType.name.capitalize()} builds"
+ assembleTask.group = "Build"
+ }
+
+ @Override
+ @NonNull
+ Configuration getCompileConfiguration() {
+ return project.configurations.getByName(sourceSet.compileConfigurationName)
+ }
+
+ @Override
+ @NonNull
+ Configuration getPackageConfiguration() {
+ return project.configurations.getByName(sourceSet.packageConfigurationName)
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/CompileOptions.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/CompileOptions.groovy
new file mode 100644
index 0000000..de70e31
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/CompileOptions.groovy
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal
+
+import org.gradle.api.JavaVersion
+
+/**
+ * Compilation options
+ */
+class CompileOptions {
+
+ JavaVersion sourceCompatibility = JavaVersion.VERSION_1_6
+ JavaVersion targetCompatibility = JavaVersion.VERSION_1_6
+ String encoding = "UTF-8"
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/ConfigurationProvider.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/ConfigurationProvider.java
new file mode 100644
index 0000000..2fc9c60
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/ConfigurationProvider.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal;
+
+import com.android.annotations.NonNull;
+import org.gradle.api.artifacts.Configuration;
+
+/**
+ * Provides the compile and package configuration.
+ */
+public interface ConfigurationProvider {
+
+ @NonNull
+ Configuration getCompileConfiguration();
+
+ @NonNull
+ Configuration getPackageConfiguration();
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/ConfigurationProviderImpl.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/ConfigurationProviderImpl.groovy
new file mode 100644
index 0000000..8b4e65a
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/ConfigurationProviderImpl.groovy
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal
+
+import com.android.annotations.NonNull
+import com.android.build.gradle.internal.api.DefaultAndroidSourceSet
+import org.gradle.api.Project
+import org.gradle.api.artifacts.Configuration
+
+/**
+ */
+public class ConfigurationProviderImpl implements ConfigurationProvider {
+
+ private final Project project
+ private final DefaultAndroidSourceSet sourceSet
+
+ public ConfigurationProviderImpl(Project project, DefaultAndroidSourceSet sourceSet) {
+ this.project = project
+ this.sourceSet = sourceSet
+ }
+
+ @Override
+ @NonNull
+ public Configuration getCompileConfiguration() {
+ return project.configurations.getByName(sourceSet.compileConfigurationName)
+ }
+
+ @Override
+ @NonNull
+ public Configuration getPackageConfiguration() {
+ return project.configurations.getByName(sourceSet.packageConfigurationName)
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/LintGradleClient.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/LintGradleClient.java
new file mode 100644
index 0000000..3bf7b8d
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/LintGradleClient.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.BasePlugin;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Variant;
+import com.android.tools.lint.LintCliClient;
+import com.android.tools.lint.LintCliFlags;
+import com.android.tools.lint.Warning;
+import com.android.tools.lint.client.api.IssueRegistry;
+import com.android.tools.lint.client.api.LintRequest;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Project;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+public class LintGradleClient extends LintCliClient {
+ private final AndroidProject mModelProject;
+ private final String mVariantName;
+ private final BasePlugin mPlugin;
+ private List<File> mCustomRules = Lists.newArrayList();
+
+ public LintGradleClient(
+ @NonNull IssueRegistry registry,
+ @NonNull LintCliFlags flags,
+ @NonNull BasePlugin plugin,
+ @NonNull AndroidProject modelProject,
+ @Nullable String variantName) {
+ super(flags);
+ mPlugin = plugin;
+ mModelProject = modelProject;
+ mVariantName = variantName;
+ mRegistry = registry;
+ }
+
+ @NonNull
+ public BasePlugin getPlugin() {
+ return mPlugin;
+ }
+
+ public void setCustomRules(List<File> customRules) {
+ mCustomRules = customRules;
+ }
+
+ @Override
+ public List<File> findRuleJars(@NonNull Project project) {
+ return mCustomRules;
+ }
+
+ @Override
+ protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+ // Should not be called by lint since we supply an explicit set of projects
+ // to the LintRequest
+ throw new IllegalStateException();
+ }
+
+ @Override
+ public File getSdkHome() {
+ File sdkHome = mPlugin.getSdkDirectory();
+ if (sdkHome != null) {
+ return sdkHome;
+ }
+ return super.getSdkHome();
+ }
+
+ @Override
+ @NonNull
+ protected LintRequest createLintRequest(@NonNull List<File> files) {
+ return new LintGradleRequest(this, mModelProject, mPlugin, mVariantName, files);
+ }
+
+ /** Run lint with the given registry and return the resulting warnings */
+ @NonNull
+ public List<Warning> run(@NonNull IssueRegistry registry) throws IOException {
+ run(registry, Collections.<File>emptyList());
+ return mWarnings;
+ }
+
+ /**
+ * Given a list of results from separate variants, merge them into a single
+ * list of warnings, and mark their
+ * @param warningMap a map from variant to corresponding warnings
+ * @param project the project model
+ * @return a merged list of issues
+ */
+ @NonNull
+ public static List<Warning> merge(
+ @NonNull Map<Variant,List<Warning>> warningMap,
+ @NonNull AndroidProject project) {
+ // Easy merge?
+ if (warningMap.size() == 1) {
+ return warningMap.values().iterator().next();
+ }
+ int maxCount = 0;
+ for (List<Warning> warnings : warningMap.values()) {
+ int size = warnings.size();
+ maxCount = Math.max(size, maxCount);
+ }
+ if (maxCount == 0) {
+ return Collections.emptyList();
+ }
+
+ int totalVariantCount = project.getVariants().size();
+
+ List<Warning> merged = Lists.newArrayListWithExpectedSize(2 * maxCount);
+
+ // Map fro issue to message to line number to file name to canonical warning
+ Map<Issue,Map<String, Map<Integer, Map<String, Warning>>>> map =
+ Maps.newHashMapWithExpectedSize(2 * maxCount);
+
+ for (Map.Entry<Variant,List<Warning>> entry : warningMap.entrySet()) {
+ Variant variant = entry.getKey();
+ List<Warning> warnings = entry.getValue();
+ for (Warning warning : warnings) {
+ Map<String,Map<Integer,Map<String,Warning>>> messageMap = map.get(warning.issue);
+ if (messageMap == null) {
+ messageMap = Maps.newHashMap();
+ map.put(warning.issue, messageMap);
+ }
+ Map<Integer, Map<String, Warning>> lineMap = messageMap.get(warning.message);
+ if (lineMap == null) {
+ lineMap = Maps.newHashMap();
+ messageMap.put(warning.message, lineMap);
+ }
+ Map<String, Warning> fileMap = lineMap.get(warning.line);
+ if (fileMap == null) {
+ fileMap = Maps.newHashMap();
+ lineMap.put(warning.line, fileMap);
+ }
+ String fileName = warning.file != null ? warning.file.getName() : "<unknown>";
+ Warning canonical = fileMap.get(fileName);
+ if (canonical == null) {
+ canonical = warning;
+ fileMap.put(fileName, canonical);
+ canonical.variants = Sets.newHashSet();
+ canonical.gradleProject = project;
+ }
+ merged.add(canonical);
+ canonical.variants.add(variant);
+ }
+ }
+
+ // Clear out variants on any nodes that don't define all
+ for (Warning warning : merged) {
+ if (warning.variants != null && warning.variants.size() == totalVariantCount) {
+ // If this error is present in all variants, just clear it out
+ warning.variants = null;
+ }
+
+ }
+
+ Collections.sort(merged);
+ return merged;
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/LintGradleProject.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/LintGradleProject.java
new file mode 100644
index 0000000..2ce9124
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/LintGradleProject.java
@@ -0,0 +1,474 @@
+package com.android.build.gradle.internal;
+
+import com.google.common.base.Charsets;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.io.Files;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidArtifact;
+import com.android.builder.model.AndroidLibrary;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.BuildTypeContainer;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.ProductFlavor;
+import com.android.builder.model.ProductFlavorContainer;
+import com.android.builder.model.SourceProvider;
+import com.android.builder.model.Variant;
+import com.android.sdklib.AndroidTargetHash;
+import com.android.sdklib.AndroidVersion;
+import com.android.tools.lint.detector.api.Project;
+import com.android.utils.Pair;
+import com.android.utils.XmlUtils;
+
+import org.w3c.dom.Document;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import static java.io.File.separatorChar;
+
+/**
+ * An implementation of Lint's {@link Project} class wrapping a Gradle model (project or
+ * library)
+ */
+public class LintGradleProject extends Project {
+ private LintGradleProject(
+ @NonNull LintGradleClient client,
+ @NonNull File dir,
+ @NonNull File referenceDir,
+ @NonNull File manifest) {
+ super(client, dir, referenceDir);
+ mGradleProject = true;
+ mMergeManifests = true;
+ mDirectLibraries = Lists.newArrayList();
+ readManifest(manifest);
+ }
+
+ /**
+ * Creates a {@link com.android.build.gradle.internal.LintGradleProject} from
+ * the given {@link com.android.builder.model.AndroidProject} definition for
+ * a given {@link com.android.builder.model.Variant}, and returns it along with
+ * a set of lint custom rule jars applicable for the given model project.
+ *
+ * @param client the client
+ * @param project the model project
+ * @param variant the variant
+ * @param gradleProject the gradle project
+ * @return a pair of new project and list of custom rule jars
+ */
+ @NonNull
+ public static Pair<LintGradleProject, List<File>> create(
+ @NonNull LintGradleClient client,
+ @NonNull AndroidProject project,
+ @NonNull Variant variant,
+ @NonNull org.gradle.api.Project gradleProject) {
+ File dir = gradleProject.getRootDir();
+ AppGradleProject lintProject = new AppGradleProject(client, dir,
+ dir, project, variant);
+
+ List<File> customRules = Lists.newArrayList();
+ File appLintJar = new File(gradleProject.getBuildDir(),
+ "lint" + separatorChar + "lint.jar");
+ if (appLintJar.exists()) {
+ customRules.add(appLintJar);
+ }
+
+ Set<AndroidLibrary> libraries = Sets.newHashSet();
+ Dependencies dependencies = variant.getMainArtifact().getDependencies();
+ for (AndroidLibrary library : dependencies.getLibraries()) {
+ lintProject.addDirectLibrary(createLibrary(client, library, libraries, customRules));
+ }
+
+ return Pair.<LintGradleProject,List<File>>of(lintProject, customRules);
+ }
+
+
+ @Override
+ protected void initialize() {
+ // Deliberately not calling super; that code is for ADT compatibility
+ }
+
+ protected void readManifest(File manifest) {
+ if (manifest.exists()) {
+ try {
+ String xml = Files.toString(manifest, Charsets.UTF_8);
+ Document document = XmlUtils.parseDocumentSilently(xml, true);
+ if (document != null) {
+ readManifest(document);
+ }
+ } catch (IOException e) {
+ mClient.log(e, "Could not read manifest %1$s", manifest);
+ }
+ }
+ }
+
+ @Override
+ public boolean isGradleProject() {
+ return true;
+ }
+
+ void addDirectLibrary(@NonNull Project project) {
+ mDirectLibraries.add(project);
+ }
+
+ @NonNull
+ private static LibraryProject createLibrary(@NonNull LintGradleClient client,
+ @NonNull AndroidLibrary library,
+ @NonNull Set<AndroidLibrary> seen, List<File> customRules) {
+ seen.add(library);
+ File dir = library.getFolder();
+ LibraryProject project = new LibraryProject(client, dir, dir, library);
+
+ File ruleJar = library.getLintJar();
+ if (ruleJar.exists()) {
+ customRules.add(ruleJar);
+ }
+
+ for (AndroidLibrary dependent : library.getLibraryDependencies()) {
+ if (!seen.contains(dependent)) {
+ project.addDirectLibrary(createLibrary(client, dependent, seen, customRules));
+ }
+ }
+
+ return project;
+ }
+
+ private static class AppGradleProject extends LintGradleProject {
+ private AndroidProject mProject;
+ private Variant mVariant;
+ private List<SourceProvider> mProviders;
+
+ private AppGradleProject(
+ @NonNull LintGradleClient client,
+ @NonNull File dir,
+ @NonNull File referenceDir,
+ @NonNull AndroidProject project,
+ @NonNull Variant variant) {
+ super(client, dir, referenceDir, variant.getMainArtifact().getGeneratedManifest());
+
+ mProject = project;
+ mVariant = variant;
+ }
+
+ @Override
+ public boolean isLibrary() {
+ return mProject.isLibrary();
+ }
+
+ @Override
+ public AndroidProject getGradleProjectModel() {
+ return mProject;
+ }
+
+ @Override
+ public Variant getCurrentVariant() {
+ return mVariant;
+ }
+
+ private List<SourceProvider> getSourceProviders() {
+ if (mProviders == null) {
+ List<SourceProvider> providers = Lists.newArrayList();
+ AndroidArtifact mainArtifact = mVariant.getMainArtifact();
+
+ providers.add(mProject.getDefaultConfig().getSourceProvider());
+
+ for (String flavorName : mVariant.getProductFlavors()) {
+ for (ProductFlavorContainer flavor : mProject.getProductFlavors()) {
+ if (flavorName.equals(flavor.getProductFlavor().getName())) {
+ providers.add(flavor.getSourceProvider());
+ break;
+ }
+ }
+ }
+
+ SourceProvider multiProvider = mainArtifact.getMultiFlavorSourceProvider();
+ if (multiProvider != null) {
+ providers.add(multiProvider);
+ }
+
+ String buildTypeName = mVariant.getBuildType();
+ for (BuildTypeContainer buildType : mProject.getBuildTypes()) {
+ if (buildTypeName.equals(buildType.getBuildType().getName())) {
+ providers.add(buildType.getSourceProvider());
+ break;
+ }
+ }
+
+ SourceProvider variantProvider = mainArtifact.getVariantSourceProvider();
+ if (variantProvider != null) {
+ providers.add(variantProvider);
+ }
+
+ mProviders = providers;
+ }
+
+ return mProviders;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getManifestFiles() {
+ if (mManifestFiles == null) {
+ mManifestFiles = Lists.newArrayList();
+ for (SourceProvider provider : getSourceProviders()) {
+ File manifestFile = provider.getManifestFile();
+ if (manifestFile.exists()) { // model returns path whether or not it exists
+ mManifestFiles.add(manifestFile);
+ }
+ }
+ }
+
+ return mManifestFiles;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getProguardFiles() {
+ if (mProguardFiles == null) {
+ ProductFlavor flavor = mProject.getDefaultConfig().getProductFlavor();
+ mProguardFiles = Lists.newArrayList();
+ for (File file : flavor.getProguardFiles()) {
+ if (file.exists()) {
+ mProguardFiles.add(file);
+ }
+ }
+ try {
+ for (File file : flavor.getConsumerProguardFiles()) {
+ if (file.exists()) {
+ mProguardFiles.add(file);
+ }
+ }
+ } catch (Throwable t) {
+ // On some models, this threw
+ // org.gradle.tooling.model.UnsupportedMethodException:
+ // Unsupported method: BaseConfig.getConsumerProguardFiles().
+ // Playing it safe for a while.
+ }
+ }
+
+ return mProguardFiles;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getResourceFolders() {
+ if (mResourceFolders == null) {
+ mResourceFolders = Lists.newArrayList();
+ for (SourceProvider provider : getSourceProviders()) {
+ Collection<File> resDirs = provider.getResDirectories();
+ for (File res : resDirs) {
+ if (res.exists()) { // model returns path whether or not it exists
+ mResourceFolders.add(res);
+ }
+ }
+ }
+ }
+
+ return mResourceFolders;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getJavaSourceFolders() {
+ if (mJavaSourceFolders == null) {
+ mJavaSourceFolders = Lists.newArrayList();
+ for (SourceProvider provider : getSourceProviders()) {
+ Collection<File> resDirs = provider.getJavaDirectories();
+ for (File res : resDirs) {
+ if (res.exists()) { // model returns path whether or not it exists
+ mJavaSourceFolders.add(res);
+ }
+ }
+ }
+ }
+
+ return mJavaSourceFolders;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getJavaClassFolders() {
+ if (mJavaClassFolders == null) {
+ mJavaClassFolders = new ArrayList<File>(1);
+ File outputClassFolder = mVariant.getMainArtifact().getClassesFolder();
+ if (outputClassFolder.exists()) {
+ mJavaClassFolders.add(outputClassFolder);
+ }
+ }
+
+ return mJavaClassFolders;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getJavaLibraries() {
+ if (mJavaLibraries == null) {
+ Collection<File> jars = mVariant.getMainArtifact().getDependencies().getJars();
+ mJavaLibraries = Lists.newArrayListWithExpectedSize(jars.size());
+ for (File jar : jars) {
+ if (jar.exists()) {
+ mJavaLibraries.add(jar);
+ }
+ }
+ }
+ return mJavaLibraries;
+ }
+
+ @Nullable
+ @Override
+ public String getPackage() {
+ // For now, lint only needs the manifest package; not the potentially variant specific
+ // package. As part of the Gradle work on the Lint API we should make two separate
+ // package lookup methods -- one for the manifest package, one for the build package
+ if (mPackage == null) { // only used as a fallback in case manifest somehow is null
+ String packageName = mProject.getDefaultConfig().getProductFlavor().getPackageName();
+ if (packageName != null) {
+ return packageName;
+ }
+ }
+
+ return mPackage; // from manifest
+ }
+
+ @Override
+ public int getMinSdk() {
+ int minSdk = mProject.getDefaultConfig().getProductFlavor().getMinSdkVersion();
+ if (minSdk != -1) {
+ return minSdk;
+ }
+
+ return mMinSdk; // from manifest
+ }
+
+ @Override
+ public int getTargetSdk() {
+ int targetSdk = mProject.getDefaultConfig().getProductFlavor().getTargetSdkVersion();
+ if (targetSdk != -1) {
+ return targetSdk;
+ }
+
+ return targetSdk; // from manifest
+ }
+
+ @Override
+ public int getBuildSdk() {
+ String compileTarget = mProject.getCompileTarget();
+ AndroidVersion version = AndroidTargetHash.getPlatformVersion(compileTarget);
+ if (version != null) {
+ return version.getApiLevel();
+ }
+
+ return super.getBuildSdk();
+ }
+ }
+
+ private static class LibraryProject extends LintGradleProject {
+ private AndroidLibrary mLibrary;
+
+ private LibraryProject(
+ @NonNull LintGradleClient client,
+ @NonNull File dir,
+ @NonNull File referenceDir,
+ @NonNull AndroidLibrary library) {
+ super(client, dir, referenceDir, library.getManifest());
+ mLibrary = library;
+
+ // TODO: Make sure we don't use this project for any source library projects!
+ mReportIssues = false;
+ }
+
+ @Override
+ public boolean isLibrary() {
+ return true;
+ }
+
+ @Override
+ public AndroidLibrary getGradleLibraryModel() {
+ return mLibrary;
+ }
+
+ @Override
+ public Variant getCurrentVariant() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getManifestFiles() {
+ if (mManifestFiles == null) {
+ File manifest = mLibrary.getManifest();
+ if (manifest.exists()) {
+ mManifestFiles = Collections.singletonList(manifest);
+ } else {
+ mManifestFiles = Collections.emptyList();
+ }
+ }
+
+ return mManifestFiles;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getProguardFiles() {
+ if (mProguardFiles == null) {
+ File proguardRules = mLibrary.getProguardRules();
+ if (proguardRules.exists()) {
+ mProguardFiles = Collections.singletonList(proguardRules);
+ } else {
+ mProguardFiles = Collections.emptyList();
+ }
+ }
+
+ return mProguardFiles;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getResourceFolders() {
+ if (mResourceFolders == null) {
+ File folder = mLibrary.getResFolder();
+ if (folder.exists()) {
+ mResourceFolders = Collections.singletonList(folder);
+ } else {
+ mResourceFolders = Collections.emptyList();
+ }
+ }
+
+ return mResourceFolders;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getJavaSourceFolders() {
+ return Collections.emptyList();
+ }
+
+ @NonNull
+ @Override
+ public List<File> getJavaClassFolders() {
+ return Collections.emptyList();
+ }
+
+ @NonNull
+ @Override
+ public List<File> getJavaLibraries() {
+ if (mJavaLibraries == null) {
+ File jarFile = mLibrary.getJarFile();
+ if (jarFile.exists()) {
+ mJavaLibraries = Collections.singletonList(jarFile);
+ } else {
+ mJavaLibraries = Collections.emptyList();
+ }
+ }
+
+ return mJavaLibraries;
+ }
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/LintGradleRequest.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/LintGradleRequest.java
new file mode 100644
index 0000000..b5674f9
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/LintGradleRequest.java
@@ -0,0 +1,63 @@
+package com.android.build.gradle.internal;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.BasePlugin;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Variant;
+import com.android.tools.lint.client.api.LintRequest;
+import com.android.tools.lint.detector.api.Project;
+import com.android.utils.Pair;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+class LintGradleRequest extends LintRequest {
+ @NonNull private final LintGradleClient mLintClient;
+ @NonNull private final BasePlugin mPlugin;
+ @NonNull private final String mVariantName;
+ @NonNull private final AndroidProject mModelProject;
+
+ public LintGradleRequest(
+ @NonNull LintGradleClient client,
+ @NonNull AndroidProject modelProject,
+ @NonNull BasePlugin plugin,
+ @Nullable String variantName,
+ @NonNull List<File> files) {
+ super(client, files);
+ mLintClient = client;
+ mModelProject = modelProject;
+ mPlugin = plugin;
+ mVariantName = variantName;
+ }
+
+ @Nullable
+ @Override
+ public Collection<Project> getProjects() {
+ if (mProjects == null) {
+ Variant variant = findVariant(mModelProject, mVariantName);
+ assert variant != null : mVariantName;
+ Pair<LintGradleProject,List<File>> result = LintGradleProject.create(
+ mLintClient, mModelProject, variant, mPlugin.getProject());
+ mProjects = Collections.<Project>singletonList(result.getFirst());
+ mLintClient.setCustomRules(result.getSecond());
+ }
+
+ return mProjects;
+ }
+
+ private static Variant findVariant(@NonNull AndroidProject project,
+ @NonNull String variantName) {
+ if (variantName != null) {
+ for (Variant variant : project.getVariants()) {
+ if (variantName.equals(variant.getName())) {
+ return variant;
+ }
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/LoggerWrapper.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/LoggerWrapper.groovy
new file mode 100644
index 0000000..575c07e
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/LoggerWrapper.groovy
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal
+
+import com.android.ide.common.res2.MergingException
+import com.android.utils.ILogger
+import org.gradle.api.logging.LogLevel
+import org.gradle.api.logging.Logger
+
+/**
+ * Implementation of Android's {@link ILogger} over gradle's {@link Logger}.
+ */
+class LoggerWrapper implements ILogger {
+
+ private final Logger logger
+
+ LoggerWrapper(Logger logger) {
+ this.logger = logger;
+ }
+
+ @Override
+ void error(Throwable throwable, String s, Object... objects) {
+ if (throwable instanceof MergingException) {
+ // MergingExceptions have a known cause: they aren't internal errors, they
+ // are errors in the user's code, so a full exception is not helpful (and
+ // these exceptions should include a pointer to the user's error right in
+ // the message).
+ //
+ // Furthermore, these exceptions are already caught by the MergeResources
+ // and MergeAsset tasks, so don't duplicate the output
+ return
+ }
+
+ if (objects != null && objects.length > 0) {
+ s = String.format(s, objects)
+ }
+
+ if (throwable == null) {
+ logger.log(LogLevel.ERROR, s)
+
+ } else {
+ logger.log(LogLevel.ERROR, s, throwable)
+ }
+ }
+
+ @Override
+ void warning(String s, Object... objects) {
+ if (objects == null || objects.length == 0) {
+ logger.log(LogLevel.WARN, s)
+ } else {
+ logger.log(LogLevel.WARN, String.format(s, objects))
+ }
+ }
+
+ @Override
+ void info(String s, Object... objects) {
+ if (objects == null || objects.length == 0) {
+ logger.log(LogLevel.INFO, s)
+ } else {
+ logger.log(LogLevel.INFO, String.format(s, objects))
+ }
+ }
+
+ @Override
+ void verbose(String s, Object... objects) {
+ if (objects == null || objects.length == 0) {
+ logger.log(LogLevel.DEBUG, s)
+
+ } else {
+ logger.log(LogLevel.DEBUG, String.format(s, objects))
+ }
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/ProductFlavorData.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/ProductFlavorData.groovy
new file mode 100644
index 0000000..2c7272a
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/ProductFlavorData.groovy
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal
+
+import com.android.build.gradle.internal.api.DefaultAndroidSourceSet
+import com.android.builder.DefaultProductFlavor
+import org.gradle.api.Project
+import org.gradle.api.Task
+
+/**
+ * Class containing a ProductFlavor and associated data (sourcesets)
+ */
+public class ProductFlavorData<T extends DefaultProductFlavor> {
+
+ final T productFlavor
+
+ final DefaultAndroidSourceSet sourceSet
+ final DefaultAndroidSourceSet testSourceSet
+ final ConfigurationProvider mainProvider
+ final ConfigurationProvider testProvider
+
+ Task assembleTask
+
+ ProductFlavorData(T productFlavor,
+ DefaultAndroidSourceSet sourceSet, DefaultAndroidSourceSet testSourceSet,
+ Project project) {
+ this.productFlavor = productFlavor
+ this.sourceSet = sourceSet
+ this.testSourceSet = testSourceSet
+ mainProvider = new ConfigurationProviderImpl(project, sourceSet)
+ testProvider = new ConfigurationProviderImpl(project, testSourceSet)
+ }
+
+ public static String getFlavoredName(ProductFlavorData[] flavorDataArray, boolean capitalized) {
+ StringBuilder builder = new StringBuilder()
+ for (ProductFlavorData data : flavorDataArray) {
+ builder.append(capitalized ?
+ data.productFlavor.name.capitalize() :
+ data.productFlavor.name)
+ }
+
+ return builder.toString()
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/Sdk.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/Sdk.groovy
new file mode 100644
index 0000000..88c8726
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/Sdk.groovy
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal
+
+import com.android.annotations.NonNull
+import com.android.build.gradle.BaseExtension
+import com.android.builder.DefaultSdkParser
+import com.android.builder.PlatformSdkParser
+import com.android.builder.SdkParser
+import com.android.sdklib.repository.FullRevision
+import com.android.utils.ILogger
+import org.gradle.api.Project
+
+import static com.android.SdkConstants.FN_LOCAL_PROPERTIES
+import static com.android.build.gradle.BasePlugin.TEST_SDK_DIR
+import static com.google.common.base.Preconditions.checkNotNull
+
+/**
+ * Encapsulate finding, parsing, and initializing the SdkParser lazily.
+ */
+public class Sdk {
+
+ @NonNull
+ private final Project project
+ @NonNull
+ private final ILogger logger
+ @NonNull
+ private SdkParser parser
+
+ private boolean isSdkParserInitialized = false
+ private File androidSdkDir
+ private File androidNdkDir
+ private boolean isPlatformSdk = false
+ private BaseExtension extension
+
+ public Sdk(@NonNull Project project, @NonNull ILogger logger) {
+ this.project = project
+ this.logger = logger
+
+ findLocation()
+ }
+
+ public void setExtension(@NonNull BaseExtension extension) {
+ this.extension = extension
+ parser = initParser()
+ }
+
+
+ public SdkParser getParser() {
+ return parser
+ }
+
+ /**
+ * Returns the parser, creating it if needed. This does not load it if it's not been loaded yet.
+ *
+ * @return the parser.
+ *
+ * @see #loadParser()
+ */
+ @NonNull
+ private SdkParser initParser() {
+ checkLocation()
+
+ SdkParser parser;
+
+ //noinspection GroovyIfStatementWithIdenticalBranches
+ if (isPlatformSdk) {
+ parser = new PlatformSdkParser(androidSdkDir.absolutePath)
+ } else {
+ parser = new DefaultSdkParser(androidSdkDir.absolutePath, androidNdkDir)
+ }
+
+ List<File> repositories = parser.repositories
+ for (File file : repositories) {
+ project.repositories.maven {
+ url = file.toURI()
+ }
+ }
+
+ return parser
+ }
+
+ /**
+ * Loads and returns the parser. If it's not been created yet, this will do it too.
+ *
+ * {@link #setExtension(BaseExtension)} must have been called before.
+ *
+ * For more light weight usage, consider {@link #getParser()}
+ *
+ * @return the loaded parser.
+ *
+ * @see #getParser()
+ */
+ @NonNull
+ public SdkParser loadParser() {
+ checkNotNull(extension, "Extension has not been set")
+
+ // call getParser to ensure it's created.
+ SdkParser theParser = getParser()
+
+ if (!isSdkParserInitialized) {
+ String target = extension.getCompileSdkVersion()
+ if (target == null) {
+ throw new IllegalArgumentException("android.compileSdkVersion is missing!")
+ }
+
+ FullRevision buildToolsRevision = extension.buildToolsRevision
+ if (buildToolsRevision == null) {
+ throw new IllegalArgumentException("android.buildToolsVersion is missing!")
+ }
+
+ theParser.initParser(target, buildToolsRevision, logger)
+
+ isSdkParserInitialized = true
+ }
+
+ return theParser
+ }
+
+ public File getSdkDirectory() {
+ checkLocation()
+ return androidSdkDir
+ }
+
+ public File getNdkDirectory() {
+ checkLocation()
+ return androidNdkDir
+ }
+
+ private void checkLocation() {
+ // don't complain in test mode
+ if (TEST_SDK_DIR != null) {
+ return
+ }
+
+ if (androidSdkDir == null) {
+ throw new RuntimeException(
+ "SDK location not found. Define location with sdk.dir in the local.properties file or with an ANDROID_HOME environment variable.")
+ }
+
+ if (!androidSdkDir.isDirectory()) {
+ throw new RuntimeException(
+ "The SDK directory '$androidSdkDir.absolutePath' does not exist.")
+ }
+ }
+
+ private void findLocation() {
+ if (TEST_SDK_DIR != null) {
+ androidSdkDir = TEST_SDK_DIR
+ return
+ }
+
+ def rootDir = project.rootDir
+ def localProperties = new File(rootDir, FN_LOCAL_PROPERTIES)
+ if (localProperties.exists()) {
+ Properties properties = new Properties()
+ localProperties.withInputStream { instr ->
+ properties.load(instr)
+ }
+ def sdkDirProp = properties.getProperty('sdk.dir')
+
+ if (sdkDirProp != null) {
+ androidSdkDir = new File(sdkDirProp)
+ } else {
+ sdkDirProp = properties.getProperty('android.dir')
+ if (sdkDirProp != null) {
+ androidSdkDir = new File(rootDir, sdkDirProp)
+ isPlatformSdk = true
+ } else {
+ throw new RuntimeException(
+ "No sdk.dir property defined in local.properties file.")
+ }
+ }
+
+ def ndkDirProp = properties.getProperty('ndk.dir')
+ if (ndkDirProp != null) {
+ androidNdkDir = new File(ndkDirProp)
+ }
+
+ } else {
+ String envVar = System.getenv("ANDROID_HOME")
+ if (envVar != null) {
+ androidSdkDir = new File(envVar)
+ } else {
+ String property = System.getProperty("android.home")
+ if (property != null) {
+ androidSdkDir = new File(property)
+ }
+ }
+
+ envVar = System.getenv("ANDROID_NDK_HOME")
+ if (envVar != null) {
+ androidNdkDir = new File(envVar)
+ }
+ }
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/SourceSetSourceProviderWrapper.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/SourceSetSourceProviderWrapper.java
new file mode 100644
index 0000000..0cc3f7c
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/SourceSetSourceProviderWrapper.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal;
+
+import com.android.annotations.NonNull;
+import com.android.builder.model.SourceProvider;
+import org.gradle.api.tasks.SourceSet;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * An implementation of SourceProvider that's wrapper around a Java SourceSet.
+ * This is useful for the case where we store SourceProviders but don't want to
+ * query the content of the SourceSet at the moment the SourceProvider is created.
+ */
+public class SourceSetSourceProviderWrapper implements SourceProvider {
+
+ @NonNull
+ private final SourceSet sourceSet;
+
+ public SourceSetSourceProviderWrapper(@NonNull SourceSet sourceSet) {
+
+ this.sourceSet = sourceSet;
+ }
+
+ @NonNull
+ @Override
+ public File getManifestFile() {
+ throw new IllegalAccessError("Shouldn't access manifest from SourceSetSourceProviderWrapper");
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getJavaDirectories() {
+ return sourceSet.getAllJava().getSrcDirs();
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getResourcesDirectories() {
+ return sourceSet.getResources().getSrcDirs();
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getAidlDirectories() {
+ return Collections.emptyList();
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getRenderscriptDirectories() {
+ return Collections.emptyList();
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getJniDirectories() {
+ return Collections.emptyList();
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getResDirectories() {
+ return Collections.emptyList();
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getAssetsDirectories() {
+ return Collections.emptyList();
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/StringHelper.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/StringHelper.groovy
new file mode 100644
index 0000000..51aa208
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/StringHelper.groovy
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal
+
+import com.android.annotations.NonNull
+
+/**
+ * Helper to give access to Groovy string methods from Java classes.
+ */
+class StringHelper {
+
+ @NonNull
+ public static String capitalize(@NonNull String string) {
+ return string.capitalize()
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/api/ApplicationVariantImpl.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/api/ApplicationVariantImpl.java
new file mode 100644
index 0000000..fcc3d0b
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/api/ApplicationVariantImpl.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.api.ApplicationVariant;
+import com.android.build.gradle.api.TestVariant;
+import com.android.build.gradle.internal.variant.ApplicationVariantData;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.build.gradle.tasks.Dex;
+import com.android.build.gradle.tasks.PackageApplication;
+import com.android.build.gradle.tasks.ZipAlign;
+import com.android.builder.DefaultProductFlavor;
+import com.android.builder.model.SigningConfig;
+import org.gradle.api.DefaultTask;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * implementation of the {@link ApplicationVariant} interface around an
+ * {@link ApplicationVariantData} object.
+ */
+public class ApplicationVariantImpl extends BaseVariantImpl implements ApplicationVariant {
+
+ @NonNull
+ private final ApplicationVariantData variantData;
+ @Nullable
+ private TestVariant testVariant = null;
+
+ public ApplicationVariantImpl(@NonNull ApplicationVariantData variantData) {
+ this.variantData = variantData;
+ }
+
+ @Override
+ protected BaseVariantData getVariantData() {
+ return variantData;
+ }
+
+ public void setTestVariant(@Nullable TestVariant testVariant) {
+ this.testVariant = testVariant;
+ }
+
+ @Override
+ @NonNull
+ public List<DefaultProductFlavor> getProductFlavors() {
+ return variantData.getVariantConfiguration().getFlavorConfigs();
+ }
+
+ @Override
+ @NonNull
+ public DefaultProductFlavor getMergedFlavor() {
+ return variantData.getVariantConfiguration().getMergedFlavor();
+ }
+
+ @Override
+ public void setOutputFile(@NonNull File outputFile) {
+ if (variantData.zipAlignTask != null) {
+ variantData.zipAlignTask.setOutputFile(outputFile);
+ } else {
+ variantData.packageApplicationTask.setOutputFile(outputFile);
+ }
+ }
+
+ @Override
+ @Nullable
+ public TestVariant getTestVariant() {
+ return testVariant;
+ }
+
+ @Override
+ public Dex getDex() {
+ return variantData.dexTask;
+ }
+
+ @Override
+ public PackageApplication getPackageApplication() {
+ return variantData.packageApplicationTask;
+ }
+
+ @Override
+ public ZipAlign getZipAlign() {
+ return variantData.zipAlignTask;
+ }
+
+ @Override
+ public DefaultTask getInstall() {
+ return variantData.installTask;
+ }
+
+ @Override
+ public DefaultTask getUninstall() {
+ return variantData.uninstallTask;
+ }
+
+ @Override
+ public SigningConfig getSigningConfig() {
+ return variantData.getVariantConfiguration().getSigningConfig();
+ }
+
+ @Override
+ public boolean isSigningReady() {
+ return variantData.isSigned();
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/api/BaseVariantImpl.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/api/BaseVariantImpl.java
new file mode 100644
index 0000000..894fe8a
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/api/BaseVariantImpl.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.api.BaseVariant;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.build.gradle.tasks.AidlCompile;
+import com.android.build.gradle.tasks.GenerateBuildConfig;
+import com.android.build.gradle.tasks.MergeAssets;
+import com.android.build.gradle.tasks.MergeResources;
+import com.android.build.gradle.tasks.ProcessAndroidResources;
+import com.android.build.gradle.tasks.ProcessManifest;
+import com.android.build.gradle.tasks.RenderscriptCompile;
+import com.android.builder.DefaultBuildType;
+import com.android.builder.DefaultProductFlavor;
+import org.gradle.api.Task;
+import org.gradle.api.tasks.Copy;
+import org.gradle.api.tasks.compile.JavaCompile;
+
+import java.io.File;
+import java.util.Collection;
+
+abstract class BaseVariantImpl implements BaseVariant {
+
+ protected abstract BaseVariantData getVariantData();
+
+ @Override
+ @NonNull
+ public String getName() {
+ return getVariantData().getVariantConfiguration().getFullName();
+ }
+
+ @Override
+ @NonNull
+ public String getDescription() {
+ return getVariantData().getDescription();
+ }
+
+ @Override
+ @NonNull
+ public String getDirName() {
+ return getVariantData().getVariantConfiguration().getDirName();
+ }
+
+ @Override
+ @NonNull
+ public String getBaseName() {
+ return getVariantData().getVariantConfiguration().getBaseName();
+ }
+
+ @NonNull
+ @Override
+ public String getFlavorName() {
+ return getVariantData().getVariantConfiguration().getFlavorName();
+ }
+
+ @Override
+ @NonNull
+ public DefaultBuildType getBuildType() {
+ return getVariantData().getVariantConfiguration().getBuildType();
+ }
+
+ @NonNull
+ @Override
+ public DefaultProductFlavor getConfig() {
+ return getVariantData().getVariantConfiguration().getDefaultConfig();
+ }
+
+ @Override
+ @NonNull
+ public File getOutputFile() {
+ return getVariantData().getOutputFile();
+ }
+
+ @Override
+ @NonNull
+ public ProcessManifest getProcessManifest() {
+ return getVariantData().processManifestTask;
+ }
+
+ @Override
+ @NonNull
+ public AidlCompile getAidlCompile() {
+ return getVariantData().aidlCompileTask;
+ }
+
+ @Override
+ @NonNull
+ public RenderscriptCompile getRenderscriptCompile() {
+ return getVariantData().renderscriptCompileTask;
+ }
+
+ @Override
+ public MergeResources getMergeResources() {
+ return getVariantData().mergeResourcesTask;
+ }
+
+ @Override
+ public MergeAssets getMergeAssets() {
+ return getVariantData().mergeAssetsTask;
+ }
+
+ @Override
+ @NonNull
+ public ProcessAndroidResources getProcessResources() {
+ return getVariantData().processResourcesTask;
+ }
+
+ @Override
+ public GenerateBuildConfig getGenerateBuildConfig() {
+ return getVariantData().generateBuildConfigTask;
+ }
+
+ @Override
+ @NonNull
+ public JavaCompile getJavaCompile() {
+ return getVariantData().javaCompileTask;
+ }
+
+ @Override
+ @NonNull
+ public Copy getProcessJavaResources() {
+ return getVariantData().processJavaResourcesTask;
+ }
+
+ @Override
+ @Nullable
+ public Task getAssemble() {
+ return getVariantData().assembleTask;
+ }
+
+ @Override
+ public void addJavaSourceFoldersToModel(@NonNull File... generatedSourceFolders) {
+ getVariantData().addJavaSourceFoldersToModel(generatedSourceFolders);
+ }
+
+ @Override
+ public void addJavaSourceFoldersToModel(@NonNull Collection<File> generatedSourceFolders) {
+ getVariantData().addJavaSourceFoldersToModel(generatedSourceFolders);
+ }
+
+ @Override
+ public void registerJavaGeneratingTask(@NonNull Task task, @NonNull File... sourceFolders) {
+ getVariantData().registerJavaGeneratingTask(task, sourceFolders);
+ }
+
+ @Override
+ public void registerJavaGeneratingTask(@NonNull Task task, @NonNull Collection<File> sourceFolders) {
+ getVariantData().registerJavaGeneratingTask(task, sourceFolders);
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/api/DefaultAndroidSourceDirectorySet.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/api/DefaultAndroidSourceDirectorySet.java
new file mode 100644
index 0000000..a9647e6
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/api/DefaultAndroidSourceDirectorySet.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.api;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.api.AndroidSourceDirectorySet;
+import com.google.common.collect.Lists;
+import org.gradle.api.internal.file.FileResolver;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Default implementation of the AndroidSourceDirectorySet.
+ */
+public class DefaultAndroidSourceDirectorySet implements AndroidSourceDirectorySet {
+
+ private final String name;
+ private final FileResolver fileResolver;
+ private List<Object> source = Lists.newArrayList();
+
+ DefaultAndroidSourceDirectorySet(@NonNull String name, @NonNull FileResolver fileResolver) {
+ this.name = name;
+ this.fileResolver = fileResolver;
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ @NonNull
+ public AndroidSourceDirectorySet srcDir(Object srcDir) {
+ source.add(srcDir);
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public AndroidSourceDirectorySet srcDirs(Object... srcDirs) {
+ Collections.addAll(source, srcDirs);
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public AndroidSourceDirectorySet setSrcDirs(Iterable<?> srcDirs) {
+ source.clear();
+ for (Object srcDir : srcDirs) {
+ source.add(srcDir);
+ }
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public Set<File> getSrcDirs() {
+ return fileResolver.resolveFiles(source.toArray()).getFiles();
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return source.toString();
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/api/DefaultAndroidSourceFile.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/api/DefaultAndroidSourceFile.java
new file mode 100644
index 0000000..67778bc
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/api/DefaultAndroidSourceFile.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.api;
+
+import com.android.build.gradle.api.AndroidSourceFile;
+import org.gradle.api.internal.file.FileResolver;
+
+import java.io.File;
+
+/**
+ */
+public class DefaultAndroidSourceFile implements AndroidSourceFile {
+
+ private final String name;
+ private final FileResolver fileResolver;
+ private Object source;
+
+ DefaultAndroidSourceFile(String name, FileResolver fileResolver) {
+ this.name = name;
+ this.fileResolver = fileResolver;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public AndroidSourceFile srcFile(Object o) {
+ source = o;
+ return this;
+ }
+
+ @Override
+ public File getSrcFile() {
+ return fileResolver.resolve(source);
+ }
+
+ @Override
+ public String toString() {
+ return source.toString();
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/api/DefaultAndroidSourceSet.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/api/DefaultAndroidSourceSet.java
new file mode 100644
index 0000000..0520fa5
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/api/DefaultAndroidSourceSet.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.api;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.build.gradle.api.AndroidSourceDirectorySet;
+import com.android.build.gradle.api.AndroidSourceFile;
+import com.android.build.gradle.api.AndroidSourceSet;
+import com.android.builder.model.SourceProvider;
+import groovy.lang.Closure;
+import org.gradle.api.file.FileTreeElement;
+import org.gradle.api.file.SourceDirectorySet;
+import org.gradle.api.internal.file.DefaultSourceDirectorySet;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.SourceSet;
+import org.gradle.util.ConfigureUtil;
+import org.gradle.util.GUtil;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ */
+public class DefaultAndroidSourceSet implements AndroidSourceSet, SourceProvider {
+ @NonNull
+ private final String name;
+ private final SourceDirectorySet javaSource;
+ private final SourceDirectorySet allJavaSource;
+ private final SourceDirectorySet javaResources;
+ private final AndroidSourceFile manifest;
+ private final AndroidSourceDirectorySet assets;
+ private final AndroidSourceDirectorySet res;
+ private final AndroidSourceDirectorySet aidl;
+ private final AndroidSourceDirectorySet renderscript;
+ private final AndroidSourceDirectorySet jni;
+ private final String displayName;
+ private final SourceDirectorySet allSource;
+
+ public DefaultAndroidSourceSet(@NonNull String name, @NonNull FileResolver fileResolver) {
+ this.name = name;
+ displayName = GUtil.toWords(this.name);
+
+ String javaSrcDisplayName = String.format("%s Java source", displayName);
+
+ javaSource = new DefaultSourceDirectorySet(javaSrcDisplayName, fileResolver);
+ javaSource.getFilter().include("**/*.java");
+
+ allJavaSource = new DefaultSourceDirectorySet(javaSrcDisplayName, fileResolver);
+ allJavaSource.getFilter().include("**/*.java");
+ allJavaSource.source(javaSource);
+
+ String javaResourcesDisplayName = String.format("%s Java resources", displayName);
+ javaResources = new DefaultSourceDirectorySet(javaResourcesDisplayName, fileResolver);
+ javaResources.getFilter().exclude(new Spec<FileTreeElement>() {
+ @Override
+ public boolean isSatisfiedBy(FileTreeElement element) {
+ return javaSource.contains(element.getFile());
+ }
+ });
+
+ String allSourceDisplayName = String.format("%s source", displayName);
+ allSource = new DefaultSourceDirectorySet(allSourceDisplayName, fileResolver);
+ allSource.source(javaResources);
+ allSource.source(javaSource);
+
+ String manifestDisplayName = String.format("%s manifest", displayName);
+ manifest = new DefaultAndroidSourceFile(manifestDisplayName, fileResolver);
+
+ String assetsDisplayName = String.format("%s assets", displayName);
+ assets = new DefaultAndroidSourceDirectorySet(assetsDisplayName, fileResolver);
+
+ String resourcesDisplayName = String.format("%s resources", displayName);
+ res = new DefaultAndroidSourceDirectorySet(resourcesDisplayName, fileResolver);
+
+ String aidlDisplayName = String.format("%s aidl", displayName);
+ aidl = new DefaultAndroidSourceDirectorySet(aidlDisplayName, fileResolver);
+
+ String renderscriptDisplayName = String.format("%s renderscript", displayName);
+ renderscript = new DefaultAndroidSourceDirectorySet(renderscriptDisplayName, fileResolver);
+
+ String jniDisplayName = String.format("%s jni", displayName);
+ jni = new DefaultAndroidSourceDirectorySet(jniDisplayName, fileResolver);
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return String.format("source set %s", getDisplayName());
+ }
+
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ @Override
+ @NonNull
+ public String getCompileConfigurationName() {
+ if (name.equals(SourceSet.MAIN_SOURCE_SET_NAME)) {
+ return "compile";
+ } else {
+ return String.format("%sCompile", name);
+ }
+ }
+
+ @Override
+ @NonNull
+ public String getPackageConfigurationName() {
+ if (name.equals(SourceSet.MAIN_SOURCE_SET_NAME)) {
+ return "apk";
+ } else {
+ return String.format("%sApk", name);
+ }
+ }
+
+ @Override
+ @NonNull
+ public AndroidSourceFile getManifest() {
+ return manifest;
+ }
+
+ @Override
+ @NonNull
+ public AndroidSourceSet manifest(Closure configureClosure) {
+ ConfigureUtil.configure(configureClosure, getManifest());
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public AndroidSourceDirectorySet getRes() {
+ return res;
+ }
+
+ @Override
+ @NonNull
+ public AndroidSourceSet res(Closure configureClosure) {
+ ConfigureUtil.configure(configureClosure, getRes());
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public AndroidSourceDirectorySet getAssets() {
+ return assets;
+ }
+
+ @Override
+ @NonNull
+ public AndroidSourceSet assets(Closure configureClosure) {
+ ConfigureUtil.configure(configureClosure, getAssets());
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public AndroidSourceDirectorySet getAidl() {
+ return aidl;
+ }
+
+ @Override
+ @NonNull
+ public AndroidSourceSet aidl(Closure configureClosure) {
+ ConfigureUtil.configure(configureClosure, getAidl());
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public AndroidSourceDirectorySet getRenderscript() {
+ return renderscript;
+ }
+
+ @Override
+ @NonNull
+ public AndroidSourceSet renderscript(Closure configureClosure) {
+ ConfigureUtil.configure(configureClosure, getRenderscript());
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public AndroidSourceDirectorySet getJni() {
+ return jni;
+ }
+
+ @Override
+ @NonNull
+ public AndroidSourceSet jni(Closure configureClosure) {
+ ConfigureUtil.configure(configureClosure, getJni());
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public SourceDirectorySet getJava() {
+ return javaSource;
+ }
+
+ @Override
+ @NonNull
+ public AndroidSourceSet java(Closure configureClosure) {
+ ConfigureUtil.configure(configureClosure, getJava());
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public SourceDirectorySet getAllJava() {
+ return allJavaSource;
+ }
+
+ @Override
+ @NonNull
+ public SourceDirectorySet getResources() {
+ return javaResources;
+ }
+
+ @Override
+ @NonNull
+ public AndroidSourceSet resources(Closure configureClosure) {
+ ConfigureUtil.configure(configureClosure, getResources());
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public SourceDirectorySet getAllSource() {
+ return allSource;
+ }
+
+ @Override
+ @NonNull
+ public AndroidSourceSet setRoot(String path) {
+ javaSource.setSrcDirs(Collections.singletonList(path + "/java"));
+ javaResources.setSrcDirs(Collections.singletonList(path + "/resources"));
+ res.setSrcDirs(Collections.singletonList(path + "/" + SdkConstants.FD_RES));
+ assets.setSrcDirs(Collections.singletonList(path + "/" + SdkConstants.FD_ASSETS));
+ manifest.srcFile(path + "/" + SdkConstants.FN_ANDROID_MANIFEST_XML);
+ aidl.setSrcDirs(Collections.singletonList(path + "/aidl"));
+ renderscript.setSrcDirs(Collections.singletonList(path + "/rs"));
+ jni.setSrcDirs(Collections.singletonList(path + "/jni"));
+ return this;
+ }
+
+ // --- SourceProvider
+
+ @NonNull
+ @Override
+ public Set<File> getJavaDirectories() {
+ return getJava().getSrcDirs();
+ }
+
+ @NonNull
+ @Override
+ public Set<File> getResourcesDirectories() {
+ return getResources().getSrcDirs();
+ }
+
+ @Override
+ @NonNull
+ public File getManifestFile() {
+ return getManifest().getSrcFile();
+ }
+
+ @Override
+ @NonNull
+ public Set<File> getAidlDirectories() {
+ return getAidl().getSrcDirs();
+ }
+
+ @Override
+ @NonNull
+ public Set<File> getRenderscriptDirectories() {
+ return getRenderscript().getSrcDirs();
+ }
+
+ @Override
+ @NonNull
+ public Set<File> getJniDirectories() {
+ return getJni().getSrcDirs();
+ }
+
+ @Override
+ @NonNull
+ public Set<File> getResDirectories() {
+ return getRes().getSrcDirs();
+ }
+
+ @Override
+ @NonNull
+ public Set<File> getAssetsDirectories() {
+ return getAssets().getSrcDirs();
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/api/LibraryVariantImpl.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/api/LibraryVariantImpl.java
new file mode 100644
index 0000000..f48a529
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/api/LibraryVariantImpl.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.api.LibraryVariant;
+import com.android.build.gradle.api.TestVariant;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.build.gradle.internal.variant.LibraryVariantData;
+import org.gradle.api.tasks.bundling.Zip;
+
+import java.io.File;
+
+/**
+ * implementation of the {@link LibraryVariant} interface around a
+ * {@link LibraryVariantData} object.
+ */
+public class LibraryVariantImpl extends BaseVariantImpl implements LibraryVariant {
+
+ @NonNull
+ private final LibraryVariantData variantData;
+ @Nullable
+ private TestVariant testVariant = null;
+
+ public LibraryVariantImpl(@NonNull LibraryVariantData variantData) {
+ this.variantData = variantData;
+ }
+
+ @Override
+ protected BaseVariantData getVariantData() {
+ return variantData;
+ }
+
+ public void setTestVariant(@Nullable TestVariant testVariant) {
+ this.testVariant = testVariant;
+ }
+
+ @Override
+ @NonNull
+ public File getOutputFile() {
+ return variantData.packageLibTask.getArchivePath();
+ }
+
+ @Override
+ public void setOutputFile(@NonNull File outputFile) {
+ variantData.packageLibTask.setDestinationDir(outputFile.getParentFile());
+ variantData.packageLibTask.setArchiveName(outputFile.getName());
+ }
+
+ @Override
+ @Nullable
+ public TestVariant getTestVariant() {
+ return testVariant;
+ }
+
+ @Override
+ public Zip getPackageLibrary() {
+ return variantData.packageLibTask;
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/api/TestVariantImpl.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/api/TestVariantImpl.java
new file mode 100644
index 0000000..073c49d
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/api/TestVariantImpl.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.api;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.api.BaseVariant;
+import com.android.build.gradle.api.TestVariant;
+import com.android.build.gradle.internal.variant.BaseVariantData;
+import com.android.build.gradle.internal.variant.TestVariantData;
+import com.android.build.gradle.tasks.Dex;
+import com.android.build.gradle.tasks.PackageApplication;
+import com.android.build.gradle.tasks.ZipAlign;
+import com.android.builder.DefaultProductFlavor;
+import com.android.builder.model.SigningConfig;
+import org.gradle.api.DefaultTask;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * implementation of the {@link TestVariant} interface around an {@link TestVariantData} object.
+ */
+public class TestVariantImpl extends BaseVariantImpl implements TestVariant {
+
+ @NonNull
+ private final TestVariantData variantData;
+ @NonNull
+ private BaseVariant testedVariant;
+
+ public TestVariantImpl(@NonNull TestVariantData variantData) {
+ this.variantData = variantData;
+ }
+
+ @Override
+ protected BaseVariantData getVariantData() {
+ return variantData;
+ }
+
+ @Override
+ @NonNull
+ public List<DefaultProductFlavor> getProductFlavors() {
+ return variantData.getVariantConfiguration().getFlavorConfigs();
+ }
+
+ @Override
+ @NonNull
+ public DefaultProductFlavor getMergedFlavor() {
+ return variantData.getVariantConfiguration().getMergedFlavor();
+ }
+
+ @Override
+ public void setOutputFile(@NonNull File outputFile) {
+ if (variantData.zipAlignTask != null) {
+ variantData.zipAlignTask.setOutputFile(outputFile);
+ } else {
+ variantData.packageApplicationTask.setOutputFile(outputFile);
+ }
+ }
+
+ @Override
+ @NonNull
+ public BaseVariant getTestedVariant() {
+ return testedVariant;
+ }
+
+ public void setTestedVariant(@NonNull BaseVariant testedVariant) {
+ this.testedVariant = testedVariant;
+ }
+
+ @Override
+ public Dex getDex() {
+ return variantData.dexTask;
+ }
+
+ @Override
+ public PackageApplication getPackageApplication() {
+ return variantData.packageApplicationTask;
+ }
+
+ @Override
+ public ZipAlign getZipAlign() {
+ return variantData.zipAlignTask;
+ }
+
+ @Override
+ public DefaultTask getInstall() {
+ return variantData.installTask;
+ }
+
+ @Override
+ public DefaultTask getUninstall() {
+ return variantData.uninstallTask;
+ }
+
+ @Override
+ public DefaultTask getConnectedInstrumentTest() {
+ return variantData.connectedTestTask;
+ }
+
+ @NonNull
+ @Override
+ public List<? extends DefaultTask> getProviderInstrumentTests() {
+ return variantData.providerTestTaskList;
+ }
+
+ @Override
+ public SigningConfig getSigningConfig() {
+ return variantData.getVariantConfiguration().getSigningConfig();
+ }
+
+ @Override
+ public boolean isSigningReady() {
+ return variantData.isSigned();
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dependency/DependencyChecker.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dependency/DependencyChecker.groovy
new file mode 100644
index 0000000..1801e1a
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dependency/DependencyChecker.groovy
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.dependency
+
+import com.android.utils.ILogger
+import org.gradle.api.artifacts.ModuleVersionIdentifier
+
+/**
+ * Checks for dependencies to ensure Android compatibility
+ */
+public class DependencyChecker {
+
+ final VariantDependencies configurationDependencies
+ final logger
+ final List<Integer> foundAndroidApis = []
+ final List<String> foundBouncyCastle = []
+
+ DependencyChecker(VariantDependencies configurationDependencies, ILogger logger) {
+ this.configurationDependencies = configurationDependencies
+ this.logger = logger;
+ }
+
+ boolean excluded(ModuleVersionIdentifier id) {
+ if (id.group == 'com.google.android' && id.name == 'android') {
+ int moduleLevel = getApiLevelFromMavenArtifact(id.version)
+ foundAndroidApis.add(moduleLevel)
+
+ logger.info("Ignoring Android API artifact %s for %s", id, configurationDependencies.name)
+ return true
+ }
+
+ if ((id.group == 'org.apache.httpcomponents' && id.name == 'httpclient') ||
+ (id.group == 'xpp3' && id.name == 'xpp3') ||
+ (id.group == 'commons-logging' && id.name == 'commons-logging') ||
+ (id.group == 'xerces' && id.name == 'xmlParserAPIs')) {
+
+ logger.warning(
+ "WARNING: Dependency %s is ignored for %s as it may be conflicting with the internal version provided by Android.\n" +
+ " In case of problem, please repackage it with jarjar to change the class packages",
+ id, configurationDependencies.name)
+ return true;
+ }
+
+ if (id.group == 'org.json' && id.name == 'json') {
+ logger.warning(
+ "WARNING: Dependency %s is ignored for %s as it may be conflicting with the internal version provided by Android.\n" +
+ " In case of problem, please repackage with jarjar to change the class packages",
+ id, configurationDependencies.name)
+ return true
+ }
+
+ if (id.group == 'org.khronos' && id.name == 'opengl-api') {
+ logger.warning(
+ "WARNING: Dependency %s is ignored for %s as it may be conflicting with the internal version provided by Android.\n" +
+ " In case of problem, please repackage with jarjar to change the class packages",
+ id, configurationDependencies.name)
+ return true
+ }
+
+ if (id.group == 'org.bouncycastle' && id.name.startsWith("bcprov")) {
+ foundBouncyCastle.add(id.version)
+ }
+
+ return false
+ }
+
+ private static int getApiLevelFromMavenArtifact(String version) {
+ switch (version) {
+ case "1.5_r3":
+ case "1.5_r4":
+ return 3;
+ case "1.6_r2":
+ return 4;
+ case "2.1_r1":
+ case "2.1.2":
+ return 7;
+ case "2.2.1":
+ return 8;
+ case "2.3.1":
+ return 9;
+ case "2.3.3":
+ return 10;
+ case "4.0.1.2":
+ return 14;
+ case "4.1.1.4":
+ return 15;
+ }
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dependency/LibraryDependencyImpl.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dependency/LibraryDependencyImpl.java
new file mode 100644
index 0000000..75d4112
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dependency/LibraryDependencyImpl.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.dependency;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.dependency.LibraryBundle;
+import com.android.builder.dependency.LibraryDependency;
+import com.android.builder.dependency.ManifestDependency;
+import com.android.builder.model.AndroidLibrary;
+
+import java.io.File;
+import java.util.List;
+
+public class LibraryDependencyImpl extends LibraryBundle {
+
+ @NonNull
+ private final List<LibraryDependency> dependencies;
+
+ public LibraryDependencyImpl(@NonNull File bundle,
+ @NonNull File explodedBundle,
+ @NonNull List<LibraryDependency> dependencies,
+ @Nullable String name) {
+ super(bundle, explodedBundle, name);
+ this.dependencies = dependencies;
+ }
+
+ @NonNull
+ @Override
+ public List<? extends AndroidLibrary> getLibraryDependencies() {
+ return dependencies;
+ }
+
+ @Override
+ @NonNull
+ public List<LibraryDependency> getDependencies() {
+ return dependencies;
+ }
+
+ @Override
+ @NonNull
+ public List<? extends ManifestDependency> getManifestDependencies() {
+ return dependencies;
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dependency/ManifestDependencyImpl.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dependency/ManifestDependencyImpl.java
new file mode 100644
index 0000000..3af2ff9
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dependency/ManifestDependencyImpl.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dependency;
+
+import com.android.annotations.NonNull;
+import com.android.builder.dependency.ManifestDependency;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.Nested;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Implementation of ManifestDependency that can be used as a Task input.
+ */
+public class ManifestDependencyImpl implements ManifestDependency{
+
+ private final File manifest;
+ private final List<ManifestDependencyImpl> dependencies;
+
+ public ManifestDependencyImpl(@NonNull File manifest,
+ @NonNull List<ManifestDependencyImpl> dependencies) {
+ this.manifest = manifest;
+ this.dependencies = dependencies;
+ }
+
+ @InputFile
+ @Override
+ @NonNull
+ public File getManifest() {
+ return manifest;
+ }
+
+ @Override
+ @NonNull
+ public List<? extends ManifestDependency> getManifestDependencies() {
+ return dependencies;
+ }
+
+ @Nested
+ @NonNull
+ public List<ManifestDependencyImpl> getManifestDependenciesForInput() {
+ return dependencies;
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dependency/SymbolFileProviderImpl.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dependency/SymbolFileProviderImpl.java
new file mode 100644
index 0000000..8fa7bb3
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dependency/SymbolFileProviderImpl.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dependency;
+
+import com.android.annotations.NonNull;
+import com.android.builder.dependency.SymbolFileProvider;
+import org.gradle.api.tasks.InputFile;
+
+import java.io.File;
+
+/**
+ * Implementation of SymbolFileProvider that can be used as a Task input.
+ */
+public class SymbolFileProviderImpl implements SymbolFileProvider {
+
+ private final File manifest;
+ private final File symbolFile;
+
+ public SymbolFileProviderImpl(@NonNull File manifest, @NonNull File symbolFile) {
+ this.manifest = manifest;
+ this.symbolFile = symbolFile;
+ }
+
+ @InputFile
+ @Override
+ @NonNull
+ public File getManifest() {
+ return manifest;
+ }
+
+ @InputFile
+ @Override
+ @NonNull
+ public File getSymbolFile() {
+ return symbolFile;
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dependency/VariantDependencies.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dependency/VariantDependencies.groovy
new file mode 100644
index 0000000..71320f3
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dependency/VariantDependencies.groovy
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dependency
+import com.android.annotations.NonNull
+import com.android.build.gradle.internal.ConfigurationProvider
+import com.android.builder.dependency.DependencyContainer
+import com.android.builder.dependency.JarDependency
+import com.android.builder.dependency.LibraryDependency
+import com.google.common.collect.Sets
+import org.gradle.api.Project
+import org.gradle.api.artifacts.Configuration
+/**
+ * Object that represents the dependencies of a "config", in the sense of defaultConfigs, build
+ * type and flavors.
+ *
+ * The dependencies are expressed as composite Gradle configuration objects that extends
+ * all the configuration objects of the "configs".
+ *
+ * It optionally contains the dependencies for a test config for the given config.
+ */
+public class VariantDependencies implements DependencyContainer, ConfigurationProvider {
+
+ final String name
+
+ @NonNull
+ final Configuration compileConfiguration
+ @NonNull
+ final Configuration packageConfiguration
+
+ @NonNull
+ private final List<LibraryDependencyImpl> libraries = []
+ @NonNull
+ private final List<JarDependency> jars = []
+ @NonNull
+ private final List<JarDependency> localJars = []
+
+ DependencyChecker checker
+
+ static VariantDependencies compute(@NonNull Project project,
+ @NonNull String name,
+ @NonNull ConfigurationProvider... providers) {
+ Set<Configuration> compileConfigs = Sets.newHashSet()
+ Set<Configuration> apkConfigs = Sets.newHashSet()
+
+ for (ConfigurationProvider provider : providers) {
+ compileConfigs.add(provider.compileConfiguration)
+ apkConfigs.add(provider.packageConfiguration)
+ }
+
+ Configuration compile = project.configurations.create("_${name}Compile")
+ compile.setExtendsFrom(compileConfigs)
+
+ Configuration apk = project.configurations.create("_${name}Apk")
+ apk.setExtendsFrom(apkConfigs)
+
+ return new VariantDependencies(name, compile, apk);
+ }
+
+ private VariantDependencies(@NonNull String name,
+ @NonNull Configuration compileConfiguration,
+ @NonNull Configuration packageConfiguration) {
+ this.name = name
+ this.compileConfiguration = compileConfiguration
+ this.packageConfiguration = packageConfiguration
+ }
+
+ public String getName() {
+ return name
+ }
+
+ void addLibraries(List<LibraryDependencyImpl> list) {
+ libraries.addAll(list)
+ }
+
+ void addJars(List<JarDependency> list) {
+ jars.addAll(list)
+ }
+
+ void addLocalJars(List<JarDependency> list) {
+ localJars.addAll(list)
+ }
+
+ @NonNull
+ List<LibraryDependencyImpl> getLibraries() {
+ return libraries
+ }
+
+ @NonNull
+ @Override
+ List<? extends LibraryDependency> getAndroidDependencies() {
+ return libraries
+ }
+
+ @NonNull
+ @Override
+ List<JarDependency> getJarDependencies() {
+ return jars
+ }
+
+ @NonNull
+ @Override
+ List<JarDependency> getLocalDependencies() {
+ return localJars
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/AaptOptionsImpl.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/AaptOptionsImpl.groovy
new file mode 100644
index 0000000..d50371e
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/AaptOptionsImpl.groovy
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dsl;
+
+import com.android.builder.model.AaptOptions
+import org.gradle.api.tasks.Input
+
+public class AaptOptionsImpl implements AaptOptions {
+
+ @Input
+ private String ignoreAssetsPattern
+
+ @Input
+ private List<String> noCompressList
+
+ public void setIgnoreAssetsPattern(String ignoreAssetsPattern) {
+ this.ignoreAssetsPattern = ignoreAssetsPattern
+ }
+
+ @Override
+ String getIgnoreAssets() {
+ return ignoreAssetsPattern
+ }
+
+ public void setNoCompress(String noCompress) {
+ noCompressList = Collections.singletonList(noCompress)
+ }
+
+ public void setNoCompress(String... noCompress) {
+ noCompressList = Arrays.asList(noCompress)
+ }
+
+ @Override
+ Collection<String> getNoCompress() {
+ return noCompressList
+ }
+
+ // -- DSL Methods. TODO remove once the instantiator does what I expect it to do.
+
+ public void noCompress(String noCompress) {
+ noCompressList = Collections.singletonList(noCompress)
+ }
+
+ public void noCompress(String... noCompress) {
+ noCompressList = Arrays.asList(noCompress)
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/AndroidSourceSetFactory.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/AndroidSourceSetFactory.java
new file mode 100644
index 0000000..d1a9af4
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/AndroidSourceSetFactory.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dsl;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.api.AndroidSourceSet;
+import com.android.build.gradle.internal.api.DefaultAndroidSourceSet;
+import org.gradle.api.NamedDomainObjectFactory;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.internal.reflect.Instantiator;
+
+/**
+ * Factory to create AndroidSourceSet object using an {@link Instantiator} to add
+ * the DSL methods.
+ */
+public class AndroidSourceSetFactory implements NamedDomainObjectFactory<AndroidSourceSet> {
+
+ @NonNull
+ private final Instantiator instantiator;
+ @NonNull
+ private final FileResolver fileResolver;
+
+ public AndroidSourceSetFactory(@NonNull Instantiator instantiator,
+ @NonNull FileResolver fileResolver) {
+ this.instantiator = instantiator;
+ this.fileResolver = fileResolver;
+ }
+
+ @Override
+ public AndroidSourceSet create(String name) {
+ return instantiator.newInstance(DefaultAndroidSourceSet.class, name, fileResolver);
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/BuildTypeDsl.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/BuildTypeDsl.groovy
new file mode 100644
index 0000000..6b51bc4
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/BuildTypeDsl.groovy
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dsl
+import com.android.annotations.NonNull
+import com.android.annotations.Nullable
+import com.android.annotations.VisibleForTesting
+import com.android.builder.AndroidBuilder
+import com.android.builder.BuilderConstants
+import com.android.builder.DefaultBuildType
+import com.android.builder.model.NdkConfig
+import com.android.builder.model.SigningConfig
+import org.gradle.api.Action
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.internal.reflect.Instantiator
+/**
+ * DSL overlay to make methods that accept String... work.
+ */
+public class BuildTypeDsl extends DefaultBuildType implements Serializable {
+ private static final long serialVersionUID = 1L
+
+ @NonNull
+ private final FileResolver fileResolver
+
+ private final NdkConfigDsl ndkConfig
+
+ public BuildTypeDsl(@NonNull String name,
+ @NonNull FileResolver fileResolver,
+ @NonNull Instantiator instantiator) {
+ super(name)
+ this.fileResolver = fileResolver
+ ndkConfig = instantiator.newInstance(NdkConfigDsl.class)
+ }
+
+ @VisibleForTesting
+ BuildTypeDsl(@NonNull String name,
+ @NonNull FileResolver fileResolver) {
+ super(name)
+ this.fileResolver = fileResolver
+ ndkConfig = null
+ }
+
+ @Override
+ @Nullable
+ public NdkConfig getNdkConfig() {
+ return ndkConfig;
+ }
+
+ public void init(SigningConfig debugSigningConfig) {
+ if (BuilderConstants.DEBUG.equals(getName())) {
+ setDebuggable(true)
+ setZipAlign(false)
+
+ assert debugSigningConfig != null
+ setSigningConfig(debugSigningConfig)
+ } else if (BuilderConstants.RELEASE.equals(getName())) {
+ // no config needed for now.
+ }
+ }
+
+ @Override
+ boolean equals(o) {
+ if (this.is(o)) return true
+ if (getClass() != o.class) return false
+ if (!super.equals(o)) return false
+
+ return true
+ }
+
+ // -- DSL Methods. TODO remove once the instantiator does what I expect it to do.
+
+ public void buildConfigField(
+ @NonNull String type,
+ @NonNull String name,
+ @NonNull String value) {
+ addBuildConfigField(AndroidBuilder.createClassField(type, name, value));
+ }
+
+ @NonNull
+ public BuildTypeDsl proguardFile(Object proguardFile) {
+ proguardFiles.add(fileResolver.resolve(proguardFile));
+ return this;
+ }
+
+ @NonNull
+ public BuildTypeDsl proguardFiles(Object... proguardFileArray) {
+ proguardFiles.addAll(fileResolver.resolveFiles(proguardFileArray).files);
+ return this;
+ }
+
+ @NonNull
+ public BuildTypeDsl setProguardFiles(Iterable<?> proguardFileIterable) {
+ proguardFiles.clear();
+ for (Object proguardFile : proguardFileIterable) {
+ proguardFiles.add(fileResolver.resolve(proguardFile));
+ }
+ return this;
+ }
+
+ @NonNull
+ public BuildTypeDsl consumerProguardFiles(Object... proguardFileArray) {
+ consumerProguardFiles.addAll(fileResolver.resolveFiles(proguardFileArray).files);
+ return this;
+ }
+
+ @NonNull
+ public BuildTypeDsl setConsumerProguardFiles(Iterable<?> proguardFileIterable) {
+ consumerProguardFiles.clear();
+ for (Object proguardFile : proguardFileIterable) {
+ consumerProguardFiles.add(fileResolver.resolve(proguardFile));
+ }
+ return this;
+ }
+
+ void ndk(Action<NdkConfigDsl> action) {
+ action.execute(ndkConfig)
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/BuildTypeFactory.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/BuildTypeFactory.java
new file mode 100644
index 0000000..486727e
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/BuildTypeFactory.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dsl;
+
+import com.android.annotations.NonNull;
+import com.android.builder.DefaultBuildType;
+import org.gradle.api.NamedDomainObjectFactory;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.internal.reflect.Instantiator;
+
+/**
+ * Factory to create BuildType object using an {@link Instantiator} to add the DSL methods.
+ */
+public class BuildTypeFactory implements NamedDomainObjectFactory<DefaultBuildType> {
+
+ @NonNull
+ private final Instantiator instantiator;
+ @NonNull
+ private final FileResolver fileResolver;
+
+ public BuildTypeFactory(@NonNull Instantiator instantiator,
+ @NonNull FileResolver fileResolver) {
+ this.instantiator = instantiator;
+ this.fileResolver = fileResolver;
+ }
+
+ @Override
+ public DefaultBuildType create(String name) {
+ return instantiator.newInstance(BuildTypeDsl.class, name, fileResolver, instantiator);
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/DexOptionsImpl.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/DexOptionsImpl.groovy
new file mode 100644
index 0000000..8895ca0
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/DexOptionsImpl.groovy
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dsl
+
+import com.android.builder.DexOptions
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.Optional
+
+public class DexOptionsImpl implements DexOptions {
+
+ @Input
+ private boolean coreLibraryFlag
+
+ @Input
+ private boolean isIncrementalFlag = false
+
+ @Input
+ private boolean isPreDexLibrariesFlag = true
+
+ @Input
+ private boolean isJumboModeFlag = false
+
+ @Input
+ @Optional
+ private String javaMaxHeapSize
+
+ public void setCoreLibrary(boolean coreLibrary) {
+ coreLibraryFlag = coreLibrary
+ }
+
+ @Override
+ boolean isCoreLibrary() {
+ return coreLibraryFlag
+ }
+
+ public void setIncremental(boolean isIncremental) {
+ isIncrementalFlag = isIncremental
+ }
+
+ @Override
+ boolean getIncremental() {
+ return false; // incremental support is broken.
+ //return isIncrementalFlag
+ }
+
+ @Override
+ boolean getPreDexLibraries() {
+ return isPreDexLibrariesFlag
+ }
+
+ void setPreDexLibraries(boolean flag) {
+ isPreDexLibrariesFlag = flag
+ }
+
+ public void setJumboMode(boolean flag) {
+ isJumboModeFlag = flag
+ }
+
+ @Override
+ boolean getJumboMode() {
+ return isJumboModeFlag
+ }
+
+ public void setJavaMaxHeapSize(String theJavaMaxHeapSize) {
+ if (theJavaMaxHeapSize.matches("\\d+[kKmMgGtT]?")) {
+ javaMaxHeapSize = theJavaMaxHeapSize
+ } else {
+ throw new IllegalArgumentException(
+ "Invalid max heap size DexOption. See `man java` for valid -Xmx arguments.")
+ }
+ }
+
+ @Override
+ public String getJavaMaxHeapSize() {
+ return javaMaxHeapSize
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/GroupableProductFlavorDsl.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/GroupableProductFlavorDsl.groovy
new file mode 100644
index 0000000..621ac85
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/GroupableProductFlavorDsl.groovy
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dsl
+
+import com.android.annotations.NonNull
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.internal.reflect.Instantiator
+
+/**
+ * A version of ProductFlavorDsl that can receive a group name
+ */
+public class GroupableProductFlavorDsl extends ProductFlavorDsl {
+ private static final long serialVersionUID = 1L
+
+ String flavorGroup
+
+ public GroupableProductFlavorDsl(
+ @NonNull String name,
+ @NonNull FileResolver fileResolver,
+ @NonNull Instantiator instantiator) {
+ super(name, fileResolver, instantiator)
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/GroupableProductFlavorFactory.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/GroupableProductFlavorFactory.java
new file mode 100644
index 0000000..ed422d9
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/GroupableProductFlavorFactory.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dsl;
+
+import com.android.annotations.NonNull;
+import org.gradle.api.NamedDomainObjectFactory;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.internal.reflect.Instantiator;
+
+/**
+ * Factory to create GroupableProductFlavorDsl object using an {@link Instantiator} to add
+ * the DSL methods.
+ */
+class GroupableProductFlavorFactory implements NamedDomainObjectFactory<GroupableProductFlavorDsl> {
+
+ @NonNull
+ private final Instantiator instantiator;
+ @NonNull
+ private final FileResolver fileResolver;
+
+ public GroupableProductFlavorFactory(@NonNull Instantiator instantiator,
+ @NonNull FileResolver fileResolver) {
+ this.fileResolver = fileResolver;
+ this.instantiator = instantiator;
+ }
+
+ @Override
+ public GroupableProductFlavorDsl create(String name) {
+ return instantiator.newInstance(GroupableProductFlavorDsl.class,
+ name, fileResolver, instantiator);
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/LintOptionsImpl.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/LintOptionsImpl.groovy
new file mode 100644
index 0000000..253cdd3
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/LintOptionsImpl.groovy
@@ -0,0 +1,537 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dsl
+
+import com.android.annotations.NonNull
+import com.android.annotations.Nullable;
+import com.android.builder.model.LintOptions
+import com.android.tools.lint.HtmlReporter
+import com.android.tools.lint.LintCliClient
+import com.android.tools.lint.LintCliFlags
+import com.android.tools.lint.Reporter
+import com.android.tools.lint.TextReporter
+import com.android.tools.lint.XmlReporter
+import com.google.common.collect.Sets
+import org.gradle.api.GradleException
+import org.gradle.api.Project
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.OutputFile
+
+import static com.android.SdkConstants.DOT_XML
+
+public class LintOptionsImpl implements LintOptions, Serializable {
+ public static final String STDOUT = "stdout"
+ private static final long serialVersionUID = 1L;
+
+ @Input
+ private Set<String> disable = Sets.newHashSet()
+ @Input
+ private Set<String> enable = Sets.newHashSet()
+ @Input
+ private Set<String> check = Sets.newHashSet()
+ @Input
+ private boolean abortOnError = true
+ @Input
+ private boolean absolutePaths = true
+ @Input
+ private boolean noLines
+ @Input
+ private boolean quiet = true
+ @Input
+ private boolean checkAllWarnings
+ @Input
+ private boolean ignoreWarnings
+ @Input
+ private boolean warningsAsErrors
+ @Input
+ private boolean showAll
+ @InputFile
+ private File lintConfig
+ @Input
+ private boolean textReport
+ @OutputFile
+ private File textOutput
+ @Input
+ private boolean htmlReport = true
+ @OutputFile
+ private File htmlOutput
+ @Input
+ private boolean xmlReport = true
+ @OutputFile
+ private File xmlOutput
+
+ public LintOptionsImpl() {
+ }
+
+ public LintOptionsImpl(
+ @Nullable Set<String> disable,
+ @Nullable Set<String> enable,
+ @Nullable Set<String> check,
+ @Nullable File lintConfig,
+ boolean textReport,
+ @Nullable File textOutput,
+ boolean htmlReport,
+ @Nullable File htmlOutput,
+ boolean xmlReport,
+ @Nullable File xmlOutput,
+ boolean abortOnError,
+ boolean absolutePaths,
+ boolean noLines,
+ boolean quiet,
+ boolean checkAllWarnings,
+ boolean ignoreWarnings,
+ boolean warningsAsErrors,
+ boolean showAll) {
+ this.disable = disable
+ this.enable = enable
+ this.check = check
+ this.lintConfig = lintConfig
+ this.textReport = textReport
+ this.textOutput = textOutput
+ this.htmlReport = htmlReport
+ this.htmlOutput = htmlOutput
+ this.xmlReport = xmlReport
+ this.xmlOutput = xmlOutput
+ this.abortOnError = abortOnError
+ this.absolutePaths = absolutePaths
+ this.noLines = noLines
+ this.quiet = quiet
+ this.checkAllWarnings = checkAllWarnings
+ this.ignoreWarnings = ignoreWarnings
+ this.warningsAsErrors = warningsAsErrors
+ this.showAll = showAll
+ }
+
+ @NonNull
+ static LintOptions create(@NonNull LintOptions source) {
+ return new LintOptionsImpl(
+ source.getDisable(),
+ source.getEnable(),
+ source.getCheck(),
+ source.getLintConfig(),
+ source.getTextReport(),
+ source.getTextOutput(),
+ source.getHtmlReport(),
+ source.getHtmlOutput(),
+ source.getXmlReport(),
+ source.getXmlOutput(),
+ source.isAbortOnError(),
+ source.isAbsolutePaths(),
+ source.isNoLines(),
+ source.isQuiet(),
+ source.isCheckAllWarnings(),
+ source.isIgnoreWarnings(),
+ source.isWarningsAsErrors(),
+ source.isShowAll()
+ )
+ }
+
+ /**
+ * Returns the set of issue id's to suppress. Callers are allowed to modify this collection.
+ */
+ @NonNull
+ public Set<String> getDisable() {
+ return disable
+ }
+
+ /**
+ * Sets the set of issue id's to suppress. Callers are allowed to modify this collection.
+ * Note that these ids add to rather than replace the given set of ids.
+ */
+ public void setDisable(@Nullable Set<String> ids) {
+ disable.addAll(ids)
+ }
+
+ /**
+ * Returns the set of issue id's to enable. Callers are allowed to modify this collection.
+ * To enable a given issue, add the {@link com.android.tools.lint.detector.api.Issue#getId()} to the returned set.
+ */
+ @NonNull
+ public Set<String> getEnable() {
+ return enable
+ }
+
+ /**
+ * Sets the set of issue id's to enable. Callers are allowed to modify this collection.
+ * Note that these ids add to rather than replace the given set of ids.
+ */
+ public void setEnable(@Nullable Set<String> ids) {
+ enable.addAll(ids)
+ }
+
+ /**
+ * Returns the exact set of issues to check, or null to run the issues that are enabled
+ * by default plus any issues enabled via {@link #getEnable} and without issues disabled
+ * via {@link #getDisable}. If non-null, callers are allowed to modify this collection.
+ */
+ @Nullable
+ public Set<String> getCheck() {
+ return check
+ }
+
+ /**
+ * Sets the <b>exact</b> set of issues to check.
+ * @param ids the set of issue id's to check
+ */
+ public void setCheck(@Nullable Set<String> ids) {
+ check.addAll(ids)
+ }
+
+ /** Whether lint should set the exit code of the process if errors are found */
+ public boolean isAbortOnError() {
+ return this.abortOnError
+ }
+
+ /** Sets whether lint should set the exit code of the process if errors are found */
+ public void setAbortOnError(boolean abortOnError) {
+ this.abortOnError = abortOnError
+ }
+
+ /**
+ * Whether lint should display full paths in the error output. By default the paths
+ * are relative to the path lint was invoked from.
+ */
+ public boolean isAbsolutePaths() {
+ return absolutePaths
+ }
+
+ /**
+ * Sets whether lint should display full paths in the error output. By default the paths
+ * are relative to the path lint was invoked from.
+ */
+ public void setAbsolutePaths(boolean absolutePaths) {
+ this.absolutePaths = absolutePaths
+ }
+
+ /**
+ * Whether lint should include the source lines in the output where errors occurred
+ * (true by default)
+ */
+ public boolean isNoLines() {
+ return this.noLines
+ }
+
+ /**
+ * Sets whether lint should include the source lines in the output where errors occurred
+ * (true by default)
+ */
+ public void setNoLines(boolean noLines) {
+ this.noLines = noLines
+ }
+
+ /**
+ * Returns whether lint should be quiet (for example, not show progress dots for each analyzed
+ * file)
+ */
+ public boolean isQuiet() {
+ return quiet
+ }
+
+ /**
+ * Sets whether lint should be quiet (for example, not show progress dots for each analyzed
+ * file)
+ */
+ public void setQuiet(boolean quiet) {
+ this.quiet = quiet
+ }
+
+ /** Returns whether lint should check all warnings, including those off by default */
+ public boolean isCheckAllWarnings() {
+ return checkAllWarnings
+ }
+
+ /** Sets whether lint should check all warnings, including those off by default */
+ public void setCheckAllWarnings(boolean warnAll) {
+ this.checkAllWarnings = warnAll
+ }
+
+ /** Returns whether lint will only check for errors (ignoring warnings) */
+ public boolean isIgnoreWarnings() {
+ return ignoreWarnings
+ }
+
+ /** Sets whether lint will only check for errors (ignoring warnings) */
+ public void setIgnoreWarnings(boolean noWarnings) {
+ this.ignoreWarnings = noWarnings
+ }
+
+ /** Returns whether lint should treat all warnings as errors */
+ public boolean isWarningsAsErrors() {
+ return warningsAsErrors
+ }
+
+ /** Sets whether lint should treat all warnings as errors */
+ public void setWarningsAsErrors(boolean allErrors) {
+ this.warningsAsErrors = allErrors
+ }
+
+ /**
+ * Returns whether lint should include all output (e.g. include all alternate
+ * locations, not truncating long messages, etc.)
+ */
+ public boolean isShowAll() {
+ return showAll
+ }
+
+ /**
+ * Sets whether lint should include all output (e.g. include all alternate
+ * locations, not truncating long messages, etc.)
+ */
+ public void setShowAll(boolean showAll) {
+ this.showAll = showAll
+ }
+
+ /**
+ * Returns the default configuration file to use as a fallback
+ */
+ public File getLintConfig() {
+ return lintConfig
+ }
+
+ @Override
+ boolean getTextReport() {
+ return textReport
+ }
+
+ void setTextReport(boolean textReport) {
+ this.textReport = textReport
+ }
+
+ void setTextOutput(@NonNull File textOutput) {
+ this.textOutput = textOutput
+ }
+
+ void setHtmlReport(boolean htmlReport) {
+ this.htmlReport = htmlReport
+ }
+
+ void setHtmlOutput(@NonNull File htmlOutput) {
+ this.htmlOutput = htmlOutput
+ }
+
+ void setXmlReport(boolean xmlReport) {
+ this.xmlReport = xmlReport
+ }
+
+ void setXmlOutput(@NonNull File xmlOutput) {
+ this.xmlOutput = xmlOutput
+ }
+
+ @Override
+ File getTextOutput() {
+ return textOutput
+ }
+
+ @Override
+ boolean getHtmlReport() {
+ return htmlReport
+ }
+
+ @Override
+ File getHtmlOutput() {
+ return htmlOutput
+ }
+
+ @Override
+ boolean getXmlReport() {
+ return xmlReport
+ }
+
+ @Override
+ File getXmlOutput() {
+ return xmlOutput
+ }
+
+ /**
+ * Sets the default config file to use as a fallback. This corresponds to a {@code lint.xml}
+ * file with severities etc to use when a project does not have more specific information.
+ */
+ public void setLintConfig(@NonNull File lintConfig) {
+ this.lintConfig = lintConfig
+ }
+
+ public void syncTo(
+ @NonNull LintCliClient client,
+ @NonNull LintCliFlags flags,
+ @Nullable String variantName,
+ @Nullable Project project,
+ boolean report) {
+ if (disable != null) {
+ flags.getSuppressedIds().addAll(disable)
+ }
+ if (enable != null) {
+ flags.getEnabledIds().addAll(enable)
+ }
+ if (check != null && !check.isEmpty()) {
+ flags.setExactCheckedIds(check)
+ }
+ flags.setSetExitCode(this.abortOnError)
+ flags.setFullPath(absolutePaths)
+ flags.setShowSourceLines(!noLines)
+ flags.setQuiet(quiet)
+ flags.setCheckAllWarnings(checkAllWarnings)
+ flags.setIgnoreWarnings(ignoreWarnings)
+ flags.setWarningsAsErrors(warningsAsErrors)
+ flags.setShowEverything(showAll)
+ flags.setDefaultConfiguration(lintConfig)
+
+ if (report) {
+ if (textReport) {
+ File output = textOutput
+ if (output == null) {
+ output = new File(STDOUT)
+ } else if (!output.isAbsolute() && !isStdOut(output)) {
+ output = project.file(output.getPath())
+ }
+ output = validateOutputFile(output)
+
+ Writer writer
+ File file = null
+ boolean closeWriter
+ if (isStdOut(output)) {
+ writer = new PrintWriter(System.out, true)
+ closeWriter = false
+ } else {
+ file = output
+ try {
+ writer = new BufferedWriter(new FileWriter(output))
+ } catch (IOException e) {
+ throw new GradleException("Text invalid argument.", e)
+ }
+ closeWriter = true
+ }
+ flags.getReporters().add(new TextReporter(client, flags, file, writer,
+ closeWriter))
+ }
+ if (xmlReport) {
+ File output = xmlOutput
+ if (output == null) {
+ output = createOutputPath(project, variantName, DOT_XML)
+ } else if (!output.isAbsolute()) {
+ output = project.file(output.getPath())
+ }
+ output = validateOutputFile(output)
+ try {
+ flags.getReporters().add(new XmlReporter(client, output))
+ } catch (IOException e) {
+ throw new GradleException("XML invalid argument.", e)
+ }
+ }
+ if (htmlReport) {
+ File output = htmlOutput
+ if (output == null) {
+ output = createOutputPath(project, variantName, ".html")
+ } else if (!output.isAbsolute()) {
+ output = project.file(output.getPath())
+ }
+ output = validateOutputFile(output)
+ try {
+ flags.getReporters().add(new HtmlReporter(client, output))
+ } catch (IOException e) {
+ throw new GradleException("HTML invalid argument.", e)
+ }
+ }
+
+ Map<String, String> map = new HashMap<String, String>() {{
+ put("", "file://")
+ }}
+ for (Reporter reporter : flags.getReporters()) {
+ reporter.setUrlMap(map)
+ }
+ }
+ }
+
+ private static boolean isStdOut(@NonNull File output) {
+ return STDOUT.equals(output.getPath())
+ }
+
+ @NonNull
+ private static File validateOutputFile(@NonNull File output) {
+ if (isStdOut(output)) {
+ return output
+ }
+
+ File parent = output.parentFile
+ if (!parent.exists()) {
+ parent.mkdirs()
+ }
+
+ output = output.getAbsoluteFile()
+ if (output.exists()) {
+ boolean delete = output.delete()
+ if (!delete) {
+ throw new GradleException("Could not delete old " + output)
+ }
+ }
+ if (output.getParentFile() != null && !output.getParentFile().canWrite()) {
+ throw new GradleException("Cannot write output file " + output)
+ }
+
+ return output
+ }
+
+ private static File createOutputPath(
+ @NonNull Project project,
+ @NonNull String variantName,
+ @NonNull String extension) {
+ StringBuilder base = new StringBuilder()
+ base.append("lint-results")
+ if (variantName != null) {
+ base.append("-")
+ base.append(variantName)
+ }
+ base.append(extension)
+ return new File(project.buildDir, base.toString())
+ }
+
+ // -- DSL Methods. TODO remove once the instantiator does what I expect it to do.
+
+ public void check(String id) {
+ check.add(id)
+ }
+
+ public void check(String... ids) {
+ check.addAll(ids)
+ }
+
+ public void enable(String id) {
+ enable.add(id)
+ }
+
+ public void enable(String... ids) {
+ enable.addAll(ids)
+ }
+
+ public void disable(String id) {
+ disable.add(id)
+ }
+
+ public void disable(String... ids) {
+ disable.addAll(ids)
+ }
+
+ // For textOutput 'stdout' (normally a file)
+ void textOutput(String textOutput) {
+ this.textOutput = new File(textOutput)
+ }
+
+ // For textOutput file()
+ void textOutput(File textOutput) {
+ this.textOutput = textOutput;
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/NdkConfigDsl.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/NdkConfigDsl.java
new file mode 100644
index 0000000..6b708e5
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/NdkConfigDsl.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dsl;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.NdkConfig;
+import com.google.common.collect.Sets;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.Optional;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Implementation of NdkConfig to be used in the gradle DSL.
+ */
+public class NdkConfigDsl implements NdkConfig, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private String moduleName;
+ private String cFlags;
+ private Set<String> ldLibs;
+ private Set<String> abiFilters;
+ private String stl;
+
+ public NdkConfigDsl() {
+ }
+
+ public NdkConfigDsl(@NonNull NdkConfigDsl ndkConfig) {
+ moduleName = ndkConfig.moduleName;
+ cFlags = ndkConfig.cFlags;
+ setLdLibs(ndkConfig.ldLibs);
+ setAbiFilters(ndkConfig.abiFilters);
+ }
+
+ @Override
+ @Input @Optional
+ public String getModuleName() {
+ return moduleName;
+ }
+
+ public void setModuleName(String moduleName) {
+ this.moduleName = moduleName;
+ }
+
+ @Override
+ @Input @Optional
+ public String getcFlags() {
+ return cFlags;
+ }
+
+ public void setcFlags(String cFlags) {
+ this.cFlags = cFlags;
+ }
+
+ @Override
+ @Input @Optional
+ public Set<String> getLdLibs() {
+ return ldLibs;
+ }
+
+ @NonNull
+ public NdkConfigDsl ldLibs(String lib) {
+ if (ldLibs == null) {
+ ldLibs = Sets.newHashSet();
+ }
+ ldLibs.add(lib);
+ return this;
+ }
+
+ @NonNull
+ public NdkConfigDsl ldLibs(String... libs) {
+ if (ldLibs == null) {
+ ldLibs = Sets.newHashSetWithExpectedSize(libs.length);
+ }
+ Collections.addAll(ldLibs, libs);
+ return this;
+ }
+
+ @NonNull
+ public NdkConfigDsl setLdLibs(Collection<String> libs) {
+ if (libs != null) {
+ if (abiFilters == null) {
+ abiFilters = Sets.newHashSetWithExpectedSize(libs.size());
+ } else {
+ abiFilters.clear();
+ }
+ for (String filter : libs) {
+ abiFilters.add(filter);
+ }
+ } else {
+ abiFilters = null;
+ }
+ return this;
+ }
+
+
+ @Override
+ @Input @Optional
+ public Set<String> getAbiFilters() {
+ return abiFilters;
+ }
+
+ @NonNull
+ public NdkConfigDsl abiFilter(String filter) {
+ if (abiFilters == null) {
+ abiFilters = Sets.newHashSetWithExpectedSize(2);
+ }
+ abiFilters.add(filter);
+ return this;
+ }
+
+ @NonNull
+ public NdkConfigDsl abiFilters(String... filters) {
+ if (abiFilters == null) {
+ abiFilters = Sets.newHashSetWithExpectedSize(2);
+ }
+ Collections.addAll(abiFilters, filters);
+ return this;
+ }
+
+ @NonNull
+ public NdkConfigDsl setAbiFilters(Collection<String> filters) {
+ if (filters != null) {
+ if (abiFilters == null) {
+ abiFilters = Sets.newHashSetWithExpectedSize(filters.size());
+ } else {
+ abiFilters.clear();
+ }
+ for (String filter : filters) {
+ abiFilters.add(filter);
+ }
+ } else {
+ abiFilters = null;
+ }
+ return this;
+ }
+
+ @Override
+ @Nullable
+ public String getStl() {
+ return stl;
+ }
+
+ public void setStl(String stl) {
+ this.stl = stl;
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/ProductFlavorDsl.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/ProductFlavorDsl.groovy
new file mode 100644
index 0000000..669fc8f
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/ProductFlavorDsl.groovy
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dsl
+
+import com.android.annotations.NonNull
+import com.android.annotations.Nullable
+import com.android.builder.AndroidBuilder
+import com.android.builder.DefaultProductFlavor
+import com.android.builder.model.NdkConfig
+import org.gradle.api.Action
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.internal.reflect.Instantiator
+/**
+ * DSL overlay to make methods that accept String... work.
+ */
+class ProductFlavorDsl extends DefaultProductFlavor {
+ private static final long serialVersionUID = 1L
+
+ @NonNull
+ private final FileResolver fileResolver
+
+ private final NdkConfigDsl ndkConfig
+
+ ProductFlavorDsl(@NonNull String name,
+ @NonNull FileResolver fileResolver,
+ @NonNull Instantiator instantiator) {
+ super(name)
+ this.fileResolver = fileResolver
+
+ ndkConfig = instantiator.newInstance(NdkConfigDsl.class)
+ }
+
+ @Override
+ @Nullable
+ public NdkConfig getNdkConfig() {
+ return ndkConfig;
+ }
+
+ // -- DSL Methods. TODO remove once the instantiator does what I expect it to do.
+
+ public void buildConfigField(
+ @NonNull String type,
+ @NonNull String name,
+ @NonNull String value) {
+ addBuildConfigField(AndroidBuilder.createClassField(type, name, value));
+ }
+
+ @NonNull
+ public ProductFlavorDsl proguardFile(Object proguardFile) {
+ proguardFiles.add(fileResolver.resolve(proguardFile))
+ return this
+ }
+
+ @NonNull
+ public ProductFlavorDsl proguardFiles(Object... proguardFileArray) {
+ proguardFiles.addAll(fileResolver.resolveFiles(proguardFileArray).files)
+ return this
+ }
+
+ @NonNull
+ public ProductFlavorDsl setProguardFiles(Iterable<?> proguardFileIterable) {
+ proguardFiles.clear()
+ for (Object proguardFile : proguardFileIterable) {
+ proguardFiles.add(fileResolver.resolve(proguardFile))
+ }
+ return this
+ }
+
+ @NonNull
+ public ProductFlavorDsl consumerProguardFiles(Object... proguardFileArray) {
+ consumerProguardFiles.addAll(fileResolver.resolveFiles(proguardFileArray).files)
+ return this
+ }
+
+ @NonNull
+ public ProductFlavorDsl setconsumerProguardFiles(Iterable<?> proguardFileIterable) {
+ consumerProguardFiles.clear()
+ for (Object proguardFile : proguardFileIterable) {
+ consumerProguardFiles.add(fileResolver.resolve(proguardFile))
+ }
+ return this
+ }
+
+ void ndk(Action<NdkConfigDsl> action) {
+ action.execute(ndkConfig)
+ }
+
+ void resConfig(@NonNull String config) {
+ addResourceConfiguration(config);
+ }
+
+ void resConfigs(@NonNull String... config) {
+ addResourceConfigurations(config);
+ }
+ void resConfigs(@NonNull Collection<String> config) {
+ addResourceConfigurations(config);
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/SigningConfigDsl.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/SigningConfigDsl.java
new file mode 100644
index 0000000..2960e18
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/SigningConfigDsl.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dsl;
+
+import com.android.annotations.NonNull;
+import com.android.builder.BuilderConstants;
+import com.android.builder.model.SigningConfig;
+import com.android.builder.signing.DefaultSigningConfig;
+import com.android.prefs.AndroidLocation;
+import com.google.common.base.Objects;
+import org.gradle.api.Named;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.Optional;
+import org.gradle.tooling.BuildException;
+
+import java.io.File;
+import java.io.Serializable;
+
+/**
+ * DSL overlay for {@link DefaultSigningConfig}.
+ */
+public class SigningConfigDsl extends DefaultSigningConfig implements Serializable, Named {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Creates a SigningConfig with a given name.
+ *
+ * @param name the name of the signingConfig.
+ *
+ */
+ public SigningConfigDsl(@NonNull String name) {
+ super(name);
+
+ if (BuilderConstants.DEBUG.equals(name)) {
+ try {
+ initDebug();
+ } catch (AndroidLocation.AndroidLocationException e) {
+ throw new BuildException("Failed to get default debug keystore location", e);
+ }
+ }
+ }
+
+ public SigningConfigDsl initWith(SigningConfig that) {
+ setStoreFile(that.getStoreFile());
+ setStorePassword(that.getStorePassword());
+ setKeyAlias(that.getKeyAlias());
+ setKeyPassword(that.getKeyPassword());
+ return this;
+ }
+
+ /**
+ * Store file getter override to annotate it with Gradle's input annotation.
+ */
+ @Override
+ @InputFile @Optional
+ public File getStoreFile() {
+ return super.getStoreFile();
+ }
+
+ /**
+ * Store password getter override to annotate it with Gradle's input annotation.
+ */
+ @Override
+ @Input
+ public String getStorePassword() {
+ return super.getStorePassword();
+ }
+
+ /**
+ * Key alias getter override to annotate it with Gradle's input annotation.
+ */
+ @Override
+ @Input
+ public String getKeyAlias() {
+ return super.getKeyAlias();
+ }
+
+ /**
+ * Key password getter override to annotate it with Gradle's input annotation.
+ */
+ @Override
+ @Input
+ public String getKeyPassword() {
+ return super.getKeyPassword();
+ }
+
+ /**
+ * Store Type getter override to annotate it with Gradle's input annotation.
+ */
+ @Override
+ @Input
+ public String getStoreType() {
+ return super.getStoreType();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
+
+ SigningConfigDsl that = (SigningConfigDsl) o;
+
+ if (!mName.equals(that.mName)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + mName.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this)
+ .add("name", mName)
+ .add("storeFile", getStoreFile() != null ? getStoreFile().getAbsolutePath() : "null")
+ .add("storePassword", getStorePassword())
+ .add("keyAlias", getKeyAlias())
+ .add("keyPassword", getKeyPassword())
+ .add("storeType", getStoreFile())
+ .toString();
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/SigningConfigFactory.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/SigningConfigFactory.groovy
new file mode 100644
index 0000000..cdb3ebf5
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/dsl/SigningConfigFactory.groovy
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dsl
+
+import com.android.builder.model.SigningConfig
+import org.gradle.api.NamedDomainObjectFactory
+import org.gradle.internal.reflect.Instantiator
+
+/**
+ * Factory to create SigningConfig object using an {@ling Instantiator} to add the DSL methods.
+ */
+class SigningConfigFactory implements NamedDomainObjectFactory<SigningConfig> {
+
+ final Instantiator instantiator
+
+ public SigningConfigFactory(Instantiator instantiator) {
+ this.instantiator = instantiator
+ }
+
+ @Override
+ SigningConfig create(String name) {
+ return instantiator.newInstance(SigningConfigDsl.class, name)
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/AndroidArtifactImpl.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/AndroidArtifactImpl.java
new file mode 100644
index 0000000..f27a055
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/AndroidArtifactImpl.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidArtifact;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.SourceProvider;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * Implementation of AndroidArtifact that is serializable
+ */
+public class AndroidArtifactImpl extends BaseArtifactImpl implements AndroidArtifact, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @NonNull
+ private final File outputFile;
+ private final boolean isSigned;
+ @Nullable
+ private final String signingConfigName;
+ @NonNull
+ private final String packageName;
+ @NonNull
+ private final String sourceGenTaskName;
+ @NonNull
+ private final File generatedManifest;
+ @NonNull
+ private final List<File> generatedSourceFolders;
+ @NonNull
+ private final List<File> generatedResourceFolders;
+
+ AndroidArtifactImpl(@NonNull String name,
+ @NonNull String assembleTaskName,
+ @NonNull File outputFile,
+ boolean isSigned,
+ @Nullable String signingConfigName,
+ @NonNull String packageName,
+ @NonNull String sourceGenTaskName,
+ @NonNull String javaCompileTaskName,
+ @NonNull File generatedManifest,
+ @NonNull List<File> generatedSourceFolders,
+ @NonNull List<File> generatedResourceFolders,
+ @NonNull File classesFolder,
+ @NonNull Dependencies dependencies,
+ @Nullable SourceProvider variantSourceProvider,
+ @Nullable SourceProvider multiFlavorSourceProviders) {
+ super(name, assembleTaskName, javaCompileTaskName, classesFolder, dependencies,
+ variantSourceProvider, multiFlavorSourceProviders);
+
+ this.outputFile = outputFile;
+ this.isSigned = isSigned;
+ this.signingConfigName = signingConfigName;
+ this.packageName = packageName;
+ this.sourceGenTaskName = sourceGenTaskName;
+ this.generatedManifest = generatedManifest;
+ this.generatedSourceFolders = generatedSourceFolders;
+ this.generatedResourceFolders = generatedResourceFolders;
+ }
+
+ @NonNull
+ @Override
+ public File getOutputFile() {
+ return outputFile;
+ }
+
+ @Override
+ public boolean isSigned() {
+ return isSigned;
+ }
+
+ @Nullable
+ @Override
+ public String getSigningConfigName() {
+ return signingConfigName;
+ }
+
+ @NonNull
+ @Override
+ public String getPackageName() {
+ return packageName;
+ }
+
+ @NonNull
+ @Override
+ public String getSourceGenTaskName() {
+ return sourceGenTaskName;
+ }
+
+ @NonNull
+ @Override
+ public File getGeneratedManifest() {
+ return generatedManifest;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getGeneratedSourceFolders() {
+ return generatedSourceFolders;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getGeneratedResourceFolders() {
+ return generatedResourceFolders;
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/AndroidLibraryImpl.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/AndroidLibraryImpl.java
new file mode 100644
index 0000000..8635ce8
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/AndroidLibraryImpl.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.dependency.LibraryDependency;
+import com.android.builder.model.AndroidLibrary;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.List;
+
+public class AndroidLibraryImpl implements AndroidLibrary, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @Nullable
+ private final String project;
+ @NonNull
+ private final File bundle;
+ @NonNull
+ private final File folder;
+ @NonNull
+ private final File manifest;
+ @NonNull
+ private final File jarFile;
+ @NonNull
+ private final Collection<File> localJars;
+ @NonNull
+ private final File resFolder;
+ @NonNull
+ private final File assetsFolder;
+ @NonNull
+ private final File jniFolder;
+ @NonNull
+ private final File aidlFolder;
+ @NonNull
+ private final File renderscriptFolder;
+ @NonNull
+ private final File proguardRules;
+ @NonNull
+ private final File lintJar;
+ @NonNull
+ private final List<AndroidLibrary> dependencies;
+
+ AndroidLibraryImpl(@NonNull LibraryDependency libraryDependency,
+ @NonNull List<AndroidLibrary> dependencies,
+ @Nullable String project) {
+ this.dependencies = dependencies;
+ bundle = libraryDependency.getBundle();
+ folder = libraryDependency.getFolder();
+ manifest = libraryDependency.getManifest();
+ jarFile = libraryDependency.getJarFile();
+ localJars = libraryDependency.getLocalJars();
+ resFolder = libraryDependency.getResFolder();
+ assetsFolder = libraryDependency.getAssetsFolder();
+ jniFolder = libraryDependency.getJniFolder();
+ aidlFolder = libraryDependency.getAidlFolder();
+ renderscriptFolder = libraryDependency.getRenderscriptFolder();
+ proguardRules = libraryDependency.getProguardRules();
+ lintJar = libraryDependency.getLintJar();
+
+ this.project = project;
+ }
+
+ @Nullable
+ @Override
+ public String getProject() {
+ return project;
+ }
+
+ @NonNull
+ @Override
+ public File getBundle() {
+ return bundle;
+ }
+
+ @NonNull
+ @Override
+ public File getFolder() {
+ return folder;
+ }
+
+ @NonNull
+ @Override
+ public List<? extends AndroidLibrary> getLibraryDependencies() {
+ return dependencies;
+ }
+
+ @NonNull
+ @Override
+ public File getManifest() {
+ return manifest;
+ }
+
+ @NonNull
+ @Override
+ public File getJarFile() {
+ return jarFile;
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getLocalJars() {
+ return localJars;
+ }
+
+ @NonNull
+ @Override
+ public File getResFolder() {
+ return resFolder;
+ }
+
+ @NonNull
+ @Override
+ public File getAssetsFolder() {
+ return assetsFolder;
+ }
+
+ @NonNull
+ @Override
+ public File getJniFolder() {
+ return jniFolder;
+ }
+
+ @NonNull
+ @Override
+ public File getAidlFolder() {
+ return aidlFolder;
+ }
+
+ @NonNull
+ @Override
+ public File getRenderscriptFolder() {
+ return renderscriptFolder;
+ }
+
+ @NonNull
+ @Override
+ public File getProguardRules() {
+ return proguardRules;
+ }
+
+ @NonNull
+ @Override
+ public File getLintJar() {
+ return lintJar;
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/ArtifactMetaDataImpl.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/ArtifactMetaDataImpl.java
new file mode 100644
index 0000000..8f33c2d
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/ArtifactMetaDataImpl.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.model;
+
+import com.android.annotations.NonNull;
+import com.android.builder.model.ArtifactMetaData;
+
+import java.io.Serializable;
+
+/**
+ * Implementation of ArtifactMetaData that is serializable
+ */
+public class ArtifactMetaDataImpl implements ArtifactMetaData, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @NonNull
+ private final String name;
+ private final boolean isTest;
+ private final int type;
+
+ public ArtifactMetaDataImpl(@NonNull String name, boolean isTest, int type) {
+ this.name = name;
+ this.isTest = isTest;
+ this.type = type;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public boolean isTest() {
+ return isTest;
+ }
+
+ @Override
+ public int getType() {
+ return type;
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/BaseArtifactImpl.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/BaseArtifactImpl.java
new file mode 100644
index 0000000..65c0c87
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/BaseArtifactImpl.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.BaseArtifact;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.SourceProvider;
+
+import java.io.File;
+import java.io.Serializable;
+
+/**
+ * Implementation of BaseArtifact that is serializable
+ */
+class BaseArtifactImpl implements BaseArtifact, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private final String name;
+ @NonNull
+ private final String assembleTaskName;
+ @NonNull
+ private final String javaCompileTaskName;
+ @NonNull
+ private final File classesFolder;
+ @NonNull
+ private final Dependencies dependencies;
+ @Nullable
+ private final SourceProvider variantSourceProvider;
+ @Nullable
+ private final SourceProvider multiFlavorSourceProviders;
+
+
+ BaseArtifactImpl(@NonNull String name,
+ @NonNull String assembleTaskName,
+ @NonNull String javaCompileTaskName,
+ @NonNull File classesFolder,
+ @NonNull Dependencies dependencies,
+ @Nullable SourceProvider variantSourceProvider,
+ @Nullable SourceProvider multiFlavorSourceProviders) {
+ this.name = name;
+ this.assembleTaskName = assembleTaskName;
+ this.javaCompileTaskName = javaCompileTaskName;
+ this.classesFolder = classesFolder;
+ this.dependencies = dependencies;
+ this.variantSourceProvider = variantSourceProvider;
+ this.multiFlavorSourceProviders = multiFlavorSourceProviders;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @NonNull
+ @Override
+ public String getJavaCompileTaskName() {
+ return javaCompileTaskName;
+ }
+
+ @NonNull
+ @Override
+ public String getAssembleTaskName() {
+ return assembleTaskName;
+ }
+
+ @NonNull
+ @Override
+ public File getClassesFolder() {
+ return classesFolder;
+ }
+
+ @NonNull
+ @Override
+ public Dependencies getDependencies() {
+ return dependencies;
+ }
+
+ @Nullable
+ @Override
+ public SourceProvider getVariantSourceProvider() {
+ return variantSourceProvider;
+ }
+
+ @Nullable
+ @Override
+ public SourceProvider getMultiFlavorSourceProvider() {
+ return multiFlavorSourceProviders;
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/BuildTypeContainerImpl.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/BuildTypeContainerImpl.java
new file mode 100644
index 0000000..a4c302f
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/BuildTypeContainerImpl.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.model;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.BuildTypeData;
+import com.android.builder.model.BuildType;
+import com.android.builder.model.BuildTypeContainer;
+import com.android.builder.model.SourceProvider;
+import com.android.builder.model.SourceProviderContainer;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+class BuildTypeContainerImpl implements BuildTypeContainer, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @NonNull
+ private final BuildType buildType;
+ @NonNull
+ private final SourceProvider sourceProvider;
+ @NonNull
+ private final Collection<SourceProviderContainer> extraSourceProviders;
+
+ /**
+ * Create a BuildTypeContainer from a BuildTypeData
+ *
+ * @param buildTypeData the build type data
+ * @param sourceProviderContainers collection of extra source providers
+ *
+ * @return a non-null BuildTypeContainer
+ */
+ @NonNull
+ static BuildTypeContainer createBTC(
+ @NonNull BuildTypeData buildTypeData,
+ @NonNull Collection<SourceProviderContainer> sourceProviderContainers) {
+
+ return new BuildTypeContainerImpl(
+ BuildTypeImpl.cloneBuildType(buildTypeData.getBuildType()),
+ SourceProviderImpl.cloneProvider(buildTypeData.getSourceSet()),
+ SourceProviderContainerImpl.cloneCollection(sourceProviderContainers));
+ }
+
+ private BuildTypeContainerImpl(
+ @NonNull BuildTypeImpl buildType,
+ @NonNull SourceProviderImpl sourceProvider,
+ @NonNull Collection<SourceProviderContainer> extraSourceProviders) {
+ this.buildType = buildType;
+ this.sourceProvider = sourceProvider;
+ this.extraSourceProviders = extraSourceProviders;
+ }
+
+ @Override
+ @NonNull
+ public BuildType getBuildType() {
+ return buildType;
+ }
+
+ @Override
+ @NonNull
+ public SourceProvider getSourceProvider() {
+ return sourceProvider;
+ }
+
+ @NonNull
+ @Override
+ public Collection<SourceProviderContainer> getExtraSourceProviders() {
+ return extraSourceProviders;
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/BuildTypeImpl.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/BuildTypeImpl.java
new file mode 100644
index 0000000..e3f5cdc
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/BuildTypeImpl.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.BuildType;
+import com.android.builder.model.ClassField;
+import com.android.builder.model.NdkConfig;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Implementation of BuildType that is serializable. Objects used in the DSL cannot be
+ * serialized.
+ */
+class BuildTypeImpl implements BuildType, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private String name;
+ private boolean debuggable;
+ private boolean jniDebugBuild;
+ private boolean renderscriptDebugBuild;
+ private int renderscriptOptimLevel;
+ private String packageNameSuffix;
+ private String versionNameSuffix;
+ private boolean runProguard;
+ private boolean zipAlign;
+
+ @NonNull
+ static BuildTypeImpl cloneBuildType(BuildType buildType) {
+ BuildTypeImpl clonedBuildType = new BuildTypeImpl();
+ clonedBuildType.name = buildType.getName();
+ clonedBuildType.debuggable = buildType.isDebuggable();
+ clonedBuildType.jniDebugBuild = buildType.isJniDebugBuild();
+ clonedBuildType.renderscriptDebugBuild = buildType.isRenderscriptDebugBuild();
+ clonedBuildType.renderscriptOptimLevel = buildType.getRenderscriptOptimLevel();
+ clonedBuildType.packageNameSuffix = buildType.getPackageNameSuffix();
+ clonedBuildType.versionNameSuffix = buildType.getVersionNameSuffix();
+ clonedBuildType.runProguard = buildType.isRunProguard();
+ clonedBuildType.zipAlign = buildType.isZipAlign();
+
+ return clonedBuildType;
+ }
+
+ private BuildTypeImpl() {
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public boolean isDebuggable() {
+ return debuggable;
+ }
+
+ @Override
+ public boolean isJniDebugBuild() {
+ return jniDebugBuild;
+ }
+
+ @Override
+ public boolean isRenderscriptDebugBuild() {
+ return renderscriptDebugBuild;
+ }
+
+ @Override
+ public int getRenderscriptOptimLevel() {
+ return renderscriptOptimLevel;
+ }
+
+ @Nullable
+ @Override
+ public String getPackageNameSuffix() {
+ return packageNameSuffix;
+ }
+
+ @Nullable
+ @Override
+ public String getVersionNameSuffix() {
+ return versionNameSuffix;
+ }
+
+ @Override
+ public boolean isRunProguard() {
+ return runProguard;
+ }
+
+ @Override
+ public boolean isZipAlign() {
+ return zipAlign;
+ }
+
+ @NonNull
+ @Override
+ public List<ClassField> getBuildConfigFields() {
+ return Collections.emptyList();
+ }
+
+ @NonNull
+ @Override
+ public List<File> getProguardFiles() {
+ return Collections.emptyList();
+ }
+
+ @NonNull
+ @Override
+ public List<File> getConsumerProguardFiles() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ @Nullable
+ public NdkConfig getNdkConfig() {
+ return null;
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/DefaultAndroidProject.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/DefaultAndroidProject.java
new file mode 100644
index 0000000..b8f7ec2
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/DefaultAndroidProject.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.model;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.CompileOptions;
+import com.android.build.gradle.internal.dsl.LintOptionsImpl;
+import com.android.builder.model.AaptOptions;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.ArtifactMetaData;
+import com.android.builder.model.BuildTypeContainer;
+import com.android.builder.model.JavaCompileOptions;
+import com.android.builder.model.LintOptions;
+import com.android.builder.model.ProductFlavorContainer;
+import com.android.builder.model.SigningConfig;
+import com.android.builder.model.Variant;
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.Collection;
+
+/**
+ * Implementation of the AndroidProject model object.
+ */
+class DefaultAndroidProject implements AndroidProject, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @NonNull
+ private final String modelVersion;
+ @NonNull
+ private final String name;
+ @NonNull
+ private final String compileTarget;
+ @NonNull
+ private final Collection<String> bootClasspath;
+ @NonNull
+ private final Collection<File> frameworkSource;
+ @NonNull
+ private final Collection<SigningConfig> signingConfigs;
+ @NonNull
+ private final Collection<ArtifactMetaData> extraArtifacts;
+ @NonNull
+ private final Collection<String> unresolvedDependencies;
+ @NonNull
+ private final JavaCompileOptions javaCompileOptions;
+ @NonNull
+ private final LintOptions lintOptions;
+ private final boolean isLibrary;
+
+ private final Collection<BuildTypeContainer> buildTypes = Lists.newArrayList();
+ private final Collection<ProductFlavorContainer> productFlavors = Lists.newArrayList();
+ private final Collection<Variant> variants = Lists.newArrayList();
+
+ private ProductFlavorContainer defaultConfig;
+
+ DefaultAndroidProject(@NonNull String modelVersion,
+ @NonNull String name,
+ @NonNull String compileTarget,
+ @NonNull Collection<String> bootClasspath,
+ @NonNull Collection<File> frameworkSource,
+ @NonNull Collection<SigningConfig> signingConfigs,
+ @NonNull Collection<ArtifactMetaData> extraArtifacts,
+ @NonNull Collection<String> unresolvedDependencies,
+ @NonNull CompileOptions compileOptions,
+ @NonNull LintOptions lintOptions,
+ boolean isLibrary) {
+ this.modelVersion = modelVersion;
+ this.name = name;
+ this.compileTarget = compileTarget;
+ this.bootClasspath = bootClasspath;
+ this.frameworkSource = frameworkSource;
+ this.signingConfigs = signingConfigs;
+ this.extraArtifacts = extraArtifacts;
+ this.unresolvedDependencies = unresolvedDependencies;
+ javaCompileOptions = new DefaultJavaCompileOptions(compileOptions);
+ this.lintOptions = lintOptions;
+ this.isLibrary = isLibrary;
+ }
+
+ @NonNull
+ DefaultAndroidProject setDefaultConfig(@NonNull ProductFlavorContainer defaultConfigContainer) {
+ defaultConfig = defaultConfigContainer;
+ return this;
+ }
+
+ @NonNull
+ DefaultAndroidProject addBuildType(@NonNull BuildTypeContainer buildTypeContainer) {
+ buildTypes.add(buildTypeContainer);
+ return this;
+ }
+
+ @NonNull
+ DefaultAndroidProject addProductFlavors(
+ @NonNull ProductFlavorContainer productFlavorContainer) {
+ productFlavors.add(productFlavorContainer);
+ return this;
+ }
+
+ @NonNull
+ DefaultAndroidProject addVariant(@NonNull VariantImpl variant) {
+ variants.add(variant);
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public String getModelVersion() {
+ return modelVersion;
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ @NonNull
+ public ProductFlavorContainer getDefaultConfig() {
+ return defaultConfig;
+ }
+
+ @Override
+ @NonNull
+ public Collection<BuildTypeContainer> getBuildTypes() {
+ return buildTypes;
+ }
+
+ @Override
+ @NonNull
+ public Collection<ProductFlavorContainer> getProductFlavors() {
+ return productFlavors;
+ }
+
+ @Override
+ @NonNull
+ public Collection<Variant> getVariants() {
+ return variants;
+ }
+
+ @NonNull
+ @Override
+ public Collection<ArtifactMetaData> getExtraArtifacts() {
+ return extraArtifacts;
+ }
+
+ @Override
+ public boolean isLibrary() {
+ return isLibrary;
+ }
+
+ @Override
+ @NonNull
+ public String getCompileTarget() {
+ return compileTarget;
+ }
+
+ @Override
+ @NonNull
+ public Collection<String> getBootClasspath() {
+ return bootClasspath;
+ }
+
+ @Override
+ @NonNull
+ public Collection<File> getFrameworkSources() {
+ return frameworkSource;
+ }
+
+ @Override
+ @NonNull
+ public Collection<SigningConfig> getSigningConfigs() {
+ return signingConfigs;
+ }
+
+ @Override
+ @NonNull
+ public AaptOptions getAaptOptions() {
+ return null;
+ }
+
+ @Override
+ @NonNull
+ public LintOptions getLintOptions() {
+ return lintOptions;
+ }
+
+ @Override
+ @NonNull
+ public Collection<String> getUnresolvedDependencies() {
+ return unresolvedDependencies;
+ }
+
+ @Override
+ @NonNull
+ public JavaCompileOptions getJavaCompileOptions() {
+ return javaCompileOptions;
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/DefaultJavaCompileOptions.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/DefaultJavaCompileOptions.java
new file mode 100644
index 0000000..4de4a89
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/DefaultJavaCompileOptions.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.model;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.CompileOptions;
+import com.android.builder.model.JavaCompileOptions;
+
+import java.io.Serializable;
+
+/**
+ * Implementation of {@link JavaCompileOptions}.
+ */
+class DefaultJavaCompileOptions implements JavaCompileOptions, Serializable {
+ @NonNull
+ private final String sourceCompatibility;
+ @NonNull
+ private final String targetCompatibility;
+
+ DefaultJavaCompileOptions(@NonNull CompileOptions options) {
+ sourceCompatibility = options.getSourceCompatibility().toString();
+ targetCompatibility = options.getTargetCompatibility().toString();
+ }
+
+ @NonNull
+ @Override
+ public String getSourceCompatibility() {
+ return sourceCompatibility;
+ }
+
+ @NonNull
+ @Override
+ public String getTargetCompatibility() {
+ return targetCompatibility;
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/DependenciesImpl.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/DependenciesImpl.java
new file mode 100644
index 0000000..fd8f5bf
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/DependenciesImpl.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.internal.dependency.LibraryDependencyImpl;
+import com.android.build.gradle.internal.dependency.VariantDependencies;
+import com.android.builder.model.Dependencies;
+import com.android.builder.dependency.JarDependency;
+import com.android.builder.dependency.LibraryDependency;
+import com.android.builder.model.AndroidLibrary;
+import com.google.common.collect.Lists;
+import org.gradle.api.Project;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ */
+public class DependenciesImpl implements Dependencies, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @NonNull
+ private final List<AndroidLibrary> libraries;
+ @NonNull
+ private final List<File> jars;
+ @NonNull
+ private final List<String> projects;
+
+ @NonNull
+ static DependenciesImpl cloneDependencies(
+ @Nullable VariantDependencies variantDependencies,
+ @NonNull Set<Project> gradleProjects) {
+
+ List<AndroidLibrary> libraries;
+ List<File> jars;
+ List<String> projects;
+
+ if (variantDependencies != null) {
+ List<LibraryDependencyImpl> libs = variantDependencies.getLibraries();
+ libraries = Lists.newArrayListWithCapacity(libs.size());
+ for (LibraryDependencyImpl libImpl : libs) {
+ AndroidLibrary clonedLib = getAndroidLibrary(libImpl, gradleProjects);
+ libraries.add(clonedLib);
+ }
+
+ List<JarDependency> jarDeps = variantDependencies.getJarDependencies();
+ List<JarDependency> localDeps = variantDependencies.getLocalDependencies();
+
+ jars = Lists.newArrayListWithExpectedSize(jarDeps.size() + localDeps.size());
+ projects = Lists.newArrayList();
+
+ for (JarDependency jarDep : jarDeps) {
+ File jarFile = jarDep.getJarFile();
+ Project projectMatch = getProject(jarFile, gradleProjects);
+ if (projectMatch != null) {
+ projects.add(projectMatch.getPath());
+ } else {
+ jars.add(jarFile);
+ }
+ }
+ for (JarDependency jarDep : localDeps) {
+ jars.add(jarDep.getJarFile());
+ }
+ } else {
+ libraries = Collections.emptyList();
+ jars = Collections.emptyList();
+ projects = Collections.emptyList();
+ }
+
+ return new DependenciesImpl(libraries, jars, projects);
+ }
+
+ private DependenciesImpl(@NonNull List<AndroidLibrary> libraries,
+ @NonNull List<File> jars,
+ @NonNull List<String> projects) {
+ this.libraries = libraries;
+ this.jars = jars;
+ this.projects = projects;
+ }
+
+ @NonNull
+ @Override
+ public List<AndroidLibrary> getLibraries() {
+ return libraries;
+ }
+
+ @NonNull
+ @Override
+ public List<File> getJars() {
+ return jars;
+ }
+
+ @NonNull
+ @Override
+ public List<String> getProjects() {
+ return projects;
+ }
+
+ @NonNull
+ private static AndroidLibrary getAndroidLibrary(@NonNull LibraryDependency libImpl,
+ @NonNull Set<Project> gradleProjects) {
+ File bundle = libImpl.getBundle();
+ Project projectMatch = getProject(bundle, gradleProjects);
+
+ List<LibraryDependency> deps = libImpl.getDependencies();
+ List<AndroidLibrary> clonedDeps = Lists.newArrayListWithCapacity(deps.size());
+ for (LibraryDependency child : deps) {
+ AndroidLibrary clonedLib = getAndroidLibrary(child, gradleProjects);
+ clonedDeps.add(clonedLib);
+ }
+
+ return new AndroidLibraryImpl(libImpl, clonedDeps,
+ projectMatch != null ? projectMatch.getPath() : null);
+ }
+
+ @Nullable
+ private static Project getProject(File outputFile, Set<Project> gradleProjects) {
+ // search for a project that contains this file in its output folder.
+ Project projectMatch = null;
+ for (Project project : gradleProjects) {
+ File buildDir = project.getBuildDir();
+ if (contains(buildDir, outputFile)) {
+ projectMatch = project;
+ break;
+ }
+ }
+ return projectMatch;
+ }
+
+ private static boolean contains(@NonNull File dir, @NonNull File file) {
+ try {
+ dir = dir.getCanonicalFile();
+ file = file.getCanonicalFile();
+ } catch (IOException e) {
+ return false;
+ }
+
+ // quick fail
+ return file.getAbsolutePath().startsWith(dir.getAbsolutePath()) && doContains(dir, file);
+ }
+
+ private static boolean doContains(@NonNull File dir, @NonNull File file) {
+ File parent = file.getParentFile();
+ return parent != null && (parent.equals(dir) || doContains(dir, parent));
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/JavaArtifactImpl.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/JavaArtifactImpl.java
new file mode 100644
index 0000000..4c7584c
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/JavaArtifactImpl.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.Dependencies;
+import com.android.builder.model.JavaArtifact;
+import com.android.builder.model.SourceProvider;
+
+import java.io.File;
+import java.io.Serializable;
+
+/**
+ * Implementation of JavaArtifact that is serializable
+ */
+public class JavaArtifactImpl extends BaseArtifactImpl implements JavaArtifact, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ public static JavaArtifactImpl clone(@NonNull JavaArtifact javaArtifact) {
+ return new JavaArtifactImpl(
+ javaArtifact.getName(),
+ javaArtifact.getAssembleTaskName(),
+ javaArtifact.getJavaCompileTaskName(),
+ javaArtifact.getClassesFolder(),
+ javaArtifact.getDependencies(), // TODO:FixME
+ SourceProviderImpl.cloneProvider(javaArtifact.getVariantSourceProvider()),
+ SourceProviderImpl.cloneProvider(javaArtifact.getMultiFlavorSourceProvider()));
+ }
+
+ public JavaArtifactImpl(@NonNull String name,
+ @NonNull String assembleTaskName,
+ @NonNull String javaCompileTaskName,
+ @NonNull File classesFolder,
+ @NonNull Dependencies dependencies,
+ @Nullable SourceProvider variantSourceProvider,
+ @Nullable SourceProvider multiFlavorSourceProviders) {
+ super(name, assembleTaskName, javaCompileTaskName, classesFolder, dependencies,
+ variantSourceProvider, multiFlavorSourceProviders);
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/ModelBuilder.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/ModelBuilder.groovy
new file mode 100644
index 0000000..e04678a
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/ModelBuilder.groovy
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.model
+import com.android.annotations.NonNull
+import com.android.annotations.Nullable
+import com.android.build.gradle.AppPlugin
+import com.android.build.gradle.BasePlugin
+import com.android.build.gradle.LibraryPlugin
+import com.android.build.gradle.internal.BuildTypeData
+import com.android.build.gradle.internal.ProductFlavorData
+import com.android.build.gradle.internal.dsl.LintOptionsImpl
+import com.android.build.gradle.internal.variant.ApplicationVariantData
+import com.android.build.gradle.internal.variant.BaseVariantData
+import com.android.build.gradle.internal.variant.LibraryVariantData
+import com.android.build.gradle.internal.variant.TestVariantData
+import com.android.builder.DefaultProductFlavor
+import com.android.builder.SdkParser
+import com.android.builder.VariantConfiguration
+import com.android.builder.model.AndroidArtifact
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.ArtifactMetaData
+import com.android.builder.model.JavaArtifact
+import com.android.builder.model.LintOptions
+import com.android.builder.model.SigningConfig
+import com.android.builder.model.SourceProvider
+import com.android.builder.model.SourceProviderContainer
+import com.google.common.collect.Lists
+import org.gradle.api.Project
+import org.gradle.api.plugins.UnknownPluginException
+import org.gradle.tooling.provider.model.ToolingModelBuilder
+
+import java.util.jar.Attributes
+import java.util.jar.Manifest
+
+import static com.android.builder.model.AndroidProject.ARTIFACT_INSTRUMENT_TEST
+import static com.android.builder.model.AndroidProject.ARTIFACT_MAIN
+
+/**
+ * Builder for the custom Android model.
+ */
+public class ModelBuilder implements ToolingModelBuilder {
+ @Override
+ public boolean canBuild(String modelName) {
+ // The default name for a model is the name of the Java interface
+ return modelName.equals(AndroidProject.class.getName())
+ }
+
+ @Override
+ public Object buildAll(String modelName, Project project) {
+ AppPlugin appPlugin = getPlugin(project, AppPlugin.class)
+ LibraryPlugin libPlugin = null
+ BasePlugin basePlugin = appPlugin
+
+ Collection<SigningConfig> signingConfigs
+
+ if (appPlugin == null) {
+ basePlugin = libPlugin = getPlugin(project, LibraryPlugin.class)
+ } else {
+ signingConfigs = appPlugin.extension.signingConfigs
+ }
+
+ if (basePlugin == null) {
+ project.logger.error("Failed to find Android plugin for project " + project.name)
+ return null
+ }
+
+ if (libPlugin != null) {
+ signingConfigs = Collections.singletonList(libPlugin.extension.debugSigningConfig)
+ }
+
+ SdkParser sdkParser = basePlugin.getLoadedSdkParser()
+ List<String> bootClasspath = basePlugin.runtimeJarList
+ List<File> frameworkSource = Collections.emptyList();
+ String compileTarget = sdkParser.target.hashString()
+
+ // list of extra artifacts
+ List<ArtifactMetaData> artifactMetaDataList = Lists.newArrayList(basePlugin.extraArtifacts)
+ // plus the instrumentation test one.
+ artifactMetaDataList.add(
+ new ArtifactMetaDataImpl(
+ ARTIFACT_INSTRUMENT_TEST,
+ true /*isTest*/,
+ ArtifactMetaData.TYPE_ANDROID));
+
+ LintOptions lintOptions = LintOptionsImpl.create(basePlugin.extension.lintOptions)
+
+ //noinspection GroovyVariableNotAssigned
+ DefaultAndroidProject androidProject = new DefaultAndroidProject(
+ getModelVersion(),
+ project.name,
+ compileTarget,
+ bootClasspath,
+ frameworkSource,
+ cloneSigningConfigs(signingConfigs),
+ artifactMetaDataList,
+ basePlugin.unresolvedDependencies,
+ basePlugin.extension.compileOptions,
+ lintOptions,
+ libPlugin != null)
+ .setDefaultConfig(ProductFlavorContainerImpl.createPFC(
+ basePlugin.defaultConfigData,
+ basePlugin.getExtraFlavorSourceProviders(basePlugin.defaultConfigData.productFlavor.name)))
+
+ if (appPlugin != null) {
+ for (BuildTypeData btData : appPlugin.buildTypes.values()) {
+ androidProject.addBuildType(BuildTypeContainerImpl.createBTC(
+ btData,
+ basePlugin.getExtraBuildTypeSourceProviders(btData.buildType.name)))
+ }
+ for (ProductFlavorData pfData : appPlugin.productFlavors.values()) {
+ androidProject.addProductFlavors(ProductFlavorContainerImpl.createPFC(
+ pfData,
+ basePlugin.getExtraFlavorSourceProviders(pfData.productFlavor.name)))
+ }
+
+ } else if (libPlugin != null) {
+ androidProject.addBuildType(BuildTypeContainerImpl.createBTC(
+ libPlugin.debugBuildTypeData,
+ basePlugin.getExtraBuildTypeSourceProviders(libPlugin.debugBuildTypeData.buildType.name)))
+ .addBuildType(BuildTypeContainerImpl.createBTC(
+ libPlugin.releaseBuildTypeData,
+ basePlugin.getExtraBuildTypeSourceProviders(libPlugin.releaseBuildTypeData.buildType.name)))
+ }
+
+ Set<Project> gradleProjects = project.getRootProject().getAllprojects();
+
+ for (BaseVariantData variantData : basePlugin.variantDataList) {
+ if (!(variantData instanceof TestVariantData)) {
+ androidProject.addVariant(createVariant(variantData, basePlugin, gradleProjects))
+ }
+ }
+
+ return androidProject
+ }
+
+ @NonNull
+ private static String getModelVersion() {
+ Class clazz = AndroidProject.class
+ String className = clazz.getSimpleName() + ".class"
+ String classPath = clazz.getResource(className).toString()
+ if (!classPath.startsWith("jar")) {
+ // Class not from JAR, unlikely
+ return "unknown"
+ }
+ String manifestPath = classPath.substring(0, classPath.lastIndexOf("!") + 1) +
+ "/META-INF/MANIFEST.MF"
+ Manifest manifest = new Manifest(new URL(manifestPath).openStream())
+ Attributes attr = manifest.getMainAttributes()
+ String version = attr.getValue("Model-Version")
+ if (version != null) {
+ return version
+ }
+
+ return "unknown"
+ }
+
+ @NonNull
+ private static VariantImpl createVariant(@NonNull BaseVariantData variantData,
+ @NonNull BasePlugin basePlugin,
+ @NonNull Set<Project> gradleProjects) {
+ TestVariantData testVariantData = null
+ if (variantData instanceof ApplicationVariantData ||
+ variantData instanceof LibraryVariantData) {
+ testVariantData = variantData.testVariantData
+ }
+
+ AndroidArtifact mainArtifact = createArtifactInfo(
+ ARTIFACT_MAIN, variantData, basePlugin, gradleProjects)
+
+ String variantName = variantData.variantConfiguration.fullName
+
+ // extra Android Artifacts
+ AndroidArtifact testArtifact = testVariantData != null ?
+ createArtifactInfo(ARTIFACT_INSTRUMENT_TEST, testVariantData, basePlugin, gradleProjects) : null
+
+ List<AndroidArtifact> extraAndroidArtifacts = Lists.newArrayList(
+ basePlugin.getExtraAndroidArtifacts(variantName))
+ if (testArtifact != null) {
+ extraAndroidArtifacts.add(testArtifact)
+ }
+
+ // extra Java Artifacts
+ List<JavaArtifact> extraJavaArtifacts = Lists.newArrayList(
+ basePlugin.getExtraJavaArtifacts(variantName))
+
+ VariantImpl variant = new VariantImpl(
+ variantName,
+ variantData.variantConfiguration.baseName,
+ variantData.variantConfiguration.buildType.name,
+ getProductFlavorNames(variantData),
+ ProductFlavorImpl.cloneFlavor(variantData.variantConfiguration.mergedFlavor),
+ mainArtifact,
+ extraAndroidArtifacts,
+ extraJavaArtifacts)
+
+ return variant
+ }
+
+ private static AndroidArtifact createArtifactInfo(
+ @NonNull String name,
+ @NonNull BaseVariantData variantData,
+ @NonNull BasePlugin basePlugin,
+ @NonNull Set<Project> gradleProjects) {
+ VariantConfiguration vC = variantData.variantConfiguration
+
+ SigningConfig signingConfig = vC.signingConfig
+ String signingConfigName = null
+ if (signingConfig != null) {
+ signingConfigName = signingConfig.name
+ }
+
+ SourceProvider variantSourceProvider = null;
+ SourceProvider multiFlavorSourceProvider = null;
+
+ if (ARTIFACT_MAIN.equals(name)) {
+ variantSourceProvider = variantData.variantConfiguration.variantSourceProvider
+ multiFlavorSourceProvider = variantData.variantConfiguration.multiFlavorSourceProvider
+ } else {
+ SourceProviderContainer container = getSourceProviderContainer(
+ basePlugin.getExtraVariantSourceProviders(variantData.getVariantConfiguration().getFullName()),
+ name)
+ if (container != null) {
+ variantSourceProvider = container.sourceProvider
+ }
+ }
+
+ variantSourceProvider = variantSourceProvider != null ? SourceProviderImpl.cloneProvider(variantSourceProvider) : null
+ multiFlavorSourceProvider = multiFlavorSourceProvider != null ? SourceProviderImpl.cloneProvider(multiFlavorSourceProvider) : null
+
+ return new AndroidArtifactImpl(
+ name,
+ variantData.assembleTask.name,
+ variantData.outputFile,
+ vC.isSigningReady(),
+ signingConfigName,
+ vC.packageName,
+ variantData.sourceGenTask.name,
+ variantData.javaCompileTask.name,
+ variantData.processManifestTask.manifestOutputFile,
+ getGeneratedSourceFolders(variantData),
+ getGeneratedResourceFolders(variantData),
+ variantData.javaCompileTask.destinationDir,
+ DependenciesImpl.cloneDependencies(variantData.variantDependency, gradleProjects),
+ variantSourceProvider,
+ multiFlavorSourceProvider)
+ }
+
+ @NonNull
+ private static List<String> getProductFlavorNames(@NonNull BaseVariantData variantData) {
+ List<String> flavorNames = Lists.newArrayList()
+
+ for (DefaultProductFlavor flavor : variantData.variantConfiguration.flavorConfigs) {
+ flavorNames.add(flavor.name)
+ }
+
+ return flavorNames
+ }
+
+ @NonNull
+ private static List<File> getGeneratedSourceFolders(@Nullable BaseVariantData variantData) {
+ if (variantData == null) {
+ return Collections.emptyList()
+ }
+
+ List<File> folders = Lists.newArrayList()
+
+ folders.add(variantData.processResourcesTask.sourceOutputDir)
+ folders.add(variantData.aidlCompileTask.sourceOutputDir)
+ folders.add(variantData.generateBuildConfigTask.sourceOutputDir)
+ if (!variantData.variantConfiguration.mergedFlavor.renderscriptNdkMode) {
+ folders.add(variantData.renderscriptCompileTask.sourceOutputDir)
+ }
+
+ List<File> extraFolders = variantData.extraGeneratedSourceFolders
+ if (extraFolders != null) {
+ folders.addAll(extraFolders)
+ }
+
+ return folders
+ }
+
+ @NonNull
+ private static List<File> getGeneratedResourceFolders(@Nullable BaseVariantData variantData) {
+ if (variantData == null) {
+ return Collections.emptyList()
+ }
+
+ return Collections.singletonList(variantData.renderscriptCompileTask.resOutputDir)
+ }
+
+ @NonNull
+ private static Collection<SigningConfig> cloneSigningConfigs(
+ @NonNull Collection<SigningConfig> signingConfigs) {
+ Collection<SigningConfig> results = Lists.newArrayListWithCapacity(signingConfigs.size())
+
+ for (SigningConfig signingConfig : signingConfigs) {
+ results.add(SigningConfigImpl.createSigningConfig(signingConfig))
+ }
+
+ return results
+ }
+
+ @Nullable
+ private static SourceProviderContainer getSourceProviderContainer(
+ @NonNull Collection<SourceProviderContainer> items,
+ @NonNull String name) {
+ for (SourceProviderContainer item : items) {
+ if (name.equals(item.getArtifactName())) {
+ return item;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Safely queries a project for a given plugin class.
+ * @param project the project to query
+ * @param pluginClass the plugin class.
+ * @return the plugin instance or null if it is not applied.
+ */
+ private static <T> T getPlugin(@NonNull Project project, @NonNull Class<T> pluginClass) {
+ try {
+ return project.getPlugins().findPlugin(pluginClass)
+ } catch (UnknownPluginException ignored) {
+ // ignore, return null below.
+ }
+
+ return null
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/ProductFlavorContainerImpl.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/ProductFlavorContainerImpl.java
new file mode 100644
index 0000000..92fa794
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/ProductFlavorContainerImpl.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.model;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.ProductFlavorData;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.ProductFlavor;
+import com.android.builder.model.ProductFlavorContainer;
+import com.android.builder.model.SourceProvider;
+import com.android.builder.model.SourceProviderContainer;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ */
+class ProductFlavorContainerImpl implements ProductFlavorContainer, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @NonNull
+ private final ProductFlavor productFlavor;
+ @NonNull
+ private final SourceProvider sourceProvider;
+ @NonNull
+ private final Collection<SourceProviderContainer> extraSourceProviders;
+
+ /**
+ * Create a ProductFlavorContainer from a ProductFlavorData
+ *
+ * @param productFlavorData the product flavor data
+ * @param sourceProviderContainers collection of extra source providers
+ *
+ * @return a non-null ProductFlavorContainer
+ */
+ @NonNull
+ static ProductFlavorContainer createPFC(
+ @NonNull ProductFlavorData productFlavorData,
+ @NonNull Collection<SourceProviderContainer> sourceProviderContainers) {
+
+ List<SourceProviderContainer> clonedContainer = SourceProviderContainerImpl.cloneCollection(sourceProviderContainers);
+
+ // instrument test Source Provider
+ SourceProviderContainer testASP = SourceProviderContainerImpl.create(
+ AndroidProject.ARTIFACT_INSTRUMENT_TEST, productFlavorData.getTestSourceSet());
+
+ clonedContainer.add(testASP);
+
+ return new ProductFlavorContainerImpl(
+ ProductFlavorImpl.cloneFlavor(productFlavorData.getProductFlavor()),
+ SourceProviderImpl.cloneProvider(productFlavorData.getSourceSet()),
+ clonedContainer);
+ }
+
+ private ProductFlavorContainerImpl(
+ @NonNull ProductFlavorImpl productFlavor,
+ @NonNull SourceProviderImpl sourceProvider,
+ @NonNull Collection<SourceProviderContainer> extraSourceProviders) {
+
+ this.productFlavor = productFlavor;
+ this.sourceProvider = sourceProvider;
+ this.extraSourceProviders = extraSourceProviders;
+ }
+
+ @NonNull
+ @Override
+ public ProductFlavor getProductFlavor() {
+ return productFlavor;
+ }
+
+ @NonNull
+ @Override
+ public SourceProvider getSourceProvider() {
+ return sourceProvider;
+ }
+
+ @NonNull
+ @Override
+ public Collection<SourceProviderContainer> getExtraSourceProviders() {
+ return extraSourceProviders;
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/ProductFlavorImpl.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/ProductFlavorImpl.java
new file mode 100644
index 0000000..9f25e30
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/ProductFlavorImpl.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.ClassField;
+import com.android.builder.model.NdkConfig;
+import com.android.builder.model.ProductFlavor;
+import com.google.common.collect.Sets;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Implementation of ProductFlavor that is serializable. Objects used in the DSL cannot be
+ * serialized.
+ **/
+class ProductFlavorImpl implements ProductFlavor, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private String name = null;
+ private int mMinSdkVersion = -1;
+ private int mTargetSdkVersion = -1;
+ private int mRenderscriptTargetApi = -1;
+ private boolean mRenderscriptSupportMode = false;
+ private boolean mRenderscriptNdkMode = false;
+ private int mVersionCode = -1;
+ private String mVersionName = null;
+ private String mPackageName = null;
+ private String mTestPackageName = null;
+ private String mTestInstrumentationRunner = null;
+ private Boolean mTestHandleProfiling = null;
+ private Boolean mTestFunctionalTest = null;
+ private Set<String> mResourceConfigurations = null;
+
+ @NonNull
+ static ProductFlavorImpl cloneFlavor(ProductFlavor productFlavor) {
+ ProductFlavorImpl clonedFlavor = new ProductFlavorImpl();
+ clonedFlavor.name = productFlavor.getName();
+
+ clonedFlavor.mMinSdkVersion = productFlavor.getMinSdkVersion();
+ clonedFlavor.mTargetSdkVersion = productFlavor.getTargetSdkVersion();
+ clonedFlavor.mRenderscriptTargetApi = productFlavor.getRenderscriptTargetApi();
+ clonedFlavor.mRenderscriptSupportMode = productFlavor.getRenderscriptSupportMode();
+ clonedFlavor.mRenderscriptNdkMode = productFlavor.getRenderscriptNdkMode();
+
+ clonedFlavor.mVersionCode = productFlavor.getVersionCode();
+ clonedFlavor.mVersionName = productFlavor.getVersionName();
+
+ clonedFlavor.mPackageName = productFlavor.getPackageName();
+
+ clonedFlavor.mTestPackageName = productFlavor.getTestPackageName();
+ clonedFlavor.mTestInstrumentationRunner = productFlavor.getTestInstrumentationRunner();
+ clonedFlavor.mTestHandleProfiling = productFlavor.getTestHandleProfiling();
+ clonedFlavor.mTestFunctionalTest = productFlavor.getTestFunctionalTest();
+
+ clonedFlavor.mResourceConfigurations = Sets.newHashSet(
+ productFlavor.getResourceConfigurations());
+
+ return clonedFlavor;
+ }
+
+ private ProductFlavorImpl() {
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Nullable
+ @Override
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ @Override
+ public int getVersionCode() {
+ return mVersionCode;
+ }
+
+ @Nullable
+ @Override
+ public String getVersionName() {
+ return mVersionName;
+ }
+
+ @Override
+ public int getMinSdkVersion() {
+ return mMinSdkVersion;
+ }
+
+ @Override
+ public int getTargetSdkVersion() {
+ return mTargetSdkVersion;
+ }
+
+ @Override
+ public int getRenderscriptTargetApi() {
+ return mRenderscriptTargetApi;
+ }
+
+ @Override
+ public boolean getRenderscriptSupportMode() {
+ return mRenderscriptSupportMode;
+ }
+
+ @Override
+ public boolean getRenderscriptNdkMode() {
+ return mRenderscriptNdkMode;
+ }
+
+ @Nullable
+ @Override
+ public String getTestPackageName() {
+ return mTestPackageName;
+ }
+
+ @Nullable
+ @Override
+ public String getTestInstrumentationRunner() {
+ return mTestInstrumentationRunner;
+ }
+
+ @Nullable
+ @Override
+ public Boolean getTestHandleProfiling() {
+ return mTestHandleProfiling;
+ }
+
+ @Nullable
+ @Override
+ public Boolean getTestFunctionalTest() {
+ return mTestFunctionalTest;
+ }
+
+
+ @NonNull
+ @Override
+ public List<ClassField> getBuildConfigFields() {
+ return Collections.emptyList();
+ }
+
+ @NonNull
+ @Override
+ public List<File> getProguardFiles() {
+ return Collections.emptyList();
+ }
+
+ @NonNull
+ @Override
+ public List<File> getConsumerProguardFiles() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ @Nullable
+ public NdkConfig getNdkConfig() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public Collection<String> getResourceConfigurations() {
+ return mResourceConfigurations;
+ }
+
+ @Override
+ public String toString() {
+ return "ProductFlavorImpl{" +
+ "name='" + name + '\'' +
+ ", mMinSdkVersion=" + mMinSdkVersion +
+ ", mTargetSdkVersion=" + mTargetSdkVersion +
+ ", mRenderscriptTargetApi=" + mRenderscriptTargetApi +
+ ", mRenderscriptSupportMode=" + mRenderscriptSupportMode +
+ ", mRenderscriptNdkMode=" + mRenderscriptNdkMode +
+ ", mVersionCode=" + mVersionCode +
+ ", mVersionName='" + mVersionName + '\'' +
+ ", mPackageName='" + mPackageName + '\'' +
+ ", mTestPackageName='" + mTestPackageName + '\'' +
+ ", mTestInstrumentationRunner='" + mTestInstrumentationRunner + '\'' +
+ ", mTestHandleProfiling='" + mTestHandleProfiling + '\'' +
+ ", mTestFunctionalTest='" + mTestFunctionalTest + '\'' +
+ ", mResourceConfigurations='" + mResourceConfigurations + '\'' +
+ '}';
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/SigningConfigImpl.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/SigningConfigImpl.java
new file mode 100644
index 0000000..19c867c
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/SigningConfigImpl.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.model;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.model.SigningConfig;
+
+import java.io.File;
+import java.io.Serializable;
+
+/**
+ * Implementation of SigningConfig that is serializable. Objects used in the DSL cannot be
+ * serialized.
+ */
+class SigningConfigImpl implements SigningConfig, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @NonNull
+ private final String name;
+ @Nullable
+ private final File storeFile;
+ @Nullable
+ private final String storePassword;
+ @Nullable
+ private final String keyAlias;
+ @Nullable
+ private final String keyPassword;
+ @Nullable
+ private final String storeType;
+ private final boolean signingReady;
+
+ @NonNull
+ static SigningConfig createSigningConfig(@NonNull SigningConfig signingConfig) {
+ return new SigningConfigImpl(
+ signingConfig.getName(),
+ signingConfig.getStoreFile(),
+ signingConfig.getStorePassword(),
+ signingConfig.getKeyAlias(),
+ signingConfig.getKeyPassword(),
+ signingConfig.getStoreType(),
+ signingConfig.isSigningReady());
+ }
+
+ private SigningConfigImpl(@NonNull String name,
+ @Nullable File storeFile,
+ @Nullable String storePassword,
+ @Nullable String keyAlias,
+ @Nullable String keyPassword,
+ @Nullable String storeType,
+ boolean signingReady) {
+
+ this.name = name;
+ this.storeFile = storeFile;
+ this.storePassword = storePassword;
+ this.keyAlias = keyAlias;
+ this.keyPassword = keyPassword;
+ this.storeType = storeType;
+ this.signingReady = signingReady;
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Nullable
+ @Override
+ public File getStoreFile() {
+ return storeFile;
+ }
+
+ @Nullable
+ @Override
+ public String getStorePassword() {
+ return storePassword;
+ }
+
+ @Nullable
+ @Override
+ public String getKeyAlias() {
+ return keyAlias;
+ }
+
+ @Nullable
+ @Override
+ public String getKeyPassword() {
+ return keyPassword;
+ }
+
+ @Nullable
+ @Override
+ public String getStoreType() {
+ return storeType;
+ }
+
+ @Override
+ public boolean isSigningReady() {
+ return signingReady;
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/SourceProviderContainerImpl.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/SourceProviderContainerImpl.java
new file mode 100644
index 0000000..fe41180
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/SourceProviderContainerImpl.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.model;
+
+import com.android.annotations.NonNull;
+import com.android.builder.model.SourceProvider;
+import com.android.builder.model.SourceProviderContainer;
+import com.google.common.collect.Lists;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Implementation of SourceProviderContainer that is serializable and is meant to be used
+ * in the model sent to the tooling API.
+ *
+ * It also provides convenient methods to create an instance, cloning the original
+ * SourceProvider.
+ *
+ * When the source Provider is cloned, its values are queried and then statically stored.
+ * Any further change through the DSL will not be impact. Therefore instances of this class
+ * should only be used when the model is built.
+ *
+ * To create more dynamic isntances of SourceProviderContainer, use
+ * {@link com.android.build.gradle.internal.variant.DefaultSourceProviderContainer}
+ */
+class SourceProviderContainerImpl implements SourceProviderContainer, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @NonNull
+ private final String name;
+ @NonNull
+ private final SourceProvider sourceProvider;
+
+ /**
+ * Create a {@link SourceProviderContainer} that is serializable to
+ * use in the model sent through the tooling API.
+ *
+ * @param sourceProviderContainer the source provider
+ *
+ * @return a non-null SourceProviderContainer
+ */
+ @NonNull
+ static SourceProviderContainer clone(
+ @NonNull SourceProviderContainer sourceProviderContainer) {
+ return create(
+ sourceProviderContainer.getArtifactName(),
+ sourceProviderContainer.getSourceProvider());
+ }
+
+ @NonNull
+ static List<SourceProviderContainer> cloneCollection(@NonNull Collection<SourceProviderContainer> containers) {
+ List<SourceProviderContainer> clones = Lists.newArrayListWithCapacity(containers.size());
+
+ for (SourceProviderContainer container : containers) {
+ clones.add(clone(container));
+ }
+
+ return clones;
+ }
+
+ @NonNull
+ static SourceProviderContainer create(
+ @NonNull String name,
+ @NonNull SourceProvider sourceProvider) {
+ return new SourceProviderContainerImpl(name,
+ SourceProviderImpl.cloneProvider(sourceProvider));
+ }
+
+ private SourceProviderContainerImpl(@NonNull String name,
+ @NonNull SourceProvider sourceProvider) {
+ this.name = name;
+ this.sourceProvider = sourceProvider;
+ }
+
+ @NonNull
+ @Override
+ public String getArtifactName() {
+ return name;
+ }
+
+ @NonNull
+ @Override
+ public SourceProvider getSourceProvider() {
+ return sourceProvider;
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/SourceProviderImpl.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/SourceProviderImpl.java
new file mode 100644
index 0000000..de02737
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/SourceProviderImpl.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.model;
+
+import com.android.annotations.NonNull;
+import com.android.builder.model.SourceProvider;
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.Collection;
+
+/**
+ * Implementation of SourceProvider that is serializable. Objects used in the DSL cannot be
+ * serialized.
+ */
+class SourceProviderImpl implements SourceProvider, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private File manifestFile;
+ private Collection<File> javaDirs;
+ private Collection<File> resourcesDirs;
+ private Collection<File> aidlDirs;
+ private Collection<File> rsDirs;
+ private Collection<File> jniDirs;
+ private Collection<File> resDirs;
+ private Collection<File> assetsDirs;
+
+ @NonNull
+ static SourceProviderImpl cloneProvider(SourceProvider sourceProvider) {
+ SourceProviderImpl sourceProviderClone = new SourceProviderImpl();
+
+ sourceProviderClone.manifestFile = sourceProvider.getManifestFile();
+ sourceProviderClone.javaDirs = sourceProvider.getJavaDirectories();
+ sourceProviderClone.resourcesDirs = sourceProvider.getResourcesDirectories();
+ sourceProviderClone.aidlDirs = sourceProvider.getAidlDirectories();
+ sourceProviderClone.rsDirs = sourceProvider.getRenderscriptDirectories();
+ sourceProviderClone.jniDirs = sourceProvider.getJniDirectories();
+ sourceProviderClone.resDirs = sourceProvider.getResDirectories();
+ sourceProviderClone.assetsDirs = sourceProvider.getAssetsDirectories();
+
+ return sourceProviderClone;
+ }
+
+ @NonNull
+ static Collection<SourceProvider> cloneCollection(
+ @NonNull Collection<SourceProvider> sourceProviders) {
+ Collection<SourceProvider> results = Lists.newArrayListWithCapacity(sourceProviders.size());
+ for (SourceProvider sourceProvider : sourceProviders) {
+ results.add(SourceProviderImpl.cloneProvider(sourceProvider));
+ }
+
+ return results;
+ }
+
+ private SourceProviderImpl() {
+ }
+
+ @NonNull
+ @Override
+ public File getManifestFile() {
+ return manifestFile;
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getJavaDirectories() {
+ return javaDirs;
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getResourcesDirectories() {
+ return resourcesDirs;
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getAidlDirectories() {
+ return aidlDirs;
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getRenderscriptDirectories() {
+ return rsDirs;
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getJniDirectories() {
+ return jniDirs;
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getResDirectories() {
+ return resDirs;
+ }
+
+ @NonNull
+ @Override
+ public Collection<File> getAssetsDirectories() {
+ return assetsDirs;
+ }
+
+ @Override
+ public String toString() {
+ return "SourceProviderImpl{" +
+ "manifestFile=" + manifestFile +
+ ", javaDirs=" + javaDirs +
+ ", resourcesDirs=" + resourcesDirs +
+ ", aidlDirs=" + aidlDirs +
+ ", rsDirs=" + rsDirs +
+ ", jniDirs=" + jniDirs +
+ ", resDirs=" + resDirs +
+ ", assetsDirs=" + assetsDirs +
+ '}';
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/VariantImpl.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/VariantImpl.java
new file mode 100644
index 0000000..b8a04e6
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/model/VariantImpl.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.model;
+
+import com.android.annotations.NonNull;
+import com.android.builder.model.AndroidArtifact;
+import com.android.builder.model.JavaArtifact;
+import com.android.builder.model.ProductFlavor;
+import com.android.builder.model.Variant;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Implementation of Variant that is serializable.
+ */
+class VariantImpl implements Variant, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @NonNull
+ private final String name;
+ @NonNull
+ private final String displayName;
+ @NonNull
+ private final String buildTypeName;
+ @NonNull
+ private final List<String> productFlavorNames;
+ @NonNull
+ private final ProductFlavor mergedFlavor;
+ @NonNull
+ private final AndroidArtifact mainArtifactInfo;
+ @NonNull
+ private final Collection<AndroidArtifact> extraAndroidArtifacts;
+ @NonNull
+ private final Collection<JavaArtifact> extraJavaArtifacts;
+
+ VariantImpl(@NonNull String name,
+ @NonNull String displayName,
+ @NonNull String buildTypeName,
+ @NonNull List<String> productFlavorNames,
+ @NonNull ProductFlavorImpl mergedFlavor,
+ @NonNull AndroidArtifact mainArtifactInfo,
+ @NonNull Collection<AndroidArtifact> extraAndroidArtifacts,
+ @NonNull Collection<JavaArtifact> extraJavaArtifacts) {
+ this.name = name;
+ this.displayName = displayName;
+ this.buildTypeName = buildTypeName;
+ this.productFlavorNames = productFlavorNames;
+ this.mergedFlavor = mergedFlavor;
+ this.mainArtifactInfo = mainArtifactInfo;
+ this.extraAndroidArtifacts = extraAndroidArtifacts;
+ this.extraJavaArtifacts = extraJavaArtifacts;
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ @NonNull
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ @Override
+ @NonNull
+ public String getBuildType() {
+ return buildTypeName;
+ }
+
+ @Override
+ @NonNull
+ public List<String> getProductFlavors() {
+ return productFlavorNames;
+ }
+
+ @Override
+ @NonNull
+ public ProductFlavor getMergedFlavor() {
+ return mergedFlavor;
+ }
+
+ @NonNull
+ @Override
+ public AndroidArtifact getMainArtifact() {
+ return mainArtifactInfo;
+ }
+
+ @NonNull
+ @Override
+ public Collection<AndroidArtifact> getExtraAndroidArtifacts() {
+ return extraAndroidArtifacts;
+ }
+
+ @NonNull
+ @Override
+ public Collection<JavaArtifact> getExtraJavaArtifacts() {
+ return extraJavaArtifacts;
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/AndroidReportTask.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/AndroidReportTask.groovy
new file mode 100644
index 0000000..f50f5aa
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/AndroidReportTask.groovy
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.tasks
+
+import com.android.build.gradle.internal.test.report.ReportType
+import com.android.build.gradle.internal.test.report.TestReport
+import com.google.common.collect.Lists
+import com.google.common.io.Files
+import org.gradle.api.GradleException
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.TaskAction
+import org.gradle.logging.ConsoleRenderer
+/**
+ * Task doing test report aggregation.
+ */
+class AndroidReportTask extends BaseTask implements AndroidTestTask {
+
+ private final List<AndroidTestTask> subTasks = Lists.newArrayList()
+
+ ReportType reportType
+
+ boolean ignoreFailures
+ boolean testFailed
+
+ @OutputDirectory
+ File reportsDir
+
+ @OutputDirectory
+ File resultsDir
+
+ public void addTask(AndroidTestTask task) {
+ subTasks.add(task)
+ dependsOn task
+ }
+
+ @InputFiles
+ List<File> getResultInputs() {
+ List<File> list = Lists.newArrayList()
+
+ for (AndroidTestTask task : subTasks) {
+ list.add(task.getResultsDir())
+ }
+
+ return list
+ }
+
+ /**
+ * Sets that this current task will run and therefore needs to tell its children
+ * class to not stop on failures.
+ */
+ public void setWillRun() {
+ for (AndroidTestTask task : subTasks) {
+ task.ignoreFailures = true
+ }
+ }
+
+ @TaskAction
+ protected void createReport() {
+ File resultsOutDir = getResultsDir()
+ File reportOutDir = getReportsDir()
+
+ // empty the folders
+ emptyFolder(resultsOutDir)
+ emptyFolder(reportOutDir)
+
+ // do the copy.
+ copyResults(resultsOutDir)
+
+ // create the report.
+ TestReport report = new TestReport(reportType, resultsOutDir, reportOutDir)
+ report.generateReport()
+
+ // fail if any of the tasks failed.
+ for (AndroidTestTask task : subTasks) {
+ if (task.testFailed) {
+
+ String reportUrl = new ConsoleRenderer().asClickableFileUrl(
+ new File(reportOutDir, "index.html"))
+ String message = "There were failing tests. See the report at: " + reportUrl
+
+ if (getIgnoreFailures()) {
+ getLogger().warn(message)
+ } else {
+ throw new GradleException(message)
+ }
+
+ break
+ }
+ }
+ }
+
+ private void copyResults(File reportOutDir) {
+ List<File> inputs = getResultInputs()
+
+ for (File input : inputs) {
+ File[] children = input.listFiles()
+ if (children != null) {
+ for (File child : children) {
+ copyFile(child, reportOutDir)
+ }
+ }
+ }
+ }
+
+ private void copyFile(File from, File to) {
+ to = new File(to, from.getName())
+ if (from.isDirectory()) {
+ if (!to.exists()) {
+ to.mkdirs()
+ }
+
+ File[] children = from.listFiles()
+ if (children != null) {
+ for (File child : children) {
+ copyFile(child, to)
+ }
+ }
+ } else if (from.isFile()) {
+ Files.copy(from, to)
+ }
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/AndroidTestTask.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/AndroidTestTask.java
new file mode 100644
index 0000000..620bf73
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/AndroidTestTask.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.tasks;
+
+import org.gradle.api.tasks.VerificationTask;
+
+import java.io.File;
+
+/**
+ * Base interface for test classes that integrate with other test classes for reporting
+ * reasons.
+ */
+public interface AndroidTestTask extends VerificationTask {
+
+ File getResultsDir();
+
+ boolean getTestFailed();
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/BaseTask.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/BaseTask.groovy
new file mode 100644
index 0000000..41c3794
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/BaseTask.groovy
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.tasks
+
+import com.android.build.gradle.BasePlugin
+import com.android.build.gradle.internal.variant.BaseVariantData
+import com.android.builder.AndroidBuilder
+import org.gradle.api.DefaultTask
+
+public abstract class BaseTask extends DefaultTask {
+
+ BasePlugin plugin
+ BaseVariantData variant
+
+ protected AndroidBuilder getBuilder() {
+ return plugin.getAndroidBuilder(variant)
+ }
+
+ protected static void emptyFolder(File folder) {
+ deleteFolder(folder)
+ folder.mkdirs()
+ }
+
+ protected static void deleteFolder(File folder) {
+ File[] files = folder.listFiles()
+ if (files != null && files.length > 0) {
+ for (File file : files) {
+ if (file.isDirectory()) {
+ deleteFolder(file)
+ } else {
+ file.delete()
+ }
+ }
+ }
+
+ folder.delete()
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/DependencyBasedCompileTask.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/DependencyBasedCompileTask.groovy
new file mode 100644
index 0000000..f264bc9
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/DependencyBasedCompileTask.groovy
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.tasks
+import com.android.annotations.NonNull
+import com.android.annotations.Nullable
+import com.android.builder.compiling.DependencyFileProcessor
+import com.android.builder.internal.incremental.DependencyData
+import com.android.builder.internal.incremental.DependencyDataStore
+import com.android.ide.common.internal.WaitableExecutor
+import com.android.ide.common.res2.FileStatus
+import com.google.common.collect.Lists
+import com.google.common.collect.Multimap
+import org.gradle.api.tasks.OutputDirectory
+
+import java.util.concurrent.Callable
+/**
+ * Base task for source generators that generate and use dependency files.
+ */
+public abstract class DependencyBasedCompileTask extends IncrementalTask {
+
+ private static final String DEPENDENCY_STORE = "dependency.store"
+
+ // ----- PUBLIC TASK API -----
+
+ @OutputDirectory
+ File sourceOutputDir
+
+ // ----- PRIVATE TASK API -----
+
+ private static class DepFileProcessor implements DependencyFileProcessor {
+
+ List<DependencyData> dependencyDataList = Lists.newArrayList()
+
+ List<DependencyData> getDependencyDataList() {
+ return dependencyDataList
+ }
+
+ @Override
+ boolean processFile(@NonNull File dependencyFile) {
+ DependencyData data = DependencyData.parseDependencyFile(dependencyFile)
+ if (data != null) {
+ dependencyDataList.add(data)
+ }
+
+ return true
+ }
+ }
+
+ /**
+ * Action methods to compile all the files.
+ *
+ * The method receives a {@link DependencyFileProcessor} to be used by the
+ * {@link com.android.builder.internal.compiler.SourceSearcher.SourceFileProcessor} during
+ * the compilation.
+ *
+ * @param dependencyFileProcessor a DependencyFileProcessor
+ */
+ protected abstract void compileAllFiles(DependencyFileProcessor dependencyFileProcessor)
+
+ /**
+ * Setup call back used once before calling multiple
+ * {@link #compileSingleFile(File, Object, DependencyFileProcessor)}
+ * during incremental compilation. The result object is passed back to the compileSingleFile
+ * method
+ *
+ * @return an object or null.
+ */
+ protected abstract Object incrementalSetup()
+
+ /**
+ * Returns whether each changed file can be processed in parallel.
+ */
+ protected abstract boolean supportsParallelization()
+
+ /**
+ * Compiles a single file.
+ * @param file the file to compile.
+ * @param data the data returned by {@link #incrementalSetup()}
+ * @param dependencyFileProcessor a DependencyFileProcessor
+ *
+ * @see #incrementalSetup()
+ */
+ protected abstract void compileSingleFile(@NonNull File file,
+ @Nullable Object data,
+ @NonNull DependencyFileProcessor dependencyFileProcessor)
+
+ /**
+ * Small wrapper around an optional WaitableExecutor.
+ */
+ private static final class ExecutorWrapper {
+ WaitableExecutor executor = null
+
+ ExecutorWrapper(boolean useExecutor) {
+ if (useExecutor) {
+ executor = new WaitableExecutor<Void>()
+ }
+ }
+
+ void execute(Callable callable) throws Exception {
+ if (executor != null) {
+ executor.execute(callable)
+ } else {
+ callable.call()
+ }
+ }
+
+ @Nullable
+ List<WaitableExecutor.TaskResult<Void>> waitForTasks() {
+ if (executor != null) {
+ return executor.waitForAllTasks()
+ }
+
+ return null
+ }
+ }
+
+ @Override
+ protected void doFullTaskAction() {
+ // this is full run, clean the previous output
+ File destinationDir = getSourceOutputDir()
+ emptyFolder(destinationDir)
+
+ DepFileProcessor processor = new DepFileProcessor()
+
+ compileAllFiles(processor)
+
+ List<DependencyData> dataList = processor.getDependencyDataList()
+
+ DependencyDataStore store = new DependencyDataStore()
+ store.addData(dataList)
+
+ store.saveTo(new File(getIncrementalFolder(), DEPENDENCY_STORE))
+ }
+
+ @Override
+ protected void doIncrementalTaskAction(Map<File, FileStatus> changedInputs) {
+
+ File incrementalData = new File(getIncrementalFolder(), DEPENDENCY_STORE)
+ DependencyDataStore store = new DependencyDataStore()
+ Multimap<String, DependencyData> inputMap
+ try {
+ inputMap = store.loadFrom(incrementalData)
+ } catch (Exception e) {
+ project.logger.info(
+ "Failed to read dependency store: full task run!")
+ doFullTaskAction()
+ return
+ }
+
+ final Object incrementalObject = incrementalSetup()
+ final DepFileProcessor processor = new DepFileProcessor()
+
+ // use an executor to parallelize the compilation of multiple files.
+ ExecutorWrapper executor = new ExecutorWrapper(supportsParallelization())
+
+ Map<String,DependencyData> mainFileMap = store.getMainFileMap()
+
+ for (Map.Entry<File, FileStatus> entry : changedInputs.entrySet()) {
+ FileStatus status = entry.getValue()
+
+ switch (status) {
+ case FileStatus.NEW:
+ executor.execute(new Callable<Void>() {
+ @Override
+ Void call() throws Exception {
+ compileSingleFile(entry.getKey(), incrementalObject, processor)
+ }
+ })
+ break
+ case FileStatus.CHANGED:
+ List<DependencyData> impactedData = inputMap.get(entry.getKey().absolutePath)
+ if (impactedData != null) {
+ for (final DependencyData data : impactedData) {
+ executor.execute(new Callable<Void>() {
+ @Override
+ Void call() throws Exception {
+ compileSingleFile(new File(data.getMainFile()),
+ incrementalObject, processor)
+ }
+ })
+ }
+ }
+ break
+ case FileStatus.REMOVED:
+ final DependencyData data = mainFileMap.get(entry.getKey().absolutePath)
+ if (data != null) {
+ executor.execute(new Callable<Void>() {
+ @Override
+ Void call() throws Exception {
+ cleanUpOutputFrom(data)
+ }
+ })
+ store.remove(data)
+ }
+ break
+ }
+ }
+
+ // results will be null if there was no spawning of threads
+ List<WaitableExecutor.TaskResult<Void>> results = executor.waitForTasks()
+ if (results != null) {
+ for (WaitableExecutor.TaskResult<Void> result : results) {
+ if (result.exception != null) {
+ throw result.exception
+ }
+ }
+ }
+
+ // get all the update data for the recompiled objects
+ store.updateAll(processor.getDependencyDataList())
+
+ store.saveTo(incrementalData)
+ }
+
+ private static void cleanUpOutputFrom(DependencyData dependencyData) {
+ List<String> outputs = dependencyData.getOutputFiles()
+
+ for (String output : outputs) {
+ new File(output).delete()
+ }
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/DependencyReportTask.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/DependencyReportTask.groovy
new file mode 100644
index 0000000..a4d832e
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/DependencyReportTask.groovy
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.tasks
+import com.android.build.gradle.internal.AndroidAsciiReportRenderer
+import com.android.build.gradle.internal.variant.BaseVariantData
+import org.gradle.api.DefaultTask
+import org.gradle.api.tasks.TaskAction
+import org.gradle.logging.StyledTextOutputFactory
+/**
+ */
+public class DependencyReportTask extends DefaultTask {
+
+ private AndroidAsciiReportRenderer renderer = new AndroidAsciiReportRenderer();
+
+ private Set<BaseVariantData> variants = [];
+
+ @TaskAction
+ public void generate() throws IOException {
+ renderer.setOutput(getServices().get(StyledTextOutputFactory.class).create(getClass()));
+
+ SortedSet<BaseVariantData> sortedConfigurations = new TreeSet<BaseVariantData>(
+ new Comparator<BaseVariantData>() {
+ public int compare(BaseVariantData conf1, BaseVariantData conf2) {
+ return conf1.getName().compareTo(conf2.getName());
+ }
+ });
+ sortedConfigurations.addAll(getVariants());
+ for (BaseVariantData variant : sortedConfigurations) {
+ renderer.startVariant(variant);
+ renderer.render(variant);
+ }
+ }
+
+ /**
+ * Returns the configurations to generate the report for. Default to all configurations of
+ * this task's containing project.
+ *
+ * @return the configurations.
+ */
+ public Set<BaseVariantData> getVariants() {
+ return variants;
+ }
+
+ /**
+ * Sets the configurations to generate the report for.
+ *
+ * @param configurations The configuration. Must not be null.
+ */
+ public void setVariants(Collection<BaseVariantData> variants) {
+ this.variants.addAll(variants);
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/DeviceProviderInstrumentTestLibraryTask.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/DeviceProviderInstrumentTestLibraryTask.groovy
new file mode 100644
index 0000000..44498c4
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/DeviceProviderInstrumentTestLibraryTask.groovy
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.tasks
+
+/**
+ * class to test library project. Exactly the same as DeviceProviderInstrumentTestTask but
+ * is needed to be gathered by the reporting plugin.
+ */
+class DeviceProviderInstrumentTestLibraryTask extends DeviceProviderInstrumentTestTask {
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/DeviceProviderInstrumentTestTask.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/DeviceProviderInstrumentTestTask.groovy
new file mode 100644
index 0000000..fa22b5e
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/DeviceProviderInstrumentTestTask.groovy
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.tasks
+import com.android.build.gradle.internal.test.report.ReportType
+import com.android.build.gradle.internal.test.report.TestReport
+import com.android.build.gradle.internal.variant.TestVariantData
+import com.android.builder.testing.SimpleTestRunner
+import com.android.builder.testing.TestRunner
+import com.android.builder.testing.api.DeviceProvider
+import org.gradle.api.GradleException
+import org.gradle.api.tasks.TaskAction
+import org.gradle.logging.ConsoleRenderer
+/**
+ * Run instrumentation tests for a given variant
+ */
+public class DeviceProviderInstrumentTestTask extends BaseTask implements AndroidTestTask {
+
+ File testApp
+ File testedApp
+
+ File reportsDir
+ File resultsDir
+
+ String flavorName
+
+ DeviceProvider deviceProvider
+
+ boolean ignoreFailures
+ boolean testFailed
+
+ @TaskAction
+ protected void runTests() {
+ assert variant instanceof TestVariantData
+
+ File resultsOutDir = getResultsDir()
+
+ // empty the folder.
+ emptyFolder(resultsOutDir)
+
+ File testApk = getTestApp()
+ File testedApk = getTestedApp()
+
+ String flavor = getFlavorName()
+
+ TestRunner testRunner = new SimpleTestRunner();
+ deviceProvider.init();
+
+ boolean success = false;
+ try {
+ success = testRunner.runTests(project.name, flavor,
+ testApk, testedApk, variant.variantConfiguration,
+ deviceProvider.devices,
+ deviceProvider.getMaxThreads(),
+ deviceProvider.getTimeout(),
+ resultsOutDir, plugin.logger);
+ } finally {
+ deviceProvider.terminate();
+ }
+
+ // run the report from the results.
+ File reportOutDir = getReportsDir()
+ emptyFolder(reportOutDir)
+
+ TestReport report = new TestReport(ReportType.SINGLE_FLAVOR, resultsOutDir, reportOutDir)
+ report.generateReport()
+
+ if (!success) {
+ testFailed = true
+ String reportUrl = new ConsoleRenderer().asClickableFileUrl(
+ new File(reportOutDir, "index.html"));
+ String message = "There were failing tests. See the report at: " + reportUrl;
+ if (getIgnoreFailures()) {
+ getLogger().warn(message)
+
+ return
+ } else {
+ throw new GradleException(message)
+ }
+ }
+
+ testFailed = false
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/IncrementalTask.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/IncrementalTask.groovy
new file mode 100644
index 0000000..2a86114
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/IncrementalTask.groovy
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.tasks
+import com.android.ide.common.res2.FileStatus
+import com.android.ide.common.res2.SourceSet
+import com.google.common.collect.Lists
+import com.google.common.collect.Maps
+import org.gradle.api.tasks.Optional
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.TaskAction
+import org.gradle.api.tasks.incremental.IncrementalTaskInputs
+
+public abstract class IncrementalTask extends BaseTask {
+
+ @OutputDirectory @Optional
+ File incrementalFolder
+
+ /**
+ * Whether this task can support incremental update.
+ *
+ * @return whether this task can support incremental update.
+ */
+ protected boolean isIncremental() {
+ return false
+ }
+
+ /**
+ * Actual task action. This is called when a full run is needed, which is always the case if
+ * {@link #isIncremental()} returns false.
+ *
+ */
+ protected abstract void doFullTaskAction()
+
+ /**
+ * Optional incremental task action.
+ * Only used if {@link #isIncremental()} returns true.
+ *
+ * @param changedInputs the changed input files.
+ */
+ protected void doIncrementalTaskAction(Map<File, FileStatus> changedInputs) {
+ // do nothing.
+ }
+
+ /**
+ * Actual entry point for the action.
+ * Calls out to the doTaskAction as needed.
+ */
+ @TaskAction
+ void taskAction(IncrementalTaskInputs inputs) {
+ if (!isIncremental()) {
+ doFullTaskAction()
+ return
+ }
+
+ if (!inputs.isIncremental()) {
+ project.logger.info("Unable do incremental execution: full task run")
+ doFullTaskAction()
+ return
+ }
+
+ Map<File, FileStatus> changedInputs = Maps.newHashMap()
+ inputs.outOfDate { change ->
+ //noinspection GroovyAssignabilityCheck
+ changedInputs.put(change.file, change.isAdded() ? FileStatus.NEW : FileStatus.CHANGED)
+ }
+
+ inputs.removed { change ->
+ //noinspection GroovyAssignabilityCheck
+ changedInputs.put(change.file, FileStatus.REMOVED)
+ }
+
+ doIncrementalTaskAction(changedInputs)
+ }
+
+ public static List<File> flattenSourceSets(List<? extends SourceSet> resourceSets) {
+ List<File> list = Lists.newArrayList()
+
+ for (SourceSet sourceSet : resourceSets) {
+ list.addAll(sourceSet.sourceFiles)
+ }
+
+ return list
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/InstallTask.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/InstallTask.groovy
new file mode 100644
index 0000000..92fd0d9
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/InstallTask.groovy
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.tasks
+import org.gradle.api.DefaultTask
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.TaskAction
+/**
+ * Task installing an app.
+ */
+public class InstallTask extends DefaultTask {
+ @InputFile
+ File adbExe
+
+ @InputFile
+ File packageFile
+
+ @TaskAction
+ void generate() {
+ project.exec {
+ executable = getAdbExe()
+ args 'install'
+ args '-r'
+ args getPackageFile()
+ }
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/MergeFileTask.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/MergeFileTask.groovy
new file mode 100644
index 0000000..bab61bd
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/MergeFileTask.groovy
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.tasks
+
+import com.google.common.base.Charsets
+import com.google.common.io.Files
+import org.gradle.api.DefaultTask
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.TaskAction
+
+/**
+ * Task to merge files. This appends all the files together into an output file.
+ */
+class MergeFileTask extends DefaultTask {
+
+ @InputFiles
+ Set<File> inputFiles
+
+ @OutputFile
+ File outputFile
+
+ @TaskAction
+ void mergeFiles() {
+
+ Set<File> files = getInputFiles();
+ File output = getOutputFile()
+
+ if (files.size() == 1) {
+ Files.copy(files.iterator().next(), output);
+ return
+ }
+
+ // first delete the current file
+ output.delete();
+
+ // no input? done.
+ if (files.isEmpty()) {
+ return
+ }
+
+ // otherwise put the all the files together
+ for (File file : files) {
+ String content = Files.toString(file, Charsets.UTF_8);
+ Files.append(content, output, Charsets.UTF_8);
+ Files.append("\n", output, Charsets.UTF_8);
+ }
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/NdkTask.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/NdkTask.groovy
new file mode 100644
index 0000000..ba48552
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/NdkTask.groovy
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.tasks
+
+import com.android.builder.model.NdkConfig
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.Optional
+
+/**
+ * Base task for tasks that require an NdkConfig
+ */
+class NdkTask extends BaseTask {
+
+ NdkConfig ndkConfig
+
+ @Input @Optional
+ String getModuleName() {
+ return getNdkConfig()?.moduleName
+ }
+
+ @Input @Optional
+ String getcFlags() {
+ return getNdkConfig()?.cFlags
+ }
+
+ @Input @Optional
+ Set<String> getLdLibs() {
+ return getNdkConfig()?.ldLibs
+ }
+
+ @Input @Optional
+ Set<String> getAbiFilters() {
+ return getNdkConfig()?.abiFilters
+ }
+
+ @Input @Optional
+ String getStl() {
+ return getNdkConfig().stl
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/OutputFileTask.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/OutputFileTask.java
new file mode 100644
index 0000000..a50c8f9
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/OutputFileTask.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.tasks;
+
+import java.io.File;
+
+/**
+ * A task that outputs a file.
+ */
+public interface OutputFileTask {
+
+ File getOutputFile();
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/PrepareDependenciesTask.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/PrepareDependenciesTask.groovy
new file mode 100644
index 0000000..1fa520b7
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/PrepareDependenciesTask.groovy
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.tasks
+
+import com.android.build.gradle.internal.dependency.DependencyChecker
+import com.android.utils.Pair
+import org.gradle.api.tasks.TaskAction
+
+public class PrepareDependenciesTask extends BaseTask {
+ final List<DependencyChecker> checkers = []
+ final Set<Pair<Integer, String>> androidDependencies = []
+
+ void addDependency(Pair<Integer, String> api) {
+ androidDependencies.add(api)
+ }
+
+ @TaskAction
+ protected void prepare() {
+ def minSdkVersion = variant.variantConfiguration.minSdkVersion
+
+ for (DependencyChecker checker : checkers) {
+ for (Integer api : checker.foundAndroidApis) {
+ if (api > minSdkVersion) {
+ throw new RuntimeException(String.format(
+ "ERROR: %s has an indirect dependency on Android API level %d, but minSdkVersion for variant '%s' is API level %d",
+ checker.configurationDependencies.name.capitalize(), api, variant.name, minSdkVersion))
+ }
+ }
+
+ }
+ }
+
+ def addChecker(DependencyChecker checker) {
+ checkers.add(checker)
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/PrepareLibraryTask.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/PrepareLibraryTask.groovy
new file mode 100644
index 0000000..e490a93
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/PrepareLibraryTask.groovy
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.tasks
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.TaskAction
+
+public class PrepareLibraryTask extends DefaultTask {
+ @InputFile
+ File bundle
+
+ @OutputDirectory
+ File explodedDir
+
+ @TaskAction
+ def prepare() {
+ project.copy {
+ from project.zipTree(bundle)
+ into explodedDir
+ }
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/SigningReportTask.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/SigningReportTask.groovy
new file mode 100644
index 0000000..9d7c170
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/SigningReportTask.groovy
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.tasks
+
+import com.android.build.gradle.internal.dsl.SigningConfigDsl
+import com.android.build.gradle.internal.variant.BaseVariantData
+import com.android.builder.model.SigningConfig
+import com.android.builder.signing.CertificateInfo
+import com.android.builder.signing.KeystoreHelper
+import com.android.builder.signing.KeytoolException
+import com.google.common.collect.Maps
+import org.gradle.api.tasks.TaskAction
+import org.gradle.logging.StyledTextOutput
+import org.gradle.logging.StyledTextOutputFactory
+
+import java.security.MessageDigest
+import java.security.NoSuchAlgorithmException
+import java.security.cert.Certificate
+import java.security.cert.CertificateEncodingException
+import java.text.DateFormat
+
+import static org.gradle.logging.StyledTextOutput.Style.Description
+import static org.gradle.logging.StyledTextOutput.Style.Failure
+import static org.gradle.logging.StyledTextOutput.Style.Identifier
+import static org.gradle.logging.StyledTextOutput.Style.Normal
+
+/**
+ * Report tasks displaying the signing information for all variants.
+ */
+class SigningReportTask extends BaseTask {
+
+ private Set<BaseVariantData> variants = [];
+
+ @TaskAction
+ public void generate() throws IOException {
+
+ StyledTextOutput textOutput = getServices().get(
+ StyledTextOutputFactory.class).create(getClass())
+
+ Map<SigningConfig, SigningInfo> cache = Maps.newHashMap()
+
+ for (BaseVariantData variant : variants) {
+ textOutput.withStyle(Identifier).text("Variant: ")
+ textOutput.withStyle(Description).text(variant.name)
+ textOutput.println()
+
+ // get the data
+ SigningConfigDsl signingConfig = (SigningConfigDsl) variant.variantConfiguration.signingConfig
+ if (signingConfig == null) {
+ textOutput.withStyle(Identifier).text("Config: ")
+ textOutput.withStyle(Normal).text("none")
+ textOutput.println()
+ } else {
+ SigningInfo signingInfo = getSigningInfo(signingConfig, cache)
+
+
+ textOutput.withStyle(Identifier).text("Config: ")
+ textOutput.withStyle(Description).text(signingConfig.name)
+ textOutput.println()
+
+ textOutput.withStyle(Identifier).text("Store: ")
+ textOutput.withStyle(Description).text(signingConfig.getStoreFile())
+ textOutput.println()
+
+ textOutput.withStyle(Identifier).text("Alias: ")
+ textOutput.withStyle(Description).text(signingConfig.getKeyAlias())
+ textOutput.println()
+
+ if (signingInfo.isValid()) {
+ if (signingInfo.error != null) {
+ textOutput.withStyle(Identifier).text("Error: ")
+ textOutput.withStyle(Failure).text(signingInfo.error)
+ textOutput.println()
+ } else {
+ textOutput.withStyle(Identifier).text("MD5: ")
+ textOutput.withStyle(Description).text(signingInfo.md5)
+ textOutput.println()
+
+ textOutput.withStyle(Identifier).text("SHA1: ")
+ textOutput.withStyle(Description).text(signingInfo.sha1)
+ textOutput.println()
+
+ textOutput.withStyle(Identifier).text("Valid until: ")
+ DateFormat df = DateFormat.getDateInstance(DateFormat.FULL)
+ textOutput.withStyle(Description).text(df.format(signingInfo.notAfter))
+ textOutput.println()
+ }
+ }
+ }
+
+ textOutput.withStyle(Normal).text("----------")
+ textOutput.println()
+ }
+ }
+
+ /**
+ * Sets the configurations to generate the report for.
+ *
+ * @param configurations The configuration. Must not be null.
+ */
+ public void setVariants(Collection<BaseVariantData> variants) {
+ this.variants.addAll(variants);
+ }
+
+ private static SigningInfo getSigningInfo(SigningConfig signingConfig,
+ Map<SigningConfig, SigningInfo> cache) {
+ SigningInfo signingInfo = cache.get(signingConfig)
+
+ if (signingInfo == null) {
+ signingInfo = new SigningInfo()
+
+ if (signingConfig.isSigningReady()) {
+ try {
+ CertificateInfo certificateInfo = KeystoreHelper.getCertificateInfo(
+ signingConfig)
+ if (certificateInfo != null) {
+ signingInfo.md5 = getFingerprint(certificateInfo.certificate, "MD5")
+ signingInfo.sha1 = getFingerprint(certificateInfo.certificate, "SHA1")
+ signingInfo.notAfter = certificateInfo.certificate.notAfter
+ } else {
+
+ }
+ } catch (KeytoolException e) {
+ signingInfo.error = e.getMessage()
+ } catch (FileNotFoundException e) {
+ signingInfo.error = "Missing keystore"
+ }
+ }
+
+ cache.put(signingConfig, signingInfo)
+ }
+
+ return signingInfo
+ }
+
+ private final static class SigningInfo {
+ String md5
+ String sha1
+ Date notAfter
+ String error
+
+ boolean isValid() {
+ return md5 != null || error != null
+ }
+ }
+
+ /**
+ * Returns the {@link Certificate} fingerprint as returned by <code>keytool</code>.
+ *
+ * @param certificate
+ * @param hashAlgorithm
+ */
+ public static String getFingerprint(Certificate cert, String hashAlgorithm) {
+ if (cert == null) {
+ return null;
+ }
+ try {
+ MessageDigest digest = MessageDigest.getInstance(hashAlgorithm);
+ return toHexadecimalString(digest.digest(cert.getEncoded()));
+ } catch(NoSuchAlgorithmException e) {
+ // ignore
+ } catch(CertificateEncodingException e) {
+ // ignore
+ }
+ return null;
+ }
+
+ private static String toHexadecimalString(byte[] value) {
+ StringBuilder sb = new StringBuilder();
+ int len = value.length;
+ for (int i = 0; i < len; i++) {
+ int num = ((int) value[i]) & 0xff;
+ if (num < 0x10) {
+ sb.append('0');
+ }
+ sb.append(Integer.toHexString(num));
+ if (i < len - 1) {
+ sb.append(':');
+ }
+ }
+ return sb.toString().toUpperCase(Locale.US);
+ }
+}
\ No newline at end of file
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/TestServerTask.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/TestServerTask.groovy
new file mode 100644
index 0000000..315fd40
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/TestServerTask.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.tasks
+
+import com.android.builder.testing.api.TestServer
+import org.gradle.api.DefaultTask
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.Optional
+import org.gradle.api.tasks.TaskAction
+
+/**
+ * Task sending APKs out to a {@link TestServer}
+ */
+public class TestServerTask extends DefaultTask {
+
+ @InputFile
+ File testApk
+
+ @InputFile @Optional
+ File testedApk
+
+ @Input
+ String variantName
+
+ TestServer testServer
+
+ @TaskAction
+ void sendToServer() {
+ testServer.uploadApks(getVariantName(), getTestApk(), getTestedApk())
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/UninstallTask.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/UninstallTask.groovy
new file mode 100644
index 0000000..96892a0
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/UninstallTask.groovy
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.tasks
+
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.TaskAction
+
+public class UninstallTask extends BaseTask {
+ @InputFile
+ File adbExe
+
+ @TaskAction
+ public void uninstall() {
+ String packageName = variant.packageName
+ logger.info("Uninstalling app: " + packageName)
+ project.exec {
+ executable = getAdbExe()
+ args "uninstall"
+ args packageName
+ }
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/ValidateSigningTask.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/ValidateSigningTask.groovy
new file mode 100644
index 0000000..cfbd2f3
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/tasks/ValidateSigningTask.groovy
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.tasks
+
+import com.android.builder.model.SigningConfig
+import com.android.builder.signing.KeystoreHelper
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.Optional
+import org.gradle.api.tasks.TaskAction
+import org.gradle.tooling.BuildException
+
+/**
+ * A validate task that creates the debug keystore if it's missing.
+ * It only creates it if it's in the default debug keystore location.
+ *
+ * It's linked to a given SigningConfig
+ *
+ */
+class ValidateSigningTask extends BaseTask {
+
+ SigningConfig signingConfig
+
+ /**
+ * Annotated getter for task input.
+ *
+ * This is an Input and not an InputFile because the file might not exist.
+ * This is not actually used by the task, this is only for Gradle to check inputs.
+ *
+ * @return the path of the keystore.
+ */
+ @Input @Optional
+ String getStoreLocation() {
+ File f = signingConfig.getStoreFile()
+ if (f != null) {
+ return f.absolutePath
+ }
+ return null;
+ }
+
+ @TaskAction
+ void validate() {
+
+ File storeFile = signingConfig.getStoreFile()
+ if (storeFile != null && !storeFile.exists()) {
+ if (KeystoreHelper.defaultDebugKeystoreLocation().equals(storeFile.absolutePath)) {
+ getLogger().info("Creating default debug keystore at %s" + storeFile.absolutePath)
+ if (!KeystoreHelper.createDebugStore(signingConfig, plugin.getLogger())) {
+ throw new BuildException("Unable to recreate missing debug keystore.", null);
+ }
+ }
+ }
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/PluginHolder.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/PluginHolder.groovy
new file mode 100644
index 0000000..0ccd3e1
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/PluginHolder.groovy
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.test
+
+import com.android.build.gradle.AppPlugin
+
+/**
+ * Class used to hold a reference to an AppPlugin.
+ * This is used only to test the plugin.
+ */
+class PluginHolder {
+
+ AppPlugin plugin
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/TestOptions.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/TestOptions.groovy
new file mode 100644
index 0000000..bc16f61
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/TestOptions.groovy
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.test
+
+/**
+ * Options for the tests.
+ */
+class TestOptions {
+
+ String resultsDir
+ String reportDir
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/report/ReportType.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/report/ReportType.java
new file mode 100644
index 0000000..df983c6
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/report/ReportType.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.test.report;
+
+/**
+ * Types of report to control aggregation and display
+ */
+public enum ReportType {
+ /**
+ * Report that only shows a single flavor
+ */
+ SINGLE_FLAVOR,
+ /**
+ * Report that shows one or more flavors
+ */
+ MULTI_FLAVOR,
+ /**
+ * Report that shows multiple projects.
+ */
+ MULTI_PROJECT
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/ApkVariantData.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/ApkVariantData.java
new file mode 100644
index 0000000..9c1f580
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/ApkVariantData.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.variant;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.tasks.Dex;
+import com.android.build.gradle.tasks.PackageApplication;
+import com.android.build.gradle.tasks.ZipAlign;
+import com.android.builder.VariantConfiguration;
+import org.gradle.api.DefaultTask;
+
+/**
+ * Base data about a variant that generates an APK file.
+ */
+public abstract class ApkVariantData extends BaseVariantData {
+
+ public Dex dexTask;
+ public PackageApplication packageApplicationTask;
+ public ZipAlign zipAlignTask;
+
+ public DefaultTask installTask;
+ public DefaultTask uninstallTask;
+
+ protected ApkVariantData(@NonNull VariantConfiguration config) {
+ super(config);
+ }
+
+ @Override
+ @NonNull
+ public String getDescription() {
+ if (getVariantConfiguration().hasFlavors()) {
+ return String.format("%s build for flavor %s",
+ getCapitalizedBuildTypeName(),
+ getCapitalizedFlavorName());
+ } else {
+ return String.format("%s build", getCapitalizedBuildTypeName());
+ }
+ }
+
+ public boolean isSigned() {
+ return getVariantConfiguration().isSigningReady();
+ }
+
+ public boolean getZipAlign() {
+ return getVariantConfiguration().getBuildType().isZipAlign();
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/ApplicationVariantData.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/ApplicationVariantData.java
new file mode 100644
index 0000000..91c4d08
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/ApplicationVariantData.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.variant;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.VariantConfiguration;
+
+/**
+ * Data about a variant that produce an application APK
+ */
+public class ApplicationVariantData extends ApkVariantData implements TestedVariantData {
+
+ @Nullable
+ private TestVariantData testVariantData = null;
+
+ public ApplicationVariantData(@NonNull VariantConfiguration config) {
+ super(config);
+ }
+
+ @Override
+ public void setTestVariantData(@Nullable TestVariantData testVariantData) {
+ this.testVariantData = testVariantData;
+ }
+
+ @Override
+ @Nullable
+ public TestVariantData getTestVariantData() {
+ return testVariantData;
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/BaseVariantData.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/BaseVariantData.java
new file mode 100644
index 0000000..83794e8
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/BaseVariantData.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.variant;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.build.gradle.internal.StringHelper;
+import com.android.build.gradle.internal.dependency.VariantDependencies;
+import com.android.build.gradle.internal.tasks.PrepareDependenciesTask;
+import com.android.build.gradle.tasks.AidlCompile;
+import com.android.build.gradle.tasks.GenerateBuildConfig;
+import com.android.build.gradle.tasks.MergeAssets;
+import com.android.build.gradle.tasks.MergeResources;
+import com.android.build.gradle.tasks.NdkCompile;
+import com.android.build.gradle.tasks.ProcessAndroidResources;
+import com.android.build.gradle.tasks.ProcessManifest;
+import com.android.build.gradle.tasks.RenderscriptCompile;
+import com.android.builder.VariantConfiguration;
+import com.google.common.collect.Lists;
+import groovy.lang.Closure;
+import org.gradle.api.Task;
+import org.gradle.api.tasks.Copy;
+import org.gradle.api.tasks.compile.JavaCompile;
+import proguard.gradle.ProGuardTask;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Base data about a variant.
+ */
+public abstract class BaseVariantData {
+
+ private final VariantConfiguration variantConfiguration;
+ private VariantDependencies variantDependency;
+
+ public Task preBuildTask;
+ public PrepareDependenciesTask prepareDependenciesTask;
+ public Task sourceGenTask;
+
+ public ProcessManifest processManifestTask;
+ public RenderscriptCompile renderscriptCompileTask;
+ public AidlCompile aidlCompileTask;
+ public MergeResources mergeResourcesTask;
+ public MergeAssets mergeAssetsTask;
+ public ProcessAndroidResources processResourcesTask;
+ public GenerateBuildConfig generateBuildConfigTask;
+
+ public JavaCompile javaCompileTask;
+ public ProGuardTask proguardTask;
+ public Copy processJavaResourcesTask;
+ public NdkCompile ndkCompileTask;
+
+ private Object outputFile;
+
+ public Task assembleTask;
+
+ private List<File> extraGeneratedSourceFolders;
+
+ public BaseVariantData(@NonNull VariantConfiguration variantConfiguration) {
+ this.variantConfiguration = variantConfiguration;
+ }
+
+ @NonNull
+ public VariantConfiguration getVariantConfiguration() {
+ return variantConfiguration;
+ }
+
+ public void setVariantDependency(@NonNull VariantDependencies variantDependency) {
+ this.variantDependency = variantDependency;
+ }
+
+ @NonNull
+ public VariantDependencies getVariantDependency() {
+ return variantDependency;
+ }
+
+ @NonNull
+ public abstract String getDescription();
+
+ @Nullable
+ public String getPackageName() {
+ return variantConfiguration.getPackageName();
+ }
+
+ @NonNull
+ protected String getCapitalizedBuildTypeName() {
+ return StringHelper.capitalize(variantConfiguration.getBuildType().getName());
+ }
+
+ @NonNull
+ protected String getCapitalizedFlavorName() {
+ return StringHelper.capitalize(variantConfiguration.getFlavorName());
+ }
+
+ public void setOutputFile(Object file) {
+ outputFile = file;
+ }
+
+ public File getOutputFile() {
+ if (outputFile instanceof File) {
+ return (File) outputFile;
+ } else if (outputFile instanceof Closure) {
+ Closure c = (Closure) outputFile;
+ return (File) c.call();
+ }
+
+ assert false;
+ return null;
+ }
+
+ @VisibleForTesting
+ @NonNull
+ String getName() {
+ return variantConfiguration.getFullName();
+ }
+
+ @Nullable
+ public List<File> getExtraGeneratedSourceFolders() {
+ return extraGeneratedSourceFolders;
+ }
+
+ public void addJavaSourceFoldersToModel(@NonNull File... generatedSourceFolders) {
+ Collections.addAll(extraGeneratedSourceFolders, generatedSourceFolders);
+ }
+
+ public void addJavaSourceFoldersToModel(@NonNull Collection<File> generatedSourceFolders) {
+ extraGeneratedSourceFolders.addAll(generatedSourceFolders);
+ }
+
+ public void registerJavaGeneratingTask(@NonNull Task task, @NonNull File... generatedSourceFolders) {
+ if (extraGeneratedSourceFolders == null) {
+ extraGeneratedSourceFolders = Lists.newArrayList();
+ }
+
+ javaCompileTask.dependsOn(task);
+
+ for (File f : generatedSourceFolders) {
+ javaCompileTask.source(f);
+ }
+
+ addJavaSourceFoldersToModel(generatedSourceFolders);
+ }
+
+ public void registerJavaGeneratingTask(@NonNull Task task, @NonNull Collection<File> generatedSourceFolders) {
+ if (extraGeneratedSourceFolders == null) {
+ extraGeneratedSourceFolders = Lists.newArrayList();
+ }
+
+ javaCompileTask.dependsOn(task);
+
+ for (File f : generatedSourceFolders) {
+ javaCompileTask.source(f);
+ }
+
+ addJavaSourceFoldersToModel(generatedSourceFolders);
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/DefaultSourceProviderContainer.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/DefaultSourceProviderContainer.java
new file mode 100644
index 0000000..06f2e5f
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/DefaultSourceProviderContainer.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.variant;
+
+import com.android.annotations.NonNull;
+import com.android.builder.model.SourceProvider;
+import com.android.builder.model.SourceProviderContainer;
+
+/**
+ * Default implementation of a SourceProviderContainer that wraps an existing instance of a
+ * SourceProvider.
+ */
+public class DefaultSourceProviderContainer implements SourceProviderContainer {
+
+ @NonNull
+ private final String name;
+ @NonNull
+ private final SourceProvider sourceProvider;
+
+ public DefaultSourceProviderContainer(@NonNull String name,
+ @NonNull SourceProvider sourceProvider) {
+ this.name = name;
+ this.sourceProvider = sourceProvider;
+ }
+
+ @NonNull
+ @Override
+ public String getArtifactName() {
+ return name;
+ }
+
+ @NonNull
+ @Override
+ public SourceProvider getSourceProvider() {
+ return sourceProvider;
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/LibraryVariantData.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/LibraryVariantData.java
new file mode 100644
index 0000000..8a86851
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/LibraryVariantData.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.variant;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.VariantConfiguration;
+import org.gradle.api.tasks.bundling.Zip;
+
+/**
+ * Data about a variant that produce a Library bundle (.aar)
+ */
+public class LibraryVariantData extends BaseVariantData implements TestedVariantData {
+
+ public Zip packageLibTask;
+
+ @Nullable
+ private TestVariantData testVariantData = null;
+
+ public LibraryVariantData(@NonNull VariantConfiguration config) {
+ super(config);
+ }
+
+ @Override
+ @NonNull
+ public String getDescription() {
+ if (getVariantConfiguration().hasFlavors()) {
+ return String.format("%s build for flavor %s",
+ getCapitalizedBuildTypeName(),
+ getCapitalizedFlavorName());
+ } else {
+ return String.format("%s build", getCapitalizedBuildTypeName());
+ }
+ }
+
+ @Override
+ public void setTestVariantData(@Nullable TestVariantData testVariantData) {
+ this.testVariantData = testVariantData;
+ }
+
+ @Override
+ @Nullable
+ public TestVariantData getTestVariantData() {
+ return testVariantData;
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/TestVariantData.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/TestVariantData.java
new file mode 100644
index 0000000..b8d45ed
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/TestVariantData.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.variant;
+
+import com.android.annotations.NonNull;
+import com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask;
+import com.android.builder.VariantConfiguration;
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+/**
+ * Data about a variant that produce a test APK
+ */
+public class TestVariantData extends ApkVariantData {
+
+ public DeviceProviderInstrumentTestTask connectedTestTask;
+ public final List<DeviceProviderInstrumentTestTask> providerTestTaskList = Lists.newArrayList();
+ @NonNull
+ private final TestedVariantData testedVariantData;
+
+ public TestVariantData(@NonNull VariantConfiguration config,
+ @NonNull TestedVariantData testedVariantData) {
+ super(config);
+ this.testedVariantData = testedVariantData;
+ }
+
+ @NonNull
+ public TestedVariantData getTestedVariantData() {
+ return testedVariantData;
+ }
+
+ @Override
+ @NonNull
+ public String getDescription() {
+ if (getVariantConfiguration().hasFlavors()) {
+ return String.format("Test build for the %s%s build",
+ getCapitalizedFlavorName(), getCapitalizedBuildTypeName());
+ } else {
+ return String.format("Test build for the %s build",
+ getCapitalizedBuildTypeName());
+ }
+ }
+
+ @Override
+ public boolean getZipAlign() {
+ return false;
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/TestedVariantData.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/TestedVariantData.java
new file mode 100644
index 0000000..b955888
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/variant/TestedVariantData.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.internal.variant;
+
+import com.android.annotations.Nullable;
+
+/**
+ * A tested variant
+ */
+public interface TestedVariantData {
+
+ void setTestVariantData(@Nullable TestVariantData testVariantData);
+
+ @Nullable
+ TestVariantData getTestVariantData();
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/AidlCompile.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/AidlCompile.groovy
new file mode 100644
index 0000000..6ad9277
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/AidlCompile.groovy
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.tasks
+
+import com.android.annotations.NonNull
+import com.android.annotations.Nullable
+import com.android.build.gradle.internal.tasks.DependencyBasedCompileTask
+import com.android.builder.compiling.DependencyFileProcessor
+import com.google.common.collect.Lists
+import org.gradle.api.tasks.InputFiles
+
+/**
+ * Task to compile aidl files. Supports incremental update.
+ */
+public class AidlCompile extends DependencyBasedCompileTask {
+
+ // ----- PRIVATE TASK API -----
+
+ @InputFiles
+ List<File> sourceDirs
+
+ @InputFiles
+ List<File> importDirs
+
+ @Override
+ protected boolean isIncremental() {
+ return true
+ }
+
+ @Override
+ protected boolean supportsParallelization() {
+ return true
+ }
+
+ @Override
+ protected void compileAllFiles(DependencyFileProcessor dependencyFileProcessor) {
+ getBuilder().compileAllAidlFiles(
+ getSourceDirs(),
+ getSourceOutputDir(),
+ getImportDirs(),
+ dependencyFileProcessor)
+ }
+
+ @Override
+ protected Object incrementalSetup() {
+ List<File> fullImportDir = Lists.newArrayList()
+ fullImportDir.addAll(getImportDirs())
+ fullImportDir.addAll(getSourceDirs())
+
+ return fullImportDir
+ }
+
+ @Override
+ protected void compileSingleFile(@NonNull File file,
+ @Nullable Object data,
+ @NonNull DependencyFileProcessor dependencyFileProcessor) {
+ getBuilder().compileAidlFile(
+ file,
+ getSourceOutputDir(),
+ (List<File>)data,
+ dependencyFileProcessor)
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/Dex.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/Dex.groovy
new file mode 100644
index 0000000..c74cdb3
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/Dex.groovy
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.tasks
+import com.android.build.gradle.internal.dsl.DexOptionsImpl
+import com.android.build.gradle.internal.tasks.IncrementalTask
+import com.android.ide.common.res2.FileStatus
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.Nested
+import org.gradle.api.tasks.OutputFile
+
+public class Dex extends IncrementalTask {
+
+ // ----- PUBLIC TASK API -----
+
+ @OutputFile
+ File outputFile
+
+ // ----- PRIVATE TASK API -----
+
+ @InputFiles
+ Iterable<File> inputFiles
+
+ @InputFiles
+ Iterable<File> preDexedLibraries
+
+ @Nested
+ DexOptionsImpl dexOptions
+
+ @Override
+ protected void doFullTaskAction() {
+ getBuilder().convertByteCode(
+ getInputFiles(),
+ getPreDexedLibraries(),
+ getOutputFile(),
+ getDexOptions(),
+ false)
+ }
+
+ @Override
+ protected void doIncrementalTaskAction(Map<File, FileStatus> changedInputs) {
+ getBuilder().convertByteCode(
+ getInputFiles(),
+ getPreDexedLibraries(),
+ getOutputFile(),
+ getDexOptions(),
+ true)
+ }
+
+ @Override
+ protected boolean isIncremental() {
+ return dexOptions.incremental
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/GenerateBuildConfig.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/GenerateBuildConfig.groovy
new file mode 100644
index 0000000..1f4d7af
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/GenerateBuildConfig.groovy
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.tasks
+
+import com.android.build.gradle.internal.tasks.IncrementalTask
+import com.android.builder.compiling.BuildConfigGenerator
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.Optional
+import org.gradle.api.tasks.OutputDirectory
+
+public class GenerateBuildConfig extends IncrementalTask {
+
+ // ----- PUBLIC TASK API -----
+
+ @OutputDirectory
+ File sourceOutputDir
+
+ // ----- PRIVATE TASK API -----
+
+ @Input
+ String buildConfigPackageName
+
+ @Input
+ String appPackageName
+
+ @Input
+ boolean debuggable
+
+ @Input
+ String flavorName
+
+ @Input
+ List<String> flavorNamesWithDimensionNames
+
+ @Input
+ String buildTypeName
+
+ @Input @Optional
+ String versionName
+
+ @Input
+ int versionCode
+
+ @Input
+ List<Object> items;
+
+ @Override
+ protected void doFullTaskAction() {
+ // must clear the folder in case the packagename changed, otherwise,
+ // there'll be two classes.
+ File destinationDir = getSourceOutputDir()
+ emptyFolder(destinationDir)
+
+ BuildConfigGenerator generator = new BuildConfigGenerator(
+ getSourceOutputDir().absolutePath,
+ getBuildConfigPackageName());
+
+ // Hack (see IDEA-100046): We want to avoid reporting "condition is always true"
+ // from the data flow inspection, so use a non-constant value. However, that defeats
+ // the purpose of this flag (when not in debug mode, if (BuildConfig.DEBUG && ...) will
+ // be completely removed by the compiler), so as a hack we do it only for the case
+ // where debug is true, which is the most likely scenario while the user is looking
+ // at source code.
+ //map.put(PH_DEBUG, Boolean.toString(mDebug));
+ generator.addField("boolean", "DEBUG", getDebuggable() ? "Boolean.parseBoolean(\"true\")" : "false")
+ .addField("String", "PACKAGE_NAME", "\"${getAppPackageName()}\"")
+ .addField("String", "BUILD_TYPE", "\"${getBuildTypeName()}\"")
+ .addField("String", "FLAVOR", "\"${getFlavorName()}\"")
+ .addField("int", "VERSION_CODE", Integer.toString(getVersionCode()))
+ .addItems(getItems());
+
+ if (getVersionName() != null) {
+ generator.addField("String", "VERSION_NAME", "\"${getVersionName()}\"")
+ }
+
+ List<String> flavors = getFlavorNamesWithDimensionNames();
+ int count = flavors.size();
+ if (count > 1) {
+ for (int i = 0; i < count ; i+=2) {
+ generator.addField("String", "FLAVOR_${flavors.get(i+1)}", "\"${flavors.get(i)}\"")
+ }
+ }
+
+ generator.generate();
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/Lint.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/Lint.groovy
new file mode 100644
index 0000000..60af0a4
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/Lint.groovy
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.tasks
+
+import com.android.annotations.NonNull
+import com.android.annotations.Nullable
+import com.android.build.gradle.BasePlugin
+import com.android.build.gradle.internal.LintGradleClient
+import com.android.build.gradle.internal.model.ModelBuilder
+import com.android.builder.model.AndroidProject
+import com.android.builder.model.Variant
+import com.android.tools.lint.HtmlReporter
+import com.android.tools.lint.LintCliFlags
+import com.android.tools.lint.Reporter
+import com.android.tools.lint.Warning
+import com.android.tools.lint.XmlReporter
+import com.android.tools.lint.checks.BuiltinIssueRegistry
+import com.android.tools.lint.client.api.IssueRegistry
+import com.android.tools.lint.detector.api.Severity
+import com.google.common.collect.Maps
+import org.gradle.api.DefaultTask
+import org.gradle.api.GradleException
+import org.gradle.api.Project
+import org.gradle.api.tasks.TaskAction
+
+import static com.android.SdkConstants.DOT_XML
+
+public class Lint extends DefaultTask {
+ @NonNull private BasePlugin mPlugin
+ @Nullable private String mVariantName
+
+ public void setPlugin(@NonNull BasePlugin plugin) {
+ mPlugin = plugin
+ }
+
+ public void setVariantName(@NonNull String variantName) {
+ mVariantName = variantName
+ }
+
+ @SuppressWarnings("GroovyUnusedDeclaration")
+ @TaskAction
+ public void lint() {
+ assert project == mPlugin.getProject()
+
+ def modelProject = createAndroidProject(project)
+ if (mVariantName != null) {
+ lintSingleVariant(modelProject, mVariantName)
+ } else {
+ lintAllVariants(modelProject)
+ }
+ }
+
+ /**
+ * Runs lint individually on all the variants, and then compares the results
+ * across variants and reports these
+ */
+ public void lintAllVariants(@NonNull AndroidProject modelProject) {
+ Map<Variant,List<Warning>> warningMap = Maps.newHashMap()
+ for (Variant variant : modelProject.getVariants()) {
+ try {
+ List<Warning> warnings = runLint(modelProject, variant.getName(), false)
+ warningMap.put(variant, warnings)
+ } catch (IOException e) {
+ throw new GradleException("Invalid arguments.", e)
+ }
+ }
+
+ // Compute error matrix
+ for (Map.Entry<Variant,List<Warning>> entry : warningMap.entrySet()) {
+ def variant = entry.getKey()
+ def warnings = entry.getValue()
+ println "Ran lint on variant " + variant.getName() + ": " + warnings.size() +
+ " issues found"
+ }
+
+ List<Warning> mergedWarnings = LintGradleClient.merge(warningMap, modelProject)
+ int errorCount = 0
+ int warningCount = 0
+ for (Warning warning : mergedWarnings) {
+ if (warning.severity == Severity.ERROR || warning.severity == Severity.FATAL) {
+ errorCount++
+ } else if (warning.severity == Severity.WARNING) {
+ warningCount++
+ }
+ }
+
+ IssueRegistry registry = new BuiltinIssueRegistry()
+ LintCliFlags flags = new LintCliFlags()
+ LintGradleClient client = new LintGradleClient(registry, flags, mPlugin, modelProject,
+ null)
+ mPlugin.getExtension().lintOptions.syncTo(client, flags, null, project, true)
+ for (Reporter reporter : flags.getReporters()) {
+ reporter.write(errorCount, warningCount, mergedWarnings)
+ }
+
+ if (flags.isSetExitCode() && errorCount > 0) {
+ throw new GradleException("Lint found errors with abortOnError=true; aborting build.")
+ }
+ }
+
+ /**
+ * Runs lint on a single specified variant
+ */
+ public void lintSingleVariant(@NonNull AndroidProject modelProject, String variantName) {
+ runLint(modelProject, variantName, true)
+ }
+
+ /** Runs lint on the given variant and returns the set of warnings */
+ private List<Warning> runLint(
+ @NonNull AndroidProject modelProject,
+ @NonNull String variantName,
+ boolean report) {
+ IssueRegistry registry = new BuiltinIssueRegistry()
+ LintCliFlags flags = new LintCliFlags()
+ LintGradleClient client = new LintGradleClient(registry, flags, mPlugin, modelProject,
+ variantName)
+ mPlugin.getExtension().lintOptions.syncTo(client, flags, variantName, project, report)
+
+ List<Warning> warnings;
+ try {
+ warnings = client.run(registry)
+ } catch (IOException e) {
+ throw new GradleException("Invalid arguments.", e)
+ }
+
+ if (report && client.haveErrors() && flags.isSetExitCode()) {
+ throw new GradleException("Lint found errors with abortOnError=true; aborting build.")
+ }
+
+ return warnings;
+ }
+
+ private static AndroidProject createAndroidProject(@NonNull Project gradleProject) {
+ String modelName = AndroidProject.class.getName()
+ ModelBuilder builder = new ModelBuilder()
+ return (AndroidProject) builder.buildAll(modelName, gradleProject)
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/MergeAssets.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/MergeAssets.groovy
new file mode 100644
index 0000000..79f6190
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/MergeAssets.groovy
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.tasks
+import com.android.build.gradle.internal.tasks.IncrementalTask
+import com.android.ide.common.res2.AssetMerger
+import com.android.ide.common.res2.AssetSet
+import com.android.ide.common.res2.FileStatus
+import com.android.ide.common.res2.FileValidity
+import com.android.ide.common.res2.MergedAssetWriter
+import com.android.ide.common.res2.MergingException
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.OutputDirectory
+
+public class MergeAssets extends IncrementalTask {
+
+ // ----- PUBLIC TASK API -----
+
+ @OutputDirectory
+ File outputDir
+
+ // ----- PRIVATE TASK API -----
+
+ // fake input to detect changes. Not actually used by the task
+ @InputFiles
+ Iterable<File> getRawInputFolders() {
+ return flattenSourceSets(getInputAssetSets())
+ }
+
+ // actual inputs
+ List<AssetSet> inputAssetSets
+
+ private final FileValidity<AssetSet> fileValidity = new FileValidity<AssetSet>();
+
+ @Override
+ protected boolean isIncremental() {
+ return true
+ }
+
+ @Override
+ protected void doFullTaskAction() {
+ // this is full run, clean the previous output
+ File destinationDir = getOutputDir()
+ emptyFolder(destinationDir)
+
+ List<AssetSet> assetSets = getInputAssetSets()
+
+ // create a new merger and populate it with the sets.
+ AssetMerger merger = new AssetMerger()
+
+ try {
+ for (AssetSet assetSet : assetSets) {
+ // set needs to be loaded.
+ assetSet.loadFromFiles(plugin.logger)
+ merger.addDataSet(assetSet)
+ }
+
+ // get the merged set and write it down.
+ MergedAssetWriter writer = new MergedAssetWriter(destinationDir)
+
+ merger.mergeData(writer, false /*doCleanUp*/)
+
+ // No exception? Write the known state.
+ merger.writeBlobTo(getIncrementalFolder(), writer)
+ } catch (MergingException e) {
+ println e.getMessage()
+ merger.cleanBlob(getIncrementalFolder())
+ throw new ResourceException(e.getMessage(), e)
+ }
+ }
+
+ @Override
+ protected void doIncrementalTaskAction(Map<File, FileStatus> changedInputs) {
+ // create a merger and load the known state.
+ AssetMerger merger = new AssetMerger()
+ try {
+ if (!merger.loadFromBlob(getIncrementalFolder(), true /*incrementalState*/)) {
+ doFullTaskAction()
+ return
+ }
+
+ // compare the known state to the current sets to detect incompatibility.
+ // This is in case there's a change that's too hard to do incrementally. In this case
+ // we'll simply revert to full build.
+ List<AssetSet> assetSets = getInputAssetSets()
+
+ if (!merger.checkValidUpdate(assetSets)) {
+ project.logger.info("Changed Asset sets: full task run!")
+ doFullTaskAction()
+ return
+ }
+
+ // The incremental process is the following:
+ // Loop on all the changed files, find which ResourceSet it belongs to, then ask
+ // the resource set to update itself with the new file.
+ for (Map.Entry<File, FileStatus> entry : changedInputs.entrySet()) {
+ File changedFile = entry.getKey()
+
+ merger.findDataSetContaining(changedFile, fileValidity)
+ if (fileValidity.status == FileValidity.FileStatus.UNKNOWN_FILE) {
+ doFullTaskAction()
+ return
+ } else if (fileValidity.status == FileValidity.FileStatus.VALID_FILE) {
+ if (!fileValidity.dataSet.updateWith(
+ fileValidity.sourceFile, changedFile, entry.getValue(),
+ plugin.logger)) {
+ project.logger.info(
+ String.format("Failed to process %s event! Full task run",
+ entry.getValue()))
+ doFullTaskAction()
+ return
+ }
+ }
+ }
+
+ MergedAssetWriter writer = new MergedAssetWriter(getOutputDir())
+
+ merger.mergeData(writer, false /*doCleanUp*/)
+
+ // No exception? Write the known state.
+ merger.writeBlobTo(getIncrementalFolder(), writer)
+ } catch (MergingException e) {
+ println e.getMessage()
+ merger.cleanBlob(getIncrementalFolder())
+ throw new ResourceException(e.getMessage(), e)
+ }
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/MergeResources.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/MergeResources.groovy
new file mode 100644
index 0000000..b6487df
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/MergeResources.groovy
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.tasks
+
+import com.android.build.gradle.LibraryPlugin
+import com.android.build.gradle.internal.tasks.IncrementalTask
+import com.android.ide.common.res2.FileStatus
+import com.android.ide.common.res2.FileValidity
+import com.android.ide.common.res2.MergedResourceWriter
+import com.android.ide.common.res2.MergingException
+import com.android.ide.common.res2.ResourceMerger
+import com.android.ide.common.res2.ResourceSet
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.OutputDirectory
+
+public class MergeResources extends IncrementalTask {
+
+ // ----- PUBLIC TASK API -----
+
+ @OutputDirectory
+ File outputDir
+
+ // ----- PRIVATE TASK API -----
+
+ // fake input to detect changes. Not actually used by the task
+ @InputFiles
+ Iterable<File> getRawInputFolders() {
+ return flattenSourceSets(getInputResourceSets())
+ }
+
+ @Input
+ boolean process9Patch
+
+ // actual inputs
+ List<ResourceSet> inputResourceSets
+
+ private final FileValidity<ResourceSet> fileValidity = new FileValidity<ResourceSet>();
+
+ @Override
+ protected boolean isIncremental() {
+ return true
+ }
+
+ @Override
+ protected void doFullTaskAction() {
+ // this is full run, clean the previous output
+ File destinationDir = getOutputDir()
+ emptyFolder(destinationDir)
+
+ List<ResourceSet> resourceSets = getInputResourceSets()
+
+ // create a new merger and populate it with the sets.
+ ResourceMerger merger = new ResourceMerger()
+
+ try {
+ for (ResourceSet resourceSet : resourceSets) {
+ // set needs to be loaded.
+ resourceSet.loadFromFiles(plugin.logger)
+ merger.addDataSet(resourceSet)
+ }
+
+ // get the merged set and write it down.
+ MergedResourceWriter writer = new MergedResourceWriter(
+ destinationDir, getProcess9Patch() ? builder.aaptRunner : null)
+ writer.setInsertSourceMarkers(builder.isInsertSourceMarkers())
+
+ merger.mergeData(writer, false /*doCleanUp*/)
+
+ // No exception? Write the known state.
+ merger.writeBlobTo(getIncrementalFolder(), writer)
+ } catch (MergingException e) {
+ println e.getMessage()
+ merger.cleanBlob(getIncrementalFolder())
+ throw new ResourceException(e.getMessage(), e)
+ }
+ }
+
+ @Override
+ protected void doIncrementalTaskAction(Map<File, FileStatus> changedInputs) {
+ // create a merger and load the known state.
+ ResourceMerger merger = new ResourceMerger()
+ try {
+ if (!merger.loadFromBlob(getIncrementalFolder(), true /*incrementalState*/)) {
+ doFullTaskAction()
+ return
+ }
+
+ // compare the known state to the current sets to detect incompatibility.
+ // This is in case there's a change that's too hard to do incrementally. In this case
+ // we'll simply revert to full build.
+ List<ResourceSet> resourceSets = getInputResourceSets()
+
+ if (!merger.checkValidUpdate(resourceSets)) {
+ project.logger.info("Changed Resource sets: full task run!")
+ doFullTaskAction()
+ return
+ }
+
+ // The incremental process is the following:
+ // Loop on all the changed files, find which ResourceSet it belongs to, then ask
+ // the resource set to update itself with the new file.
+ for (Map.Entry<File, FileStatus> entry : changedInputs.entrySet()) {
+ File changedFile = entry.getKey()
+
+ merger.findDataSetContaining(changedFile, fileValidity)
+ if (fileValidity.status == FileValidity.FileStatus.UNKNOWN_FILE) {
+ doFullTaskAction()
+ return
+ } else if (fileValidity.status == FileValidity.FileStatus.VALID_FILE) {
+ if (!fileValidity.dataSet.updateWith(
+ fileValidity.sourceFile, changedFile, entry.getValue(),
+ plugin.logger)) {
+ project.logger.info(
+ String.format("Failed to process %s event! Full task run",
+ entry.getValue()))
+ doFullTaskAction()
+ return
+ }
+ }
+ }
+
+ MergedResourceWriter writer = new MergedResourceWriter(
+ getOutputDir(), getProcess9Patch() ? builder.aaptRunner : null)
+ writer.setInsertSourceMarkers(builder.isInsertSourceMarkers())
+ merger.mergeData(writer, false /*doCleanUp*/)
+ // No exception? Write the known state.
+ merger.writeBlobTo(getIncrementalFolder(), writer)
+ } catch (MergingException e) {
+ println e.getMessage()
+ merger.cleanBlob(getIncrementalFolder())
+ throw new ResourceException(e.getMessage(), e)
+ }
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/NdkCompile.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/NdkCompile.groovy
new file mode 100644
index 0000000..eba8d72
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/NdkCompile.groovy
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.tasks
+import com.android.annotations.NonNull
+import com.android.build.gradle.internal.tasks.NdkTask
+import com.android.builder.model.NdkConfig
+import com.android.sdklib.IAndroidTarget
+import com.google.common.base.Charsets
+import com.google.common.base.Joiner
+import com.google.common.collect.Lists
+import com.google.common.io.Files
+import org.gradle.api.GradleException
+import org.gradle.api.file.FileTree
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.TaskAction
+import org.gradle.api.tasks.incremental.IncrementalTaskInputs
+import org.gradle.api.tasks.util.PatternSet
+/**
+ */
+class NdkCompile extends NdkTask {
+
+ List<File> sourceFolders
+
+ @OutputFile
+ File generatedMakefile
+
+ @Input
+ boolean debuggable
+
+ @OutputDirectory
+ File soFolder
+
+ @OutputDirectory
+ File objFolder
+
+ @Input
+ boolean ndkRenderScriptMode
+
+ @InputFiles
+ FileTree getSource() {
+ FileTree src = null
+ List<File> sources = getSourceFolders()
+ if (!sources.isEmpty()) {
+ src = getProject().files(new ArrayList<Object>(sources)).getAsFileTree()
+ }
+ return src == null ? getProject().files().getAsFileTree() : src
+ }
+
+ @TaskAction
+ void taskAction(IncrementalTaskInputs inputs) {
+
+ FileTree sourceFileTree = getSource()
+ Set<File> sourceFiles = sourceFileTree.matching(new PatternSet().exclude("**/*.h")).files
+ File makefile = getGeneratedMakefile()
+
+ if (sourceFiles.isEmpty()) {
+ makefile.delete()
+ emptyFolder(getSoFolder())
+ emptyFolder(getObjFolder())
+ return
+ }
+
+ File ndkDirectory = getPlugin().ndkDirectory
+ if (ndkDirectory == null || !ndkDirectory.isDirectory()) {
+ throw new GradleException("NDK not configured")
+ }
+
+ boolean generateMakefile = false
+
+ if (!inputs.isIncremental()) {
+ project.logger.info("Unable do incremental execution: full task run")
+ generateMakefile = true
+ emptyFolder(getSoFolder())
+ emptyFolder(getObjFolder())
+ } else {
+ // look for added or removed files *only*
+
+ //noinspection GroovyAssignabilityCheck
+ inputs.outOfDate { change ->
+ if (change.isAdded()) {
+ generateMakefile = true
+ }
+ }
+
+ //noinspection GroovyAssignabilityCheck
+ inputs.removed { change ->
+ generateMakefile = true
+ }
+ }
+
+ if (generateMakefile) {
+ writeMakefile(sourceFiles, makefile)
+ }
+
+ // now build
+ runNdkBuild(ndkDirectory, makefile)
+ }
+
+ private void writeMakefile(@NonNull Set<File> sourceFiles, @NonNull File makefile) {
+ NdkConfig ndk = getNdkConfig()
+
+ StringBuilder sb = new StringBuilder()
+
+ sb.append(
+ 'LOCAL_PATH := $(call my-dir)\n' +
+ 'include \$(CLEAR_VARS)\n\n')
+
+ sb.append('LOCAL_MODULE := ').append(ndk.moduleName != null ? ndk.moduleName : project.name).append('\n')
+
+ if (ndk.cFlags != null) {
+ sb.append('LOCAL_CFLAGS := ').append(ndk.cFlags).append('\n')
+ }
+
+ List<String> fullLdlibs = Lists.newArrayList()
+ if (ndk.ldLibs != null) {
+ fullLdlibs.addAll(ndk.ldLibs)
+ }
+ if (getNdkRenderScriptMode()) {
+ fullLdlibs.add("dl")
+ fullLdlibs.add("log")
+ fullLdlibs.add("jnigraphics")
+ fullLdlibs.add("RScpp_static")
+ fullLdlibs.add("cutils")
+ }
+
+ if (!fullLdlibs.isEmpty()) {
+ sb.append('LOCAL_LDLIBS := \\\n')
+ for (String lib : fullLdlibs) {
+ sb.append('\t-l') .append(lib).append(' \\\n')
+ }
+ sb.append('\n')
+ }
+
+ sb.append('LOCAL_SRC_FILES := \\\n')
+ for (File sourceFile : sourceFiles) {
+ sb.append('\t').append(sourceFile.absolutePath).append(' \\\n')
+ }
+ sb.append('\n')
+
+ for (File sourceFolder : getSourceFolders()) {
+ sb.append("LOCAL_C_INCLUDES += ${sourceFolder.absolutePath}\n")
+ }
+
+ if (getNdkRenderScriptMode()) {
+ sb.append('LOCAL_LDFLAGS += -L$(call host-path,$(TARGET_C_INCLUDES)/../lib/rs)\n')
+
+ sb.append('LOCAL_C_INCLUDES += $(TARGET_C_INCLUDES)/rs/cpp\n')
+ sb.append('LOCAL_C_INCLUDES += $(TARGET_C_INCLUDES)/rs\n')
+ sb.append('LOCAL_C_INCLUDES += $(TARGET_OBJS)/$(LOCAL_MODULE)\n')
+ }
+
+ sb.append(
+ '\ninclude \$(BUILD_SHARED_LIBRARY)\n')
+
+ Files.write(sb.toString(), makefile, Charsets.UTF_8)
+ }
+
+ private void runNdkBuild(@NonNull File ndkLocation, @NonNull File makefile) {
+ NdkConfig ndk = getNdkConfig()
+
+ List<String> commands = Lists.newArrayList()
+
+ commands.add(ndkLocation.absolutePath + File.separator + "ndk-build")
+
+ commands.add("NDK_PROJECT_PATH=null")
+
+ commands.add("APP_BUILD_SCRIPT=" + makefile.absolutePath)
+
+ // target
+ IAndroidTarget target = getPlugin().loadedSdkParser.target
+ if (!target.isPlatform()) {
+ target = target.parent
+ }
+ commands.add("APP_PLATFORM=" + target.hashString())
+
+ // temp out
+ commands.add("NDK_OUT=" + getObjFolder().absolutePath)
+
+ // libs out
+ commands.add("NDK_LIBS_OUT=" + getSoFolder().absolutePath)
+
+ // debug builds
+ if (getDebuggable()) {
+ commands.add("NDK_DEBUG=1")
+ }
+
+ if (ndk.getStl() != null) {
+ commands.add("APP_STL=" + ndk.getStl())
+ }
+
+ Set<String> abiFilters = ndk.abiFilters
+ if (abiFilters != null && !abiFilters.isEmpty()) {
+ if (abiFilters.size() == 1) {
+ commands.add("APP_ABI=" + abiFilters.iterator().next())
+ } else {
+ Joiner joiner = Joiner.on(',').skipNulls()
+ commands.add("APP_ABI=" + joiner.join(abiFilters.iterator()))
+ }
+ } else {
+ commands.add("APP_ABI=all")
+ }
+
+ getBuilder().commandLineRunner.runCmdLine(commands, null)
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/PackageApplication.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/PackageApplication.groovy
new file mode 100644
index 0000000..de6d159
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/PackageApplication.groovy
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.tasks
+
+import com.android.build.gradle.internal.dsl.SigningConfigDsl
+import com.android.build.gradle.internal.tasks.IncrementalTask
+import com.android.build.gradle.internal.tasks.OutputFileTask
+import com.android.builder.packaging.DuplicateFileException
+import org.gradle.api.file.FileTree
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputDirectory
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.Nested
+import org.gradle.api.tasks.Optional
+import org.gradle.api.tasks.OutputFile
+import org.gradle.tooling.BuildException
+
+public class PackageApplication extends IncrementalTask implements OutputFileTask {
+
+ // ----- PUBLIC TASK API -----
+
+ @InputFile
+ File resourceFile
+
+ @InputFile
+ File dexFile
+
+ @InputDirectory @Optional
+ File javaResourceDir
+
+ Set<File> jniFolders
+
+ @OutputFile
+ File outputFile
+
+ @Input @Optional
+ Set<String> abiFilters
+
+ // ----- PRIVATE TASK API -----
+
+ @InputFiles
+ List<File> packagedJars
+
+ @Input
+ boolean jniDebugBuild
+
+ @Nested @Optional
+ SigningConfigDsl signingConfig
+
+ @InputFiles
+ public FileTree getNativeLibraries() {
+ FileTree src = null
+ Set<File> folders = getJniFolders()
+ if (!folders.isEmpty()) {
+ src = getProject().files(new ArrayList<Object>(folders)).getAsFileTree()
+ }
+ return src == null ? getProject().files().getAsFileTree() : src
+ }
+
+ @Override
+ protected void doFullTaskAction() {
+ try {
+ getBuilder().packageApk(
+ getResourceFile().absolutePath,
+ getDexFile().absolutePath,
+ getPackagedJars(),
+ getJavaResourceDir()?.absolutePath,
+ getJniFolders(),
+ getAbiFilters(),
+ getJniDebugBuild(),
+ getSigningConfig(),
+ getOutputFile().absolutePath)
+ } catch (DuplicateFileException e) {
+ def logger = getLogger()
+ logger.error("Error: duplicate files during packaging of APK " + getOutputFile().absolutePath)
+ logger.error("\tPath in archive: " + e.archivePath)
+ logger.error("\tOrigin 1: " + e.file1)
+ logger.error("\tOrigin 2: " + e.file2)
+ throw new BuildException(e.getMessage(), e);
+ } catch (Exception e) {
+ throw new BuildException(e.getMessage(), e);
+ }
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/PreDex.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/PreDex.groovy
new file mode 100644
index 0000000..6a464fe
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/PreDex.groovy
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.tasks
+import com.android.SdkConstants
+import com.android.annotations.NonNull
+import com.android.build.gradle.internal.dsl.DexOptionsImpl
+import com.android.build.gradle.internal.tasks.BaseTask
+import com.android.builder.AndroidBuilder
+import com.android.builder.DexOptions
+import com.google.common.hash.HashCode
+import com.google.common.hash.HashFunction
+import com.google.common.hash.Hashing
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.Nested
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.TaskAction
+import org.gradle.api.tasks.incremental.IncrementalTaskInputs
+
+public class PreDex extends BaseTask {
+
+ // ----- PUBLIC TASK API -----
+
+ // ----- PRIVATE TASK API -----
+
+ // this is used automatically by Gradle, even though nothing
+ // in the class uses it.
+ @SuppressWarnings("GroovyUnusedDeclaration")
+ @InputFiles
+ Iterable<File> inputFiles
+
+ @OutputDirectory
+ File outputFolder
+
+ @Nested
+ DexOptionsImpl dexOptions
+
+ @TaskAction
+ void taskAction(IncrementalTaskInputs taskInputs) {
+ final File outFolder = getOutputFolder()
+ final DexOptions options = getDexOptions()
+
+ // if we are not in incremental mode, then outOfDate will contain
+ // all th files, but first we need to delete the previous output
+ if (!taskInputs.isIncremental()) {
+ emptyFolder(outFolder)
+ }
+
+ AndroidBuilder builder = getBuilder()
+
+ taskInputs.outOfDate { change ->
+
+ //noinspection GroovyAssignabilityCheck
+ File preDexedFile = getDexFileName(outFolder, change.file)
+ //noinspection GroovyAssignabilityCheck
+ builder.preDexLibrary(change.file, preDexedFile, options)
+ }
+
+ taskInputs.removed { change ->
+ //noinspection GroovyAssignabilityCheck
+ File preDexedFile = getDexFileName(outFolder, change.file)
+ preDexedFile.delete()
+ }
+ }
+
+ /**
+ * Returns a unique File for the pre-dexed library, even
+ * if there are 2 libraries with the same file names (but different
+ * paths)
+ *
+ * @param outFolder
+ * @param inputFile the library
+ * @return
+ */
+ @NonNull
+ private static File getDexFileName(@NonNull File outFolder, @NonNull File inputFile) {
+ // get the filename
+ String name = inputFile.getName();
+ // remove the extension
+ int pos = name.lastIndexOf('.');
+ if (pos != -1) {
+ name = name.substring(0, pos);
+ }
+
+ // add a hash of the original file path
+ HashFunction hashFunction = Hashing.md5();
+ HashCode hashCode = hashFunction.hashString(inputFile.getAbsolutePath());
+
+ return new File(outFolder, name + "-" + hashCode.toString() + SdkConstants.DOT_JAR);
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/ProcessAndroidResources.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/ProcessAndroidResources.groovy
new file mode 100644
index 0000000..3c981ac
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/ProcessAndroidResources.groovy
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.tasks
+import com.android.build.gradle.internal.dependency.SymbolFileProviderImpl
+import com.android.build.gradle.internal.dsl.AaptOptionsImpl
+import com.android.build.gradle.internal.tasks.IncrementalTask
+import com.android.builder.VariantConfiguration
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputDirectory
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.Nested
+import org.gradle.api.tasks.Optional
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.OutputFile
+
+public class ProcessAndroidResources extends IncrementalTask {
+
+ // ----- PUBLIC TASK API -----
+
+ @InputFile
+ File manifestFile
+
+ @InputDirectory
+ File resDir
+
+ @InputDirectory @Optional
+ File assetsDir
+
+ @OutputDirectory @Optional
+ File sourceOutputDir
+
+ @OutputDirectory @Optional
+ File textSymbolOutputDir
+
+ @OutputFile @Optional
+ File packageOutputFile
+
+ @OutputFile @Optional
+ File proguardOutputFile
+
+ @Input
+ Collection<String> resourceConfigs
+
+ // ----- PRIVATE TASK API -----
+
+ @Nested
+ List<SymbolFileProviderImpl> libraries
+
+ @Input @Optional
+ String packageForR
+
+ // this doesn't change from one build to another, so no need to annotate
+ VariantConfiguration.Type type
+
+ @Input
+ boolean debuggable
+
+ @Nested
+ AaptOptionsImpl aaptOptions
+
+ @Override
+ protected void doFullTaskAction() {
+ // we have to clean the source folder output in case the package name changed.
+ File srcOut = getSourceOutputDir()
+ if (srcOut != null) {
+ emptyFolder(srcOut)
+ }
+
+ getBuilder().processResources(
+ getManifestFile(),
+ getResDir(),
+ getAssetsDir(),
+ getLibraries(),
+ getPackageForR(),
+ srcOut?.absolutePath,
+ getTextSymbolOutputDir()?.absolutePath,
+ getPackageOutputFile()?.absolutePath,
+ getProguardOutputFile()?.absolutePath,
+ getType(),
+ getDebuggable(),
+ getAaptOptions(),
+ getResourceConfigs())
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/ProcessAppManifest.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/ProcessAppManifest.groovy
new file mode 100644
index 0000000..72ddcd8
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/ProcessAppManifest.groovy
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.tasks
+import com.android.build.gradle.internal.dependency.ManifestDependencyImpl
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.Nested
+import org.gradle.api.tasks.Optional
+/**
+ * A task that processes the manifest
+ */
+public class ProcessAppManifest extends ProcessManifest {
+
+ // ----- PRIVATE TASK API -----
+
+ @InputFile
+ File mainManifest
+
+ @InputFiles
+ List<File> manifestOverlays
+
+ @Nested
+ List<ManifestDependencyImpl> libraries
+
+ @Input @Optional
+ String packageNameOverride
+
+ @Input
+ int versionCode
+
+ @Input @Optional
+ String versionName
+
+ @Input
+ int minSdkVersion
+
+ @Input
+ int targetSdkVersion
+
+ @Override
+ protected void doFullTaskAction() {
+ getBuilder().processManifest(
+ getMainManifest(),
+ getManifestOverlays(),
+ getLibraries(),
+ getPackageNameOverride(),
+ getVersionCode(),
+ getVersionName(),
+ getMinSdkVersion(),
+ getTargetSdkVersion(),
+ getManifestOutputFile().absolutePath)
+ }
+
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/ProcessManifest.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/ProcessManifest.groovy
new file mode 100644
index 0000000..03d0d20
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/ProcessManifest.groovy
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.tasks
+import com.android.build.gradle.internal.tasks.IncrementalTask
+import org.gradle.api.tasks.OutputFile
+/**
+ * A task that processes the manifest
+ */
+public abstract class ProcessManifest extends IncrementalTask {
+
+ // ----- PUBLIC TASK API -----
+
+ /**
+ * The processed Manifest.
+ */
+ @OutputFile
+ File manifestOutputFile
+
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/ProcessTestManifest.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/ProcessTestManifest.groovy
new file mode 100644
index 0000000..6a24155
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/ProcessTestManifest.groovy
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.tasks
+
+import com.android.build.gradle.internal.dependency.ManifestDependencyImpl
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.Nested
+
+/**
+ * A task that processes the manifest
+ */
+public class ProcessTestManifest extends ProcessManifest {
+
+ // ----- PRIVATE TASK API -----
+
+ @Input
+ String testPackageName
+
+ @Input
+ int minSdkVersion
+
+ @Input
+ int targetSdkVersion
+
+ @Input
+ String testedPackageName
+
+ @Input
+ String instrumentationRunner
+
+ @Input
+ Boolean handleProfiling;
+
+ @Input
+ Boolean functionalTest;
+
+ @Nested
+ List<ManifestDependencyImpl> libraries
+
+ @Override
+ protected void doFullTaskAction() {
+ getBuilder().processTestManifest(
+ getTestPackageName(),
+ getMinSdkVersion(),
+ getTargetSdkVersion(),
+ getTestedPackageName(),
+ getInstrumentationRunner(),
+ getHandleProfiling(),
+ getFunctionalTest(),
+ getLibraries(),
+ getManifestOutputFile().absolutePath)
+ }
+
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/RenderscriptCompile.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/RenderscriptCompile.groovy
new file mode 100644
index 0000000..a5f7986
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/RenderscriptCompile.groovy
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.tasks
+
+import com.android.build.gradle.internal.tasks.NdkTask
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.TaskAction
+/**
+ * Task to compile Renderscript files. Supports incremental update.
+ */
+public class RenderscriptCompile extends NdkTask {
+
+ // ----- PUBLIC TASK API -----
+
+ @OutputDirectory
+ File sourceOutputDir
+
+ @OutputDirectory
+ File resOutputDir
+
+ @OutputDirectory
+ File objOutputDir
+
+ @OutputDirectory
+ File libOutputDir
+
+
+ // ----- PRIVATE TASK API -----
+
+ @InputFiles
+ List<File> sourceDirs
+
+ @InputFiles
+ List<File> importDirs
+
+ @Input
+ int targetApi
+
+ @Input
+ boolean supportMode
+
+ @Input
+ int optimLevel
+
+ @Input
+ boolean debugBuild
+
+ @Input
+ boolean ndkMode
+
+ @TaskAction
+ void taskAction() {
+ // this is full run (always), clean the previous outputs
+ File sourceDestDir = getSourceOutputDir()
+ emptyFolder(sourceDestDir)
+
+ File resDestDir = getResOutputDir()
+ emptyFolder(resDestDir)
+
+ File objDestDir = getObjOutputDir()
+ emptyFolder(objDestDir)
+
+ File libDestDir = getLibOutputDir()
+ emptyFolder(libDestDir)
+
+ // get the import folders. If the .rsh files are not directly under the import folders,
+ // we need to get the leaf folders, as this is what llvm-rs-cc expects.
+ List<File> importFolders = getBuilder().getLeafFolders("rsh",
+ getImportDirs(), getSourceDirs())
+
+ getBuilder().compileAllRenderscriptFiles(
+ getSourceDirs(),
+ importFolders,
+ sourceDestDir,
+ resDestDir,
+ objDestDir,
+ libDestDir,
+ getTargetApi(),
+ getDebugBuild(),
+ getOptimLevel(),
+ getNdkMode(),
+ getSupportMode(),
+ getNdkConfig()?.abiFilters)
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/ResourceException.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/ResourceException.java
new file mode 100644
index 0000000..7d1b142
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/ResourceException.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.tasks;
+
+import com.android.ide.common.res2.MergingException;
+
+/**
+ * Exception used for resource merging errors, thrown when
+ * a {@link MergingException} is thrown by the resource merging code.
+ * We can't just rethrow the {@linkplain MergingException} because
+ * gradle 1.8 seems to want a RuntimeException; without it you get
+ * the error message
+ * {@code
+ * > Could not call IncrementalTask.taskAction() on task ':MyPrj:mergeDebugResources'
+ * }
+ */
+public class ResourceException extends RuntimeException {
+ public ResourceException(String message, Throwable throwable) {
+ super(message, throwable);
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/ZipAlign.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/ZipAlign.groovy
new file mode 100644
index 0000000..517a6ab
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/tasks/ZipAlign.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle.tasks
+
+import com.android.build.gradle.internal.tasks.OutputFileTask
+import org.gradle.api.DefaultTask
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.TaskAction
+
+public class ZipAlign extends DefaultTask implements OutputFileTask {
+
+ // ----- PUBLIC TASK API -----
+
+ @OutputFile
+ File outputFile
+
+ @InputFile
+ File inputFile
+
+ // ----- PRIVATE TASK API -----
+
+ @InputFile
+ File zipAlignExe
+
+ @TaskAction
+ void zipAlign() {
+ project.exec {
+ executable = getZipAlignExe()
+ args '-f', '4'
+ args getInputFile()
+ args getOutputFile()
+ }
+ }
+}
diff --git a/build-system/gradle/src/main/resources/META-INF/gradle-plugins/android-library.properties b/build-system/gradle/src/main/resources/META-INF/gradle-plugins/android-library.properties
new file mode 100644
index 0000000..c3e8a16
--- /dev/null
+++ b/build-system/gradle/src/main/resources/META-INF/gradle-plugins/android-library.properties
@@ -0,0 +1 @@
+implementation-class=com.android.build.gradle.LibraryPlugin
\ No newline at end of file
diff --git a/build-system/gradle/src/main/resources/META-INF/gradle-plugins/android-reporting.properties b/build-system/gradle/src/main/resources/META-INF/gradle-plugins/android-reporting.properties
new file mode 100644
index 0000000..38b4622
--- /dev/null
+++ b/build-system/gradle/src/main/resources/META-INF/gradle-plugins/android-reporting.properties
@@ -0,0 +1 @@
+implementation-class=com.android.build.gradle.ReportingPlugin
\ No newline at end of file
diff --git a/build-system/gradle/src/main/resources/META-INF/gradle-plugins/android.properties b/build-system/gradle/src/main/resources/META-INF/gradle-plugins/android.properties
new file mode 100644
index 0000000..88dd73d
--- /dev/null
+++ b/build-system/gradle/src/main/resources/META-INF/gradle-plugins/android.properties
@@ -0,0 +1 @@
+implementation-class=com.android.build.gradle.AppPlugin
\ No newline at end of file
diff --git a/build-system/gradle/src/test/groovy/com/android/build/gradle/AppPluginDslTest.groovy b/build-system/gradle/src/test/groovy/com/android/build/gradle/AppPluginDslTest.groovy
new file mode 100644
index 0000000..bef2ec7
--- /dev/null
+++ b/build-system/gradle/src/test/groovy/com/android/build/gradle/AppPluginDslTest.groovy
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle
+import com.android.annotations.NonNull
+import com.android.build.gradle.api.ApkVariant
+import com.android.build.gradle.api.ApplicationVariant
+import com.android.build.gradle.api.TestVariant
+import com.android.build.gradle.internal.test.BaseTest
+import org.gradle.api.Project
+import org.gradle.testfixtures.ProjectBuilder
+/**
+ * Tests for the public DSL of the App plugin ("android")
+ */
+public class AppPluginDslTest extends BaseTest {
+
+ @Override
+ protected void setUp() throws Exception {
+ BasePlugin.TEST_SDK_DIR = new File("foo")
+ }
+
+ public void testBasic() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "basic")).build()
+
+ project.apply plugin: 'android'
+
+ project.android {
+ compileSdkVersion 15
+ }
+
+ project.afterEvaluate {
+ Set<ApplicationVariant> variants = project.android.applicationVariants
+ assertEquals(2, variants.size())
+
+ Set<TestVariant> testVariants = project.android.testVariants
+ assertEquals(1, testVariants.size())
+
+ checkTestedVariant("Debug", "Test", variants, testVariants)
+ checkNonTestedVariant("Release", variants)
+ }
+ }
+
+ /**
+ * Same as Basic but with a slightly different DSL.
+ */
+ public void testBasic2() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "basic")).build()
+
+ project.apply plugin: 'android'
+
+ project.android {
+ compileSdkVersion = 15
+ }
+
+ project.afterEvaluate {
+ Set<ApplicationVariant> variants = project.android.applicationVariants
+ assertEquals(2, variants.size())
+
+ Set<TestVariant> testVariants = project.android.testVariants
+ assertEquals(1, testVariants.size())
+
+ checkTestedVariant("Debug", "Test", variants, testVariants)
+ checkNonTestedVariant("Release", variants)
+ }
+ }
+
+ public void testBasicWithStringTarget() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "basic")).build()
+
+ project.apply plugin: 'android'
+
+ project.android {
+ compileSdkVersion "android-15"
+ }
+
+ project.afterEvaluate {
+ Set<ApplicationVariant> variants = project.android.applicationVariants
+ assertEquals(2, variants.size())
+
+ Set<TestVariant> testVariants = project.android.testVariants
+ assertEquals(1, testVariants.size())
+
+ checkTestedVariant("Debug", "Test", variants, testVariants)
+ checkNonTestedVariant("Release", variants)
+ }
+ }
+
+ public void testMultiRes() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "multires")).build()
+
+ project.apply plugin: 'android'
+
+ project.android {
+ compileSdkVersion 15
+
+ sourceSets {
+ main {
+ res {
+ srcDirs 'src/main/res1', 'src/main/res2'
+ }
+ }
+ }
+ }
+
+ // nothing to be done here. If the DSL fails, it'll throw an exception
+ }
+
+ public void testBuildTypes() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "basic")).build()
+
+ project.apply plugin: 'android'
+
+ project.android {
+ compileSdkVersion 15
+ testBuildType "staging"
+
+ buildTypes {
+ staging {
+ signingConfig signingConfigs.debug
+ }
+ }
+ }
+
+ project.afterEvaluate {
+ // does not include tests
+ Set<ApplicationVariant> variants = project.android.applicationVariants
+ assertEquals(3, variants.size())
+
+ Set<TestVariant> testVariants = project.android.testVariants
+ assertEquals(1, testVariants.size())
+
+ checkTestedVariant("Staging", "Test", variants, testVariants)
+
+ checkNonTestedVariant("Debug", variants)
+ checkNonTestedVariant("Release", variants)
+ }
+ }
+
+ public void testFlavors() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "basic")).build()
+
+ project.apply plugin: 'android'
+
+ project.android {
+ compileSdkVersion 15
+
+ productFlavors {
+ flavor1 {
+
+ }
+ flavor2 {
+
+ }
+ }
+ }
+
+ project.afterEvaluate {
+ // does not include tests
+ Set<ApplicationVariant> variants = project.android.applicationVariants
+ assertEquals(4, variants.size())
+
+ Set<TestVariant> testVariants = project.android.testVariants
+ assertEquals(2, testVariants.size())
+
+ checkTestedVariant("Flavor1Debug", "Flavor1Test", variants, testVariants)
+ checkTestedVariant("Flavor2Debug", "Flavor2Test", variants, testVariants)
+
+ checkNonTestedVariant("Flavor1Release", variants)
+ checkNonTestedVariant("Flavor2Release", variants)
+ }
+ }
+
+ public void testMultiFlavors() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "basic")).build()
+
+ project.apply plugin: 'android'
+
+ project.android {
+ compileSdkVersion 15
+
+ flavorGroups "group1", "group2"
+
+ productFlavors {
+ f1 {
+ flavorGroup "group1"
+ }
+ f2 {
+ flavorGroup "group1"
+ }
+
+ fa {
+ flavorGroup "group2"
+ }
+ fb {
+ flavorGroup "group2"
+ }
+ fc {
+ flavorGroup "group2"
+ }
+ }
+ }
+
+ project.afterEvaluate {
+ // does not include tests
+ Set<ApplicationVariant> variants = project.android.applicationVariants
+ assertEquals(12, variants.size())
+
+ Set<TestVariant> testVariants = project.android.testVariants
+ assertEquals(6, testVariants.size())
+
+ checkTestedVariant("F1FaDebug", "F1FaTest", variants, testVariants)
+ checkTestedVariant("F1FbDebug", "F1FbTest", variants, testVariants)
+ checkTestedVariant("F1FcDebug", "F1FcTest", variants, testVariants)
+ checkTestedVariant("F2FaDebug", "F2FaTest", variants, testVariants)
+ checkTestedVariant("F2FbDebug", "F2FbTest", variants, testVariants)
+ checkTestedVariant("F2FcDebug", "F2FcTest", variants, testVariants)
+
+ checkNonTestedVariant("F1FaRelease", variants)
+ checkNonTestedVariant("F1FbRelease", variants)
+ checkNonTestedVariant("F1FcRelease", variants)
+ checkNonTestedVariant("F2FaRelease", variants)
+ checkNonTestedVariant("F2FbRelease", variants)
+ checkNonTestedVariant("F2FcRelease", variants)
+ }
+ }
+
+ public void testSourceSetsApi() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "basic")).build()
+
+ project.apply plugin: 'android'
+
+ project.android {
+ compileSdkVersion 15
+ }
+
+ // query the sourceSets, will throw if missing
+ println project.android.sourceSets.main.java.srcDirs
+ println project.android.sourceSets.main.resources.srcDirs
+ println project.android.sourceSets.main.manifest.srcFile
+ println project.android.sourceSets.main.res.srcDirs
+ println project.android.sourceSets.main.assets.srcDirs
+ }
+
+
+ private static void checkTestedVariant(@NonNull String variantName,
+ @NonNull String testedVariantName,
+ @NonNull Set<ApplicationVariant> variants,
+ @NonNull Set<TestVariant> testVariants) {
+ ApplicationVariant variant = findNamedItem(variants, variantName, "variantData")
+ assertNotNull(variant.testVariant)
+ assertEquals(testedVariantName, variant.testVariant.name)
+ assertEquals(variant.testVariant, findNamedItemMaybe(testVariants, testedVariantName))
+ checkTasks(variant)
+ checkTasks(variant.testVariant)
+ }
+
+ private static void checkNonTestedVariant(@NonNull String variantName,
+ @NonNull Set<ApplicationVariant> variants) {
+ ApplicationVariant variant = findNamedItem(variants, variantName, "variantData")
+ assertNull(variant.testVariant)
+ checkTasks(variant)
+ }
+
+ private static void checkTasks(@NonNull ApkVariant variant) {
+ boolean isTestVariant = variant instanceof TestVariant;
+
+ assertNotNull(variant.processManifest)
+ assertNotNull(variant.aidlCompile)
+ assertNotNull(variant.mergeResources)
+ assertNotNull(variant.mergeAssets)
+ assertNotNull(variant.processResources)
+ assertNotNull(variant.generateBuildConfig)
+ assertNotNull(variant.javaCompile)
+ assertNotNull(variant.processJavaResources)
+ assertNotNull(variant.dex)
+ assertNotNull(variant.packageApplication)
+
+ assertNotNull(variant.assemble)
+ assertNotNull(variant.uninstall)
+
+ if (variant.isSigningReady()) {
+ assertNotNull(variant.install)
+
+ // tested variant are never zipAligned.
+ if (!isTestVariant && variant.buildType.zipAlign) {
+ assertNotNull(variant.zipAlign)
+ } else {
+ assertNull(variant.zipAlign)
+ }
+ } else {
+ assertNull(variant.install)
+ }
+
+ if (isTestVariant) {
+ TestVariant testVariant = variant as TestVariant
+ assertNotNull(testVariant.connectedInstrumentTest)
+ assertNotNull(testVariant.testedVariant)
+ }
+ }
+}
\ No newline at end of file
diff --git a/build-system/gradle/src/test/groovy/com/android/build/gradle/AppPluginInternalTest.groovy b/build-system/gradle/src/test/groovy/com/android/build/gradle/AppPluginInternalTest.groovy
new file mode 100644
index 0000000..4578945
--- /dev/null
+++ b/build-system/gradle/src/test/groovy/com/android/build/gradle/AppPluginInternalTest.groovy
@@ -0,0 +1,421 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle
+
+import com.android.build.gradle.internal.BadPluginException
+import com.android.build.gradle.internal.test.BaseTest
+import com.android.build.gradle.internal.test.PluginHolder
+import com.android.build.gradle.internal.variant.BaseVariantData
+import com.android.builder.BuilderConstants
+import com.android.builder.DefaultBuildType
+import com.android.builder.model.SigningConfig
+import com.android.builder.signing.KeystoreHelper
+import org.gradle.api.Project
+import org.gradle.testfixtures.ProjectBuilder
+
+/**
+ * Tests for the internal workings of the app plugin ("android")
+ */
+public class AppPluginInternalTest extends BaseTest {
+
+ @Override
+ protected void setUp() throws Exception {
+ BasePlugin.TEST_SDK_DIR = new File("foo")
+ AppPlugin.pluginHolder = new PluginHolder();
+ }
+
+ public void testBasic() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "basic")).build()
+
+ project.apply plugin: 'android'
+
+ project.android {
+ compileSdkVersion 15
+ }
+
+ AppPlugin plugin = AppPlugin.pluginHolder.plugin
+ plugin.createAndroidTasks(true /*force*/)
+
+ assertEquals(2, plugin.buildTypes.size())
+ assertNotNull(plugin.buildTypes.get(BuilderConstants.DEBUG))
+ assertNotNull(plugin.buildTypes.get(BuilderConstants.RELEASE))
+ assertEquals(0, plugin.productFlavors.size())
+
+
+ List<BaseVariantData> variants = plugin.variantDataList
+ assertEquals(3, variants.size()) // includes the test variant(s)
+
+ findNamedItem(variants, "debug", "variantData")
+ findNamedItem(variants, "release", "variantData")
+ findNamedItem(variants, "debugTest", "variantData")
+ }
+
+ public void testDefaultConfig() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "basic")).build()
+
+ project.apply plugin: 'android'
+
+ project.android {
+ compileSdkVersion 15
+
+ signingConfigs {
+ fakeConfig {
+ storeFile project.file("aa")
+ storePassword "bb"
+ keyAlias "cc"
+ keyPassword "dd"
+ }
+ }
+
+ defaultConfig {
+ versionCode 1
+ versionName "2.0"
+ minSdkVersion 2
+ targetSdkVersion 3
+
+ signingConfig signingConfigs.fakeConfig
+ }
+ }
+
+ AppPlugin plugin = AppPlugin.pluginHolder.plugin
+ plugin.createAndroidTasks(true /*force*/)
+
+ assertEquals(1, plugin.extension.defaultConfig.versionCode)
+ assertEquals(2, plugin.extension.defaultConfig.minSdkVersion)
+ assertEquals(3, plugin.extension.defaultConfig.targetSdkVersion)
+ assertEquals("2.0", plugin.extension.defaultConfig.versionName)
+
+ assertEquals(new File(project.projectDir, "aa"),
+ plugin.extension.defaultConfig.signingConfig.storeFile)
+ assertEquals("bb", plugin.extension.defaultConfig.signingConfig.storePassword)
+ assertEquals("cc", plugin.extension.defaultConfig.signingConfig.keyAlias)
+ assertEquals("dd", plugin.extension.defaultConfig.signingConfig.keyPassword)
+ }
+
+ public void testBuildTypes() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "basic")).build()
+
+ project.apply plugin: 'android'
+
+ project.android {
+ compileSdkVersion 15
+ testBuildType "staging"
+
+ buildTypes {
+ staging {
+ signingConfig signingConfigs.debug
+ }
+ }
+ }
+
+ AppPlugin plugin = AppPlugin.pluginHolder.plugin
+ plugin.createAndroidTasks(true /*force*/)
+
+ assertEquals(3, plugin.buildTypes.size())
+
+ List<BaseVariantData> variants = plugin.variantDataList
+ assertEquals(4, variants.size()) // includes the test variant(s)
+
+ String[] variantNames = [
+ "debug", "release", "staging"]
+
+ for (String variantName : variantNames) {
+ findNamedItem(variants, variantName, "variantData")
+ }
+
+ BaseVariantData testVariant = findNamedItem(variants, "stagingTest", "variantData")
+ assertEquals("staging", testVariant.variantConfiguration.buildType.name)
+ }
+
+ public void testFlavors() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "basic")).build()
+
+ project.apply plugin: 'android'
+
+ project.android {
+ compileSdkVersion 15
+
+ productFlavors {
+ flavor1 {
+
+ }
+ flavor2 {
+
+ }
+ }
+ }
+
+ AppPlugin plugin = AppPlugin.pluginHolder.plugin
+ plugin.createAndroidTasks(true /*force*/)
+
+ assertEquals(2, plugin.productFlavors.size())
+
+ List<BaseVariantData> variants = plugin.variantDataList
+ assertEquals(6, variants.size()) // includes the test variant(s)
+
+ String[] variantNames = [
+ "flavor1Debug", "flavor1Release", "flavor1DebugTest",
+ "flavor2Debug", "flavor2Release", "flavor2DebugTest"]
+
+ for (String variantName : variantNames) {
+ findNamedItem(variants, variantName, "variantData")
+ }
+ }
+
+ public void testMultiFlavors() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "basic")).build()
+
+ project.apply plugin: 'android'
+
+ project.android {
+ compileSdkVersion 15
+
+ flavorGroups "group1", "group2"
+
+ productFlavors {
+ f1 {
+ flavorGroup "group1"
+ }
+ f2 {
+ flavorGroup "group1"
+ }
+
+ fa {
+ flavorGroup "group2"
+ }
+ fb {
+ flavorGroup "group2"
+ }
+ fc {
+ flavorGroup "group2"
+ }
+ }
+ }
+
+ AppPlugin plugin = AppPlugin.pluginHolder.plugin
+ plugin.createAndroidTasks(true /*force*/)
+
+ assertEquals(5, plugin.productFlavors.size())
+
+ List<BaseVariantData> variants = plugin.variantDataList
+ assertEquals(18, variants.size()) // includes the test variant(s)
+
+ String[] variantNames = [
+ "f1FaDebug",
+ "f1FbDebug",
+ "f1FcDebug",
+ "f2FaDebug",
+ "f2FbDebug",
+ "f2FcDebug",
+ "f1FaRelease",
+ "f1FbRelease",
+ "f1FcRelease",
+ "f2FaRelease",
+ "f2FbRelease",
+ "f2FcRelease",
+ "f1FaDebugTest",
+ "f1FbDebugTest",
+ "f1FcDebugTest",
+ "f2FaDebugTest",
+ "f2FbDebugTest",
+ "f2FcDebugTest"];
+
+ for (String variantName : variantNames) {
+ findNamedItem(variants, variantName, "variantData");
+ }
+ }
+
+ public void testSigningConfigs() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "basic")).build()
+
+ project.apply plugin: 'android'
+
+ project.android {
+ compileSdkVersion 15
+
+ signingConfigs {
+ one {
+ storeFile project.file("a1")
+ storePassword "b1"
+ keyAlias "c1"
+ keyPassword "d1"
+ }
+ two {
+ storeFile project.file("a2")
+ storePassword "b2"
+ keyAlias "c2"
+ keyPassword "d2"
+ }
+ three {
+ storeFile project.file("a3")
+ storePassword "b3"
+ keyAlias "c3"
+ keyPassword "d3"
+ }
+ }
+
+ defaultConfig {
+ versionCode 1
+ versionName "2.0"
+ minSdkVersion 2
+ targetSdkVersion 3
+ }
+
+ buildTypes {
+ debug {
+ }
+ staging {
+ }
+ release {
+ signingConfig owner.signingConfigs.three
+ }
+ }
+
+ productFlavors {
+ flavor1 {
+ }
+ flavor2 {
+ signingConfig owner.signingConfigs.one
+ }
+ }
+
+ }
+
+ AppPlugin plugin = AppPlugin.pluginHolder.plugin
+ plugin.createAndroidTasks(true /*force*/)
+
+ List<BaseVariantData> variants = plugin.variantDataList
+ assertEquals(8, variants.size()) // includes the test variant(s)
+
+ BaseVariantData variant
+ SigningConfig signingConfig
+
+ variant = findNamedItem(variants, "flavor1Debug", "variantData")
+ signingConfig = variant.variantConfiguration.signingConfig
+ assertNotNull(signingConfig)
+ assertEquals(KeystoreHelper.defaultDebugKeystoreLocation(), signingConfig.storeFile?.absolutePath)
+
+ variant = findNamedItem(variants, "flavor1Staging", "variantData")
+ signingConfig = variant.variantConfiguration.signingConfig
+ assertNull(signingConfig)
+
+ variant = findNamedItem(variants, "flavor1Release", "variantData")
+ signingConfig = variant.variantConfiguration.signingConfig
+ assertNotNull(signingConfig)
+ assertEquals(new File(project.projectDir, "a3"), signingConfig.storeFile)
+
+ variant = findNamedItem(variants, "flavor2Debug", "variantData")
+ signingConfig = variant.variantConfiguration.signingConfig
+ assertNotNull(signingConfig)
+ assertEquals(KeystoreHelper.defaultDebugKeystoreLocation(), signingConfig.storeFile?.absolutePath)
+
+ variant = findNamedItem(variants, "flavor2Staging", "variantData")
+ signingConfig = variant.variantConfiguration.signingConfig
+ assertNotNull(signingConfig)
+ assertEquals(new File(project.projectDir, "a1"), signingConfig.storeFile)
+
+ variant = findNamedItem(variants, "flavor2Release", "variantData")
+ signingConfig = variant.variantConfiguration.signingConfig
+ assertNotNull(signingConfig)
+ assertEquals(new File(project.projectDir, "a3"), signingConfig.storeFile)
+ }
+
+ /**
+ * test that debug build type maps to the SigningConfig object as the signingConfig container
+ * @throws Exception
+ */
+ public void testDebugSigningConfig() throws Exception {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "basic")).build()
+
+ project.apply plugin: 'android'
+
+ project.android {
+ compileSdkVersion 15
+
+ signingConfigs {
+ debug {
+ storePassword = "foo"
+ }
+ }
+ }
+
+ AppPlugin plugin = AppPlugin.pluginHolder.plugin
+
+ // check that the debug buildType has the updated debug signing config.
+ DefaultBuildType buildType = plugin.buildTypes.get(BuilderConstants.DEBUG).buildType
+ SigningConfig signingConfig = buildType.signingConfig
+ assertEquals(plugin.signingConfigs.get(BuilderConstants.DEBUG), signingConfig)
+ assertEquals("foo", signingConfig.storePassword)
+ }
+
+ public void testSigningConfigInitWith() throws Exception {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "basic")).build()
+
+ project.apply plugin: 'android'
+
+ project.android {
+ compileSdkVersion 15
+
+ signingConfigs {
+ foo.initWith(owner.signingConfigs.debug)
+ }
+ }
+
+ AppPlugin plugin = AppPlugin.pluginHolder.plugin
+
+ SigningConfig debugSC = plugin.signingConfigs.get(BuilderConstants.DEBUG)
+ SigningConfig fooSC = plugin.signingConfigs.get("foo")
+
+ assertNotNull(fooSC);
+
+ assertEquals(debugSC.getStoreFile(), fooSC.getStoreFile());
+ assertEquals(debugSC.getStorePassword(), fooSC.getStorePassword());
+ assertEquals(debugSC.getKeyAlias(), fooSC.getKeyAlias());
+ assertEquals(debugSC.getKeyPassword(), fooSC.getKeyPassword());
+ }
+
+ public void testPluginDetection() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "basic")).build()
+
+ project.apply plugin: 'android'
+ project.apply plugin: 'java'
+
+ project.android {
+ compileSdkVersion 15
+ }
+
+ AppPlugin plugin = AppPlugin.pluginHolder.plugin
+ Exception recordedException = null;
+ try {
+ plugin.createAndroidTasks(true /*force*/)
+ } catch (Exception e) {
+ recordedException = e;
+ }
+
+ assertNotNull(recordedException)
+ assertEquals(BadPluginException.class, recordedException.getClass())
+ }
+}
\ No newline at end of file
diff --git a/build-system/gradle/src/test/groovy/com/android/build/gradle/LibraryPluginDslTest.groovy b/build-system/gradle/src/test/groovy/com/android/build/gradle/LibraryPluginDslTest.groovy
new file mode 100644
index 0000000..f76b7a97
--- /dev/null
+++ b/build-system/gradle/src/test/groovy/com/android/build/gradle/LibraryPluginDslTest.groovy
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.build.gradle
+
+import com.android.annotations.NonNull
+import com.android.build.gradle.api.LibraryVariant
+import com.android.build.gradle.api.TestVariant
+import com.android.build.gradle.internal.test.BaseTest
+import com.android.builder.model.SigningConfig
+import org.gradle.api.Project
+import org.gradle.testfixtures.ProjectBuilder
+
+/**
+ * Tests for the public DSL of the App plugin ("android-library")
+ */
+public class LibraryPluginDslTest extends BaseTest {
+
+ @Override
+ protected void setUp() throws Exception {
+ BasePlugin.TEST_SDK_DIR = new File("foo")
+ }
+
+ public void testBasic() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "basic")).build()
+
+ project.apply plugin: 'android-library'
+
+ project.android {
+ compileSdkVersion 15
+ }
+
+ project.afterEvaluate {
+ Set<LibraryVariant> variants = project.android.libraryVariants
+ assertEquals(2, variants.size())
+
+ Set<TestVariant> testVariants = project.android.testVariants
+ assertEquals(1, testVariants.size())
+
+ checkTestedVariant("Debug", "Test", variants, testVariants)
+ checkNonTestedVariant("Release", variants)
+ }
+ }
+
+ /**
+ * test that debug build type maps to the SigningConfig object as the signingConfig container
+ * @throws Exception
+ */
+ public void testDebugSigningConfig() throws Exception {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "basic")).build()
+
+ project.apply plugin: 'android-library'
+
+ project.android {
+ compileSdkVersion 15
+
+ debugSigningConfig {
+ storePassword = "foo"
+ }
+ }
+
+ SigningConfig signingConfig = project.android.debug.signingConfig
+
+ assertEquals(project.android.debugSigningConfig, signingConfig)
+ assertEquals("foo", signingConfig.storePassword)
+ }
+
+ private static void checkTestedVariant(@NonNull String variantName,
+ @NonNull String testedVariantName,
+ @NonNull Set<LibraryVariant> variants,
+ @NonNull Set<TestVariant> testVariants) {
+ LibraryVariant variant = findNamedItem(variants, variantName)
+ assertNotNull(variant)
+ assertNotNull(variant.testVariant)
+ assertEquals(testedVariantName, variant.testVariant.name)
+ assertEquals(variant.testVariant, findNamedItem(testVariants, testedVariantName))
+ checkLibraryTasks(variant)
+ checkTestTasks(variant.testVariant)
+ }
+
+ private static void checkNonTestedVariant(@NonNull String variantName,
+ @NonNull Set<LibraryVariant> variants) {
+ LibraryVariant variant = findNamedItem(variants, variantName)
+ assertNotNull(variant)
+ assertNull(variant.testVariant)
+ checkLibraryTasks(variant)
+ }
+
+ private static void checkTestTasks(@NonNull TestVariant variant) {
+ assertNotNull(variant.processManifest)
+ assertNotNull(variant.aidlCompile)
+ assertNotNull(variant.mergeResources)
+ assertNotNull(variant.mergeAssets)
+ assertNotNull(variant.processResources)
+ assertNotNull(variant.generateBuildConfig)
+ assertNotNull(variant.javaCompile)
+ assertNotNull(variant.processJavaResources)
+ assertNotNull(variant.dex)
+ assertNotNull(variant.packageApplication)
+
+ assertNotNull(variant.assemble)
+ assertNotNull(variant.uninstall)
+
+ assertNull(variant.zipAlign)
+
+ if (variant.isSigningReady()) {
+ assertNotNull(variant.install)
+ } else {
+ assertNull(variant.install)
+ }
+
+ assertNotNull(variant.connectedInstrumentTest)
+ }
+
+ private static void checkLibraryTasks(@NonNull LibraryVariant variant) {
+ assertNotNull(variant.processManifest)
+ assertNotNull(variant.aidlCompile)
+ assertNotNull(variant.processResources)
+ assertNotNull(variant.generateBuildConfig)
+ assertNotNull(variant.javaCompile)
+ assertNotNull(variant.processJavaResources)
+
+ assertNotNull(variant.assemble)
+ }
+}
\ No newline at end of file
diff --git a/build-system/gradle/src/test/groovy/com/android/build/gradle/internal/dsl/BuildTypeDslTest.groovy b/build-system/gradle/src/test/groovy/com/android/build/gradle/internal/dsl/BuildTypeDslTest.groovy
new file mode 100644
index 0000000..3744b79
--- /dev/null
+++ b/build-system/gradle/src/test/groovy/com/android/build/gradle/internal/dsl/BuildTypeDslTest.groovy
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License")
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dsl
+
+import com.android.build.gradle.AppPlugin
+import com.android.build.gradle.internal.test.BaseTest
+import com.android.builder.DefaultBuildType
+import com.android.builder.BuilderConstants
+import org.gradle.api.Project
+import org.gradle.testfixtures.ProjectBuilder
+
+/**
+ * test that the build type are properly initialized
+ */
+public class BuildTypeDslTest extends BaseTest {
+
+ public void testDebug() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "basic")).build()
+
+ project.apply plugin: 'android'
+
+ project.android {
+ compileSdkVersion 15
+ }
+
+ AppPlugin plugin = AppPlugin.pluginHolder.plugin
+
+ DefaultBuildType type = plugin.buildTypes.get(BuilderConstants.DEBUG).buildType
+
+ assertTrue(type.isDebuggable())
+ assertFalse(type.isJniDebugBuild())
+ assertFalse(type.isRenderscriptDebugBuild())
+ assertNotNull(type.getSigningConfig())
+ assertTrue(type.getSigningConfig().isSigningReady())
+ assertFalse(type.isZipAlign())
+ }
+
+ public void testRelease() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "basic")).build()
+
+ project.apply plugin: 'android'
+
+ project.android {
+ compileSdkVersion 15
+ }
+
+ AppPlugin plugin = AppPlugin.pluginHolder.plugin
+
+ DefaultBuildType type = plugin.buildTypes.get(BuilderConstants.RELEASE).buildType
+
+ assertFalse(type.isDebuggable())
+ assertFalse(type.isJniDebugBuild())
+ assertFalse(type.isRenderscriptDebugBuild())
+ assertTrue(type.isZipAlign())
+ }
+
+ public void testInitWith() {
+ Project project = ProjectBuilder.builder().withProjectDir(
+ new File(testDir, "basic")).build()
+
+ BuildTypeDsl object1 = new BuildTypeDsl("foo", project.fileResolver)
+
+ // change every value from their default.
+ object1.setDebuggable(true)
+ object1.setJniDebugBuild(true)
+ object1.setRenderscriptDebugBuild(true)
+ object1.setRenderscriptOptimLevel(0)
+ object1.setPackageNameSuffix("foo")
+ object1.setVersionNameSuffix("foo")
+ object1.setRunProguard(true)
+ object1.setSigningConfig(new SigningConfigDsl("blah"))
+ object1.setZipAlign(false)
+
+ BuildTypeDsl object2 = new BuildTypeDsl(object1.name, project.fileResolver)
+ object2.initWith(object1)
+
+ assertEquals(object1, object2)
+ }
+}
diff --git a/build-system/gradle/src/test/groovy/com/android/build/gradle/internal/dsl/SigningConfigDslTest.java b/build-system/gradle/src/test/groovy/com/android/build/gradle/internal/dsl/SigningConfigDslTest.java
new file mode 100644
index 0000000..5a3bc66
--- /dev/null
+++ b/build-system/gradle/src/test/groovy/com/android/build/gradle/internal/dsl/SigningConfigDslTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.dsl;
+
+import com.android.builder.BuilderConstants;
+import junit.framework.TestCase;
+
+public class SigningConfigDslTest extends TestCase {
+
+ public void testInitWith() throws Exception {
+ SigningConfigDsl debug = new SigningConfigDsl(BuilderConstants.DEBUG);
+ SigningConfigDsl foo = new SigningConfigDsl("foo").initWith(debug);
+
+ assertEquals(debug.getStoreFile(), foo.getStoreFile());
+ assertEquals(debug.getStorePassword(), foo.getStorePassword());
+ assertEquals(debug.getKeyAlias(), foo.getKeyAlias());
+ assertEquals(debug.getKeyPassword(), foo.getKeyPassword());
+ }
+}
diff --git a/build-system/gradle/src/test/groovy/com/android/build/gradle/internal/test/BaseTest.groovy b/build-system/gradle/src/test/groovy/com/android/build/gradle/internal/test/BaseTest.groovy
new file mode 100755
index 0000000..fc35099
--- /dev/null
+++ b/build-system/gradle/src/test/groovy/com/android/build/gradle/internal/test/BaseTest.groovy
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.build.gradle.internal.test
+import com.android.annotations.NonNull
+import com.android.annotations.Nullable
+import com.android.sdklib.internal.project.ProjectProperties
+import com.android.sdklib.internal.project.ProjectPropertiesWorkingCopy
+import junit.framework.TestCase
+import org.gradle.tooling.GradleConnector
+import org.gradle.tooling.ProjectConnection
+
+import java.security.CodeSource
+/**
+ * Base class for tests.
+ */
+public abstract class BaseTest extends TestCase {
+
+ /**
+ * Returns the root dir for the gradle plugin project
+ */
+ protected File getRootDir() {
+ CodeSource source = getClass().getProtectionDomain().getCodeSource()
+ if (source != null) {
+ URL location = source.getLocation();
+ try {
+ File dir = new File(location.toURI())
+ assertTrue(dir.getPath(), dir.exists())
+
+ File f= dir.getParentFile().getParentFile().getParentFile().getParentFile().getParentFile().getParentFile().getParentFile().getParentFile();
+ return new File(f, "tools" + File.separator + "base" + File.separator + "build-system")
+ } catch (URISyntaxException e) {
+ fail(e.getLocalizedMessage())
+ }
+ }
+
+ fail("Fail to get the tools/build folder")
+ }
+
+ /**
+ * Returns the root folder for the tests projects.
+ */
+ protected File getTestDir() {
+ File rootDir = getRootDir()
+ return new File(rootDir, "tests")
+ }
+
+ /**
+ * Returns the SDK folder as built from the Android source tree.
+ * @return
+ */
+ protected File getSdkDir() {
+ String androidHome = System.getenv("ANDROID_HOME");
+ if (androidHome != null) {
+ File f = new File(androidHome);
+ if (f.isDirectory()) {
+ return f;
+ } else {
+ System.out.println("Failed to find SDK in ANDROID_HOME=" + androidHome)
+ }
+ }
+
+ // get the gradle project root dir.
+ File rootDir = getRootDir()
+
+ // go up twice and get the root Android dir.
+ File androidRootDir = rootDir.getParentFile().getParentFile()
+
+ // get the sdk folder
+ String outFolder = "out" + File.separatorChar + "host" + File.separatorChar + "darwin-x86" + File.separatorChar + "sdk";
+ File sdk = new File(androidRootDir, outFolder)
+
+ File[] files = sdk.listFiles(new FilenameFilter() {
+
+ @Override
+ boolean accept(File file, String s) {
+ return s.startsWith("android-sdk_") && new File(file,s ).isDirectory()
+ }
+ })
+
+ if (files != null && files.length == 1) {
+ return files[0]
+ }
+
+ fail(String.format(
+ "Failed to find a valid SDK. Make sure %s is present at the root of the Android tree, or that ANDROID_HOME is defined.",
+ outFolder))
+ return null
+ }
+
+ /**
+ * Returns the SDK folder as built from the Android source tree.
+ * @return
+ */
+ protected File getNdkDir() {
+ String androidHome = System.getenv("ANDROID_NDK_HOME");
+ if (androidHome != null) {
+ File f = new File(androidHome);
+ if (f.isDirectory()) {
+ return f;
+ } else {
+ System.out.println("Failed to find NDK in ANDROID_NDK_HOME=" + androidHome)
+ }
+ }
+ }
+
+ protected static File createLocalProp(@NonNull File project,
+ @NonNull File sdkDir,
+ @Nullable File ndkDir) {
+ ProjectPropertiesWorkingCopy localProp = ProjectProperties.create(
+ project.absolutePath, ProjectProperties.PropertyType.LOCAL)
+ localProp.setProperty(ProjectProperties.PROPERTY_SDK, sdkDir.absolutePath)
+ if (ndkDir != null) {
+ localProp.setProperty(ProjectProperties.PROPERTY_NDK, ndkDir.absolutePath)
+ }
+ localProp.save()
+
+ return (File) localProp.file
+ }
+
+ protected File runTasksOn(String name, String gradleVersion, String... tasks) {
+ File project = new File(testDir, name)
+
+ runGradleTasks(sdkDir, ndkDir, gradleVersion, project, tasks)
+
+ return project;
+ }
+
+ protected static void runGradleTasks(File sdkDir, File ndkDir,
+ String gradleVersion,
+ File project, String... tasks) {
+ File localProp = createLocalProp(project, sdkDir, ndkDir)
+
+ try {
+
+ GradleConnector connector = GradleConnector.newConnector()
+
+ ProjectConnection connection = connector
+ .useGradleVersion(gradleVersion)
+ .forProjectDirectory(project)
+ .connect()
+ try {
+ connection.newBuild().forTasks(tasks).withArguments("-i").run()
+ } finally {
+ connection.close()
+ }
+ } finally {
+ localProp.delete()
+ }
+ }
+
+ protected static void deleteFolder(File folder) {
+ File[] files = folder.listFiles()
+ if (files != null && files.length > 0) {
+ for (File file : files) {
+ if (file.isDirectory()) {
+ deleteFolder(file)
+ } else {
+ file.delete()
+ }
+ }
+ }
+
+ folder.delete()
+ }
+
+ /**
+ * Returns the name item from the collection of items. The items *must* have a "name" property.
+ * @param items the item collection to search for a match
+ * @param name the name of the item to return
+ * @return the found item or null
+ */
+ protected static <T> T findNamedItemMaybe(@NonNull Collection<T> items,
+ @NonNull String name) {
+ for (T item : items) {
+ if (name.equals(item.name)) {
+ return item
+ }
+ }
+
+ return null
+ }
+
+ /**
+ * Returns the name item from the collection of items. The items *must* have a "name" property.
+ * @param items the item collection to search for a match
+ * @param name the name of the item to return
+ * @return the found item or null
+ */
+ protected static <T> T findNamedItem(@NonNull Collection<T> items,
+ @NonNull String name,
+ @NonNull String typeName) {
+ T foundItem = findNamedItemMaybe(items, name);
+ assertNotNull("$name $typeName null-check", foundItem)
+ return foundItem
+ }
+}
diff --git a/build-system/gradle/wrapper/gradle-wrapper.jar b/build-system/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..b6b646b
--- /dev/null
+++ b/build-system/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/build-system/gradle/wrapper/gradle-wrapper.properties b/build-system/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..b5d9920
--- /dev/null
+++ b/build-system/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Dec 10 15:17:09 PST 2012
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=../../../external/gradle/gradle-1.9-bin.zip
diff --git a/manifest-merger/.classpath b/build-system/manifest-merger/.classpath
similarity index 100%
rename from manifest-merger/.classpath
rename to build-system/manifest-merger/.classpath
diff --git a/manifest-merger/.gitignore b/build-system/manifest-merger/.gitignore
similarity index 100%
rename from manifest-merger/.gitignore
rename to build-system/manifest-merger/.gitignore
diff --git a/manifest-merger/.project b/build-system/manifest-merger/.project
similarity index 100%
rename from manifest-merger/.project
rename to build-system/manifest-merger/.project
diff --git a/manifest-merger/.settings/org.eclipse.jdt.core.prefs b/build-system/manifest-merger/.settings/org.eclipse.jdt.core.prefs
similarity index 100%
rename from manifest-merger/.settings/org.eclipse.jdt.core.prefs
rename to build-system/manifest-merger/.settings/org.eclipse.jdt.core.prefs
diff --git a/manifest-merger/NOTICE b/build-system/manifest-merger/NOTICE
similarity index 100%
rename from manifest-merger/NOTICE
rename to build-system/manifest-merger/NOTICE
diff --git a/build-system/manifest-merger/build.gradle b/build-system/manifest-merger/build.gradle
new file mode 100644
index 0000000..ffc78f3
--- /dev/null
+++ b/build-system/manifest-merger/build.gradle
@@ -0,0 +1,33 @@
+apply plugin: 'java'
+apply plugin: 'distrib'
+
+evaluationDependsOn(':sdklib')
+
+group = 'com.android.tools.build'
+archivesBaseName = 'manifest-merger'
+
+dependencies {
+ compile project(':common')
+ compile project(':sdklib')
+ compile 'kxml2:kxml2:2.3.0'
+
+ testCompile project(':sdklib').sourceSets.test.output
+ testCompile 'junit:junit:3.8.1'
+}
+
+sourceSets {
+ main.resources.srcDir 'src/main/java'
+ test.resources.srcDir 'src/test/java'
+}
+
+jar {
+ from 'NOTICE'
+}
+
+project.ext.pomName = 'Android Tools Manifest Merger library'
+project.ext.pomDesc = 'A Library to merge Android manifests.'
+
+apply from: '../../baseVersion.gradle'
+apply from: '../../publish.gradle'
+apply from: '../../javadoc.gradle'
+
diff --git a/manifest-merger/etc/manifmerger b/build-system/manifest-merger/etc/manifmerger
similarity index 100%
rename from manifest-merger/etc/manifmerger
rename to build-system/manifest-merger/etc/manifmerger
diff --git a/manifest-merger/manifest-merger.iml b/build-system/manifest-merger/manifest-merger.iml
similarity index 100%
rename from manifest-merger/manifest-merger.iml
rename to build-system/manifest-merger/manifest-merger.iml
diff --git a/manifest-merger/src/main/java/com/android/manifmerger/ArgvParser.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/ArgvParser.java
similarity index 100%
rename from manifest-merger/src/main/java/com/android/manifmerger/ArgvParser.java
rename to build-system/manifest-merger/src/main/java/com/android/manifmerger/ArgvParser.java
diff --git a/manifest-merger/src/main/java/com/android/manifmerger/ICallback.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/ICallback.java
similarity index 100%
rename from manifest-merger/src/main/java/com/android/manifmerger/ICallback.java
rename to build-system/manifest-merger/src/main/java/com/android/manifmerger/ICallback.java
diff --git a/manifest-merger/src/main/java/com/android/manifmerger/IMergerLog.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/IMergerLog.java
similarity index 100%
rename from manifest-merger/src/main/java/com/android/manifmerger/IMergerLog.java
rename to build-system/manifest-merger/src/main/java/com/android/manifmerger/IMergerLog.java
diff --git a/manifest-merger/src/main/java/com/android/manifmerger/Main.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/Main.java
similarity index 100%
rename from manifest-merger/src/main/java/com/android/manifmerger/Main.java
rename to build-system/manifest-merger/src/main/java/com/android/manifmerger/Main.java
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/ManifestMerger.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/ManifestMerger.java
new file mode 100755
index 0000000..7a01d48
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/ManifestMerger.java
@@ -0,0 +1,1728 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.manifmerger;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.manifmerger.IMergerLog.FileAndLine;
+import com.android.manifmerger.IMergerLog.Severity;
+import com.android.utils.SdkUtils;
+import com.android.utils.XmlUtils;
+import com.android.xml.AndroidXPathFactory;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+
+/**
+ * Merges a library manifest into a main application manifest.
+ * <p/>
+ * To use, create with {@link ManifestMerger#ManifestMerger(IMergerLog, ICallback)} then
+ * call {@link ManifestMerger#process(File, File, File[], Map, String)}.
+ * <p/>
+ * <pre> Merge operations:
+ * - root manifest: attributes ignored, warn if defined.
+ * - application:
+ * G- {@code @attributes}: most attributes are ignored in libs
+ * except: application:name if defined, it must match.
+ * except: application:agentBackup if defined, it must match.
+ * (these represent class names and we don't want a lib to assume their app or backup
+ * classes are being used when that will never be the case.)
+ * C- activity / activity-alias / service / receiver / provider
+ * => Merge as-is. Error if exists in the destination (same {@code @name})
+ * unless the definitions are exactly the same.
+ * New elements are always merged at the end of the application element.
+ * => Indicate if there's a dup.
+ * D- uses-library
+ * => Merge. OK if already exists same {@code @name}.
+ * => Merge {@code @required}: true>false.
+ * C- meta-data
+ * => Merge as-is. Error if exists in the destination (same {@code @name})
+ * unless the definitions are exactly the same.
+ * New elements are always merged at the end of the application element.
+ * => Indicate if there's a dup.
+ * A- instrumentation:
+ * => Do not merge. ignore the ones from libs.
+ * C- permission / permission-group / permission-tree:
+ * => Merge as-is. Error if exists in the destination (same {@code @name})
+ * unless the definitions are exactly the same.
+ * C- uses-permission:
+ * => Add. OK if already defined.
+ * E- uses-sdk:
+ * {@code @minSdkVersion}: error if dest<lib. Never automatically change dest minsdk.
+ * Codenames are accepted if we can resolve their API level.
+ * {@code @targetSdkVersion}: warning if dest<lib.
+ * Never automatically change dest targetsdk.
+ * {@code @maxSdkVersion}: obsolete, ignored. Not used in comparisons and not merged.
+ * D- uses-feature with {@code @name}:
+ * => Merge with same {@code @name}
+ * => Merge {@code @required}: true>false.
+ * - Do not merge any {@code @glEsVersion} attribute at this point.
+ * F- uses-feature with {@code @glEsVersion}:
+ * => Error if defined in lib+dest with dest<lib. Never automatically change dest.
+ * B- uses-configuration:
+ * => There can be many. Error if source defines one that is not an exact match in dest.
+ * (e.g. right now app must manually define something that matches exactly each lib)
+ * B- supports-screens / compatible-screens:
+ * => Do not merge.
+ * => Error (warn?) if defined in lib and not strictly the same as in dest.
+ * B- supports-gl-texture:
+ * => Do not merge. Can have more than one.
+ * => Error (warn?) if defined in lib and not present as-is in dest.
+ *
+ * Strategies:
+ * A = Ignore, do not merge (no-op).
+ * B = Do not merge but if defined in both must match equally.
+ * C = Must not exist in dest or be exactly the same (key is the {@code @name} attribute).
+ * D = Add new or merge with same key {@code @name}, adjust {@code @required} true>false.
+ * E, F, G = Custom strategies; see above.
+ *
+ * What happens when merging libraries with conflicting information?
+ * Say for example a main manifest has a minSdkVersion of 3, whereas libraries have
+ * a minSdkVersion of 4 and 11. We could have 2 point of views:
+ * - Play it safe: If we have a library with a minSdkVersion of 11, it means this
+ * library code knows it can't work reliably on a lower API level. So the safest end
+ * result would be a merged manifest with the highest minSdkVersion of all libraries.
+ * - Trust the main manifest: When an app declares a given minSdkVersion, it also expects
+ * to run a given range of devices. If we change the final minSdkVersion, the app won't
+ * be available on as many devices as the developer might expect. And as a counterpoint
+ * to issue 1, the app may be careful and not call the library without checking the
+ * necessary features or APIs are available before hand.
+ * Both points of views are conflicting. The solution taken here is to be conservative
+ * and generate an error rather than merge and change a value that might be surprising.
+ * On the other hand this can be problematic and force a developer to keep the main
+ * manifest in sync with the libraries ones, in essence reducing the usefulness of the
+ * automated merge to pure trivial cases. The idea is to just start this way and enhance
+ * or revisit the mechanism later.
+ * </pre>
+ */
+public class ManifestMerger {
+
+ /** Logger object. Never null. */
+ private final IMergerLog mLog;
+ /** An optional callback that the merger can use to query the calling SDK. */
+ private final ICallback mCallback;
+ private XPath mXPath;
+ private Document mMainDoc;
+ /** Option to extract the package prefixes from the merged manifest. */
+ private boolean mExtractPackagePrefix;
+ /** Whether the merger should insert comments pointing to the merge source files */
+ private boolean mInsertSourceMarkers;
+
+ /** Namespace for Android attributes in an AndroidManifest.xml */
+ private static final String NS_URI = SdkConstants.NS_RESOURCES;
+ /** Prefix for the Android namespace to use in XPath expressions. */
+ private static final String NS_PREFIX = AndroidXPathFactory.DEFAULT_NS_PREFIX;
+ /** Namespace used in XML files for Android Tooling attributes */
+ private static final String TOOLS_URI = SdkConstants.TOOLS_URI;
+ /** The name of the tool:merge attribute, to either override or ignore merges. */
+ private static final String MERGE_ATTR = "merge"; //$NON-NLS-1$
+ /** tool:merge="override" means to ignore what comes from libraries and only keep the
+ * version from the main manifest. No conflict can be generated. */
+ private static final String MERGE_OVERRIDE = "override"; //$NON-NLS-1$
+ /** tool:merge="remove" means to remove a node and prevent merging -- not only is the
+ * node from the libraries not merged, but the element is removed from the main manifest. */
+ private static final String MERGE_REMOVE = "remove"; //$NON-NLS-1$
+
+ /**
+ * Sets of element/attribute that need to be treated as class names.
+ * The attribute name must be the local name for the Android namespace.
+ * For example "application/name" maps to <application android:name=...>.
+ */
+ private static final String[] sClassAttributes = {
+ "application/name",
+ "application/backupAgent",
+ "activity/name",
+ "activity/parentActivityName",
+ "activity-alias/name",
+ "receiver/name",
+ "service/name",
+ "provider/name",
+ "instrumentation/name"
+ };
+
+ /**
+ * Creates a new {@link ManifestMerger}.
+ *
+ * @param log A non-null merger log to capture all warnings, errors and their location.
+ * @param callback An optional callback that the merger can use to query the calling SDK.
+ */
+ public ManifestMerger(@NonNull IMergerLog log, @Nullable ICallback callback) {
+ mLog = log;
+ mCallback = callback;
+ }
+
+ /**
+ * Sets whether the manifest merger should extract package prefixes.
+ * <p/>
+ * When true, the merged document is revisited and class names attributes
+ * are shortened when possible, e.g. the package prefix is removed from the
+ * class name if it matches.
+ *
+ * @param extract If true, extract package prefixes.
+ * @return this, for constructor chaining
+ */
+ public ManifestMerger setExtractPackagePrefix(boolean extract) {
+ mExtractPackagePrefix = extract;
+ return this;
+ }
+
+ /**
+ * Performs the merge operation.
+ * <p/>
+ * This does NOT stop on errors, in an attempt to accumulate as much
+ * info as possible to return to the user.
+ * Unless it failed to read the main manifest, a result file will be
+ * created. However if process() returns false, the file should not
+ * be used except for debugging purposes.
+ *
+ * @param outputFile The output path to generate. Can be the same as the main path.
+ * @param mainFile The main manifest paths to read. What we merge into.
+ * @param libraryFiles The library manifest paths to read. Must not be null.
+ * @param injectAttributes A map of attributes to inject in the form [pseudo-xpath] => value.
+ * The key is "/manifest/elements...|attribute-ns-uri attribute-local-name",
+ * for example "/manifest/uses-sdk|http://schemas.android.com/apk/res/android minSdkVersion".
+ * (note the space separator between the attribute URI and its local name.)
+ * The elements will be created if they don't exists. Existing attributes will be modified.
+ * The replacement is done on the main document <em>before</em> merging.
+ * @param packageOverride an optional package override. This only affects the package attribute,
+ * all components (activities, receivers, etc...) are not affected by this.
+ * @return True if the merge was completed, false otherwise.
+ */
+ public boolean process(
+ File outputFile,
+ File mainFile,
+ File[] libraryFiles,
+ Map<String, String> injectAttributes,
+ String packageOverride) {
+ Document mainDoc = MergerXmlUtils.parseDocument(mainFile, mLog, this);
+ if (mainDoc == null) {
+ mLog.error(Severity.ERROR, new FileAndLine(mainFile.getAbsolutePath(), 0),
+ "Failed to read manifest file.");
+ return false;
+ }
+
+ boolean success = process(mainDoc, libraryFiles, injectAttributes, packageOverride);
+
+ if (!MergerXmlUtils.printXmlFile(mainDoc, outputFile, mLog)) {
+ mLog.error(Severity.ERROR, new FileAndLine(outputFile.getAbsolutePath(), 0),
+ "Failed to write manifest file.");
+ success = false;
+ }
+
+ return success;
+ }
+
+ /**
+ * Performs the merge operation in-place in the given DOM.
+ * <p/>
+ * This does NOT stop on errors, in an attempt to accumulate as much
+ * info as possible to return to the user.
+ * <p/>
+ * The method might modify the input XML document in-place for its own processing.
+ *
+ * @param mainDoc The document to merge into. Will be modified in-place.
+ * @param libraryFiles The library manifest paths to read. Must not be null.
+ * These will be modified in-place.
+ * @param injectAttributes A map of attributes to inject in the form [pseudo-xpath] => value.
+ * The key is "/manifest/elements...|attribute-ns-uri attribute-local-name",
+ * for example "/manifest/uses-sdk|http://schemas.android.com/apk/res/android minSdkVersion".
+ * (note the space separator between the attribute URI and its local name.)
+ * The elements will be created if they don't exists. Existing attributes will be modified.
+ * The replacement is done on the main document <em>before</em> merging.
+ * @param packageOverride an optional package override. This only affects the package attribute,
+ * all components (activities, receivers, etc...) are not affected by this.
+ * @return True on success, false if any error occurred (printed to the {@link IMergerLog}).
+ */
+ public boolean process(
+ Document mainDoc,
+ File[] libraryFiles,
+ Map<String, String> injectAttributes,
+ String packageOverride) {
+
+ boolean success = true;
+ mMainDoc = mainDoc;
+ MergerXmlUtils.decorateDocument(mainDoc, IMergerLog.MAIN_MANIFEST);
+ MergerXmlUtils.injectAttributes(mainDoc, injectAttributes, mLog);
+
+ String prefix = XmlUtils.lookupNamespacePrefix(mainDoc, SdkConstants.NS_RESOURCES);
+ mXPath = AndroidXPathFactory.newXPath(prefix);
+
+ expandFqcns(mainDoc);
+ for (File libFile : libraryFiles) {
+ Document libDoc = MergerXmlUtils.parseDocument(libFile, mLog, this);
+ if (libDoc == null || !mergeLibDoc(cleanupToolsAttributes(libDoc))) {
+ success = false;
+ }
+ }
+
+ if (packageOverride != null) {
+ MergerXmlUtils.injectAttributes(mainDoc,
+ Collections.singletonMap("/manifest| package", packageOverride),
+ mLog);
+ }
+
+ cleanupToolsAttributes(mainDoc);
+
+ if (mExtractPackagePrefix) {
+ extractFqcns(mainDoc);
+ }
+
+ if (mInsertSourceMarkers) {
+ insertSourceMarkers(mainDoc);
+ }
+
+ mXPath = null;
+ mMainDoc = null;
+ return success;
+ }
+
+ /**
+ * Performs the merge operation in-place in the given DOM.
+ * <p/>
+ * This does NOT stop on errors, in an attempt to accumulate as much
+ * info as possible to return to the user.
+ * <p/>
+ * The method might modify the input XML documents in-place for its own processing.
+ *
+ * @param mainDoc The document to merge into. Will be modified in-place.
+ * @param libraryDocs The library manifest documents to merge in. Must not be null.
+ * These will be modified in-place.
+ * @return True on success, false if any error occurred (printed to the {@link IMergerLog}).
+ */
+ public boolean process(@NonNull Document mainDoc, @NonNull Document... libraryDocs) {
+
+ boolean success = true;
+ mMainDoc = mainDoc;
+ MergerXmlUtils.decorateDocument(mainDoc, IMergerLog.MAIN_MANIFEST);
+
+ String prefix = XmlUtils.lookupNamespacePrefix(mainDoc, SdkConstants.NS_RESOURCES);
+ mXPath = AndroidXPathFactory.newXPath(prefix);
+
+ expandFqcns(mainDoc);
+ for (Document libDoc : libraryDocs) {
+ MergerXmlUtils.decorateDocument(libDoc, IMergerLog.LIBRARY);
+ if (!mergeLibDoc(cleanupToolsAttributes(libDoc))) {
+ success = false;
+ }
+ }
+
+ cleanupToolsAttributes(mainDoc);
+
+ if (mInsertSourceMarkers) {
+ insertSourceMarkers(mainDoc);
+ }
+
+ mXPath = null;
+ mMainDoc = null;
+ return success;
+ }
+
+ // --------
+
+ /**
+ * Merges the given library manifest into the destination manifest.
+ * See {@link ManifestMerger} for merge details.
+ *
+ * @param libDoc The library document to merge from. Must not be null.
+ * @return True on success, false if any error occurred (printed to the {@link IMergerLog}).
+ */
+ private boolean mergeLibDoc(Document libDoc) {
+
+ boolean err = false;
+
+ expandFqcns(libDoc);
+
+ // Strategy G (check <application> is compatible)
+ err |= !checkApplication(libDoc);
+
+ // Strategy B
+ err |= !doNotMergeCheckEqual("/manifest/uses-configuration", libDoc); //$NON-NLS-1$
+ err |= !doNotMergeCheckEqual("/manifest/supports-screens", libDoc); //$NON-NLS-1$
+ err |= !doNotMergeCheckEqual("/manifest/compatible-screens", libDoc); //$NON-NLS-1$
+ err |= !doNotMergeCheckEqual("/manifest/supports-gl-texture", libDoc); //$NON-NLS-1$
+
+ boolean skipApplication = hasOverrideOrRemoveTag(
+ findFirstElement(mMainDoc, "/manifest/application")); //$NON-NLS-1$
+
+ // Strategy C
+ if (!skipApplication) {
+ err |= !mergeNewOrEqual(
+ "/manifest/application/activity", //$NON-NLS-1$
+ "name", //$NON-NLS-1$
+ libDoc,
+ true);
+ err |= !mergeNewOrEqual(
+ "/manifest/application/activity-alias", //$NON-NLS-1$
+ "name", //$NON-NLS-1$
+ libDoc,
+ true);
+ err |= !mergeNewOrEqual(
+ "/manifest/application/service", //$NON-NLS-1$
+ "name", //$NON-NLS-1$
+ libDoc,
+ true);
+ err |= !mergeNewOrEqual(
+ "/manifest/application/receiver", //$NON-NLS-1$
+ "name", //$NON-NLS-1$
+ libDoc,
+ true);
+ err |= !mergeNewOrEqual(
+ "/manifest/application/provider", //$NON-NLS-1$
+ "name", //$NON-NLS-1$
+ libDoc,
+ true);
+ }
+ err |= !mergeNewOrEqual(
+ "/manifest/permission", //$NON-NLS-1$
+ "name", //$NON-NLS-1$
+ libDoc,
+ false);
+ err |= !mergeNewOrEqual(
+ "/manifest/permission-group", //$NON-NLS-1$
+ "name", //$NON-NLS-1$
+ libDoc,
+ false);
+ err |= !mergeNewOrEqual(
+ "/manifest/permission-tree", //$NON-NLS-1$
+ "name", //$NON-NLS-1$
+ libDoc,
+ false);
+ err |= !mergeNewOrEqual(
+ "/manifest/uses-permission", //$NON-NLS-1$
+ "name", //$NON-NLS-1$
+ libDoc,
+ false);
+
+ // Strategy D
+ if (!skipApplication) {
+ err |= !mergeAdjustRequired(
+ "/manifest/application/uses-library", //$NON-NLS-1$
+ "name", //$NON-NLS-1$
+ "required", //$NON-NLS-1$
+ libDoc,
+ null /*alternateKeyAttr*/);
+ err |= !mergeNewOrEqual(
+ "/manifest/application/meta-data", //$NON-NLS-1$
+ "name", //$NON-NLS-1$
+ libDoc,
+ true);
+ }
+ err |= !mergeAdjustRequired(
+ "/manifest/uses-feature", //$NON-NLS-1$
+ "name", //$NON-NLS-1$
+ "required", //$NON-NLS-1$
+ libDoc,
+ "glEsVersion" /*alternateKeyAttr*/);
+
+ // Strategy E
+ err |= !checkSdkVersion(libDoc);
+
+ // Strategy F
+ err |= !checkGlEsVersion(libDoc);
+
+ return !err;
+ }
+
+ /**
+ * Expand all possible class names attributes in the given document.
+ * <p/>
+ * Some manifest attributes represent class names. These can be specified as fully
+ * qualified class names or use a short notation consisting of just the terminal
+ * class simple name or a dot followed by a partial class name. Unfortunately this
+ * makes textual comparison of the attributes impossible. To simplify this, we can
+ * modify the document to fully expand all these class names. The list of elements
+ * and attributes to process is listed by {@link #sClassAttributes} and the expansion
+ * simply consists of appending the manifest' package if defined.
+ *
+ * @param doc The document in which to expand potential FQCNs.
+ */
+ private void expandFqcns(Document doc) {
+ // Find the package attribute of the manifest.
+ String pkg = null;
+ Element manifest = findFirstElement(doc, "/manifest");
+ if (manifest != null) {
+ pkg = manifest.getAttribute("package");
+ }
+
+ if (pkg == null || pkg.length() == 0) {
+ // We can't adjust FQCNs if we don't know the root package name.
+ // It's not a proper manifest if this is missing anyway.
+ assert manifest != null;
+ mLog.error(Severity.WARNING,
+ xmlFileAndLine(manifest),
+ "Missing 'package' attribute in manifest.");
+ return;
+ }
+
+ for (String elementAttr : sClassAttributes) {
+ String[] names = elementAttr.split("/");
+ if (names.length != 2) {
+ continue;
+ }
+ String elemName = names[0];
+ String attrName = names[1];
+ NodeList elements = doc.getElementsByTagName(elemName);
+ for (int i = 0; i < elements.getLength(); i++) {
+ Node elem = elements.item(i);
+ if (elem instanceof Element) {
+ Attr attr = ((Element) elem).getAttributeNodeNS(NS_URI, attrName);
+ if (attr != null) {
+ String value = attr.getNodeValue();
+
+ // We know it's a shortened FQCN if it starts with a dot
+ // or does not contain any dot.
+ if (value != null && value.length() > 0 &&
+ (value.indexOf('.') == -1 || value.charAt(0) == '.')) {
+ if (value.charAt(0) == '.') {
+ value = pkg + value;
+ } else {
+ value = pkg + '.' + value;
+ }
+ attr.setNodeValue(value);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Extracts the fully qualified class names from the manifest and uses the
+ * prefix notation relative to the manifest package. This basically reverses
+ * the effects of {@link #expandFqcns(Document)}, though of course it may
+ * also remove prefixes which were inlined in the original documents.
+ *
+ * @param doc the document in which to extract the FQCNs.
+ */
+ private void extractFqcns(Document doc) {
+ // Find the package attribute of the manifest.
+ String pkg = null;
+ Element manifest = findFirstElement(doc, "/manifest");
+ if (manifest != null) {
+ pkg = manifest.getAttribute("package");
+ }
+
+ if (pkg == null || pkg.length() == 0) {
+ return;
+ }
+
+ int pkgLength = pkg.length();
+ for (String elementAttr : sClassAttributes) {
+ String[] names = elementAttr.split("/");
+ if (names.length != 2) {
+ continue;
+ }
+ String elemName = names[0];
+ String attrName = names[1];
+ NodeList elements = doc.getElementsByTagName(elemName);
+ for (int i = 0; i < elements.getLength(); i++) {
+ Node elem = elements.item(i);
+ if (elem instanceof Element) {
+ Attr attr = ((Element) elem).getAttributeNodeNS(NS_URI, attrName);
+ if (attr != null) {
+ String value = attr.getNodeValue();
+
+ // We know it's a shortened FQCN if it starts with a dot
+ // or does not contain any dot.
+ if (value != null && value.length() > pkgLength &&
+ value.startsWith(pkg) && value.charAt(pkgLength) == '.') {
+ value = value.substring(pkgLength);
+ attr.setNodeValue(value);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks (but does not merge) the application attributes using the following rules:
+ * <pre>
+ * - {@code @name}: Ignore if empty. Warning if its expanded FQCN doesn't match the main doc.
+ * - {@code @backupAgent}: Ignore if empty. Warning if its expanded FQCN doesn't match main doc.
+ * - All other attributes are ignored.
+ * </pre>
+ * The name and backupAgent represent classes and the merger will warn since if a lib has
+ * these defined they will never be used anyway.
+ * @param libDoc The library document to merge from. Must not be null.
+ * @return True on success, false if any error occurred (printed to the {@link IMergerLog}).
+ */
+ private boolean checkApplication(Document libDoc) {
+
+ Element mainApp = findFirstElement(mMainDoc, "/manifest/application"); //$NON-NLS-1$
+ Element libApp = findFirstElement(libDoc, "/manifest/application"); //$NON-NLS-1$
+
+ // A manifest does not necessarily define an application.
+ // If the lib has none, there's nothing to check for.
+ if (libApp == null) {
+ return true;
+ }
+ if (hasOverrideOrRemoveTag(mainApp)) {
+ // Don't check the <application> element since it is tagged with override or remove.
+ return true;
+ }
+
+ for (String attrName : new String[] { "name", "backupAgent" }) {
+ String libValue = getAttributeValue(libApp, attrName);
+ if (libValue == null || libValue.length() == 0) {
+ // Nothing to do if the attribute is not defined in the lib.
+ continue;
+ }
+ // The main doc does not have to have an application node.
+ String mainValue = mainApp == null ? "" : getAttributeValue(mainApp, attrName);
+ if (!libValue.equals(mainValue)) {
+ assert mainApp != null;
+ mLog.conflict(Severity.WARNING,
+ xmlFileAndLine(mainApp),
+ xmlFileAndLine(libApp),
+ mainApp == null ?
+ "Library has <application android:%1$s='%3$s'> but main manifest has no application element." :
+ "Main manifest has <application android:%1$s='%2$s'> but library uses %1$s='%3$s'.",
+ attrName,
+ mainValue,
+ libValue);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Do not merge anything. Instead it checks that the requested elements from the
+ * given library are all present and equal in the destination and prints a warning
+ * if it's not the case.
+ * <p/>
+ * For example if a library supports a given screen configuration, print a
+ * warning if the main manifest doesn't indicate the app supports the same configuration.
+ * We should not merge it since we don't want to silently give the impression an app
+ * supports a configuration just because it uses a library which does.
+ * On the other hand we don't want to silently ignore this fact.
+ * <p/>
+ * TODO there should be a way to silence this warning.
+ * The current behavior is certainly arbitrary and needs to be tweaked somehow.
+ *
+ * @param path The XPath of the elements to merge from the library. Must not be null.
+ * @param libDoc The library document to merge from. Must not be null.
+ * @return True on success, false if any error occurred (printed to the {@link IMergerLog}).
+ */
+ private boolean doNotMergeCheckEqual(String path, Document libDoc) {
+
+ for (Element src : findElements(libDoc, path)) {
+
+ boolean found = false;
+
+ for (Element dest : findElements(mMainDoc, path)) {
+ if (hasOverrideOrRemoveTag(dest)) {
+ continue;
+ }
+ if (compareElements(dest, src, false, null /*diff*/, null /*keyAttr*/)) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ mLog.conflict(Severity.WARNING,
+ xmlFileAndLine(mMainDoc),
+ xmlFileAndLine(src),
+ "%1$s defined in library, missing from main manifest:\n%2$s",
+ path,
+ MergerXmlUtils.dump(src, false /*nextSiblings*/));
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Merges the requested elements from the library in the main document.
+ * The key attribute name is used to identify the same elements.
+ * Merged elements must either not exist in the destination or be identical.
+ * <p/>
+ * When merging, append to the end of the application element.
+ * Also merges any preceding whitespace and up to one comment just prior to the merged element.
+ *
+ * @param path The XPath of the elements to merge from the library. Must not be null.
+ * @param keyAttr The Android-namespace attribute used as key to identify similar elements.
+ * E.g. "name" for "android:name"
+ * @param libDoc The library document to merge from. Must not be null.
+ * @param warnDups When true, will print a warning when a library definition is already
+ * present in the destination and is equal.
+ * @return True on success, false if any error occurred (printed to the {@link IMergerLog}).
+ */
+ private boolean mergeNewOrEqual(
+ String path,
+ String keyAttr,
+ Document libDoc,
+ boolean warnDups) {
+
+ // The parent of XPath /p1/p2/p3 is /p1/p2. To find it, delete the last "/segment"
+ int pos = path.lastIndexOf('/');
+ assert pos > 1;
+ String parentPath = path.substring(0, pos);
+ Element parent = findFirstElement(mMainDoc, parentPath);
+ assert parent != null;
+ if (parent == null) {
+ mLog.error(Severity.ERROR,
+ xmlFileAndLine(mMainDoc),
+ "Could not find element %1$s.",
+ parentPath);
+ return false;
+ }
+
+ boolean success = true;
+
+ nextSource: for (Element src : findElements(libDoc, path)) {
+ String name = getAttributeValue(src, keyAttr);
+ if (name.length() == 0) {
+ mLog.error(Severity.ERROR,
+ xmlFileAndLine(src),
+ "Undefined '%1$s' attribute in %2$s.",
+ keyAttr, path);
+ success = false;
+ continue;
+ }
+
+ // Look for the same item in the destination
+ List<Element> dests = findElements(mMainDoc, path, keyAttr, name);
+ if (dests.size() > 1) {
+ // This should not be happening. We'll just use the first one found in this case.
+ mLog.error(Severity.WARNING,
+ xmlFileAndLine(dests.get(0)),
+ "Manifest has more than one %1$s[@%2$s=%3$s] element.",
+ path, keyAttr, name);
+ }
+ boolean doMerge = true;
+ for (Element dest : dests) {
+ // Don't try to merge this element since it has tools:merge=override|remove.
+ if (hasOverrideOrRemoveTag(dest)) {
+ doMerge = false;
+ continue;
+ }
+ // If there's already a similar node in the destination, check it's identical.
+ StringBuilder diff = new StringBuilder();
+ if (compareElements(dest, src, false, diff, keyAttr)) {
+ // Same element. Skip.
+ if (warnDups) {
+ mLog.conflict(Severity.INFO,
+ xmlFileAndLine(dest),
+ xmlFileAndLine(src),
+ "Skipping identical %1$s[@%2$s=%3$s] element.",
+ path, keyAttr, name);
+ }
+ continue nextSource;
+ } else {
+ // Print the diff we got from the comparison.
+ mLog.conflict(Severity.ERROR,
+ xmlFileAndLine(dest),
+ xmlFileAndLine(src),
+ "Trying to merge incompatible %1$s[@%2$s=%3$s] element:\n%4$s",
+ path, keyAttr, name, diff.toString());
+ success = false;
+ continue nextSource;
+ }
+ }
+
+ if (doMerge) {
+ // Ready to merge element src. Select which previous siblings to merge.
+ Node start = selectPreviousSiblings(src);
+
+ insertAtEndOf(parent, start, src);
+ }
+ }
+
+ return success;
+ }
+
+ /**
+ * Returns the value of the given "android:attribute" in the given element.
+ *
+ * @param element The non-null element where to extract the attribute.
+ * @param attrName The local name of the attribute.
+ * It must use the {@link #NS_URI} but no prefix should be specified here.
+ * @return The value of the attribute or a non-null empty string if not found.
+ */
+ private String getAttributeValue(Element element, String attrName) {
+ Attr attr = element.getAttributeNodeNS(NS_URI, attrName);
+ String value = attr == null ? "" : attr.getNodeValue(); //$NON-NLS-1$
+ return value;
+ }
+
+ /**
+ * Merge elements as identified by their key name attribute.
+ * The element must have an option boolean "required" attribute which can be either "true" or
+ * "false". Default is true if the attribute is missing. When merging, a "false" is superseded
+ * by a "true" (explicit or implicit).
+ * <p/>
+ * When merging, this does NOT merge any other attributes than {@code keyAttr} and
+ * {@code requiredAttr}.
+ *
+ * @param path The XPath of the elements to merge from the library. Must not be null.
+ * @param keyAttr The Android-namespace attribute used as key to identify similar elements.
+ * E.g. "name" for "android:name"
+ * @param requiredAttr The name of the Android-namespace boolean attribute that must be merged.
+ * Typically should be "required".
+ * @param libDoc The library document to merge from. Must not be null.
+ * @param alternateKeyAttr When non-null, this is an alternate valid key attribute. If the
+ * default key attribute is missing, we won't output a warning if the alternate one is
+ * present.
+ * @return True on success, false if any error occurred (printed to the {@link IMergerLog}).
+ */
+ private boolean mergeAdjustRequired(
+ String path,
+ String keyAttr,
+ String requiredAttr,
+ Document libDoc,
+ @Nullable String alternateKeyAttr) {
+
+ // The parent of XPath /p1/p2/p3 is /p1/p2. To find it, delete the last "/segment"
+ int pos = path.lastIndexOf('/');
+ assert pos > 1;
+ String parentPath = path.substring(0, pos);
+ Element parent = findFirstElement(mMainDoc, parentPath);
+ assert parent != null;
+ if (parent == null) {
+ mLog.error(Severity.ERROR,
+ xmlFileAndLine(mMainDoc),
+ "Could not find element %1$s.",
+ parentPath);
+ return false;
+ }
+
+ boolean success = true;
+
+ for (Element src : findElements(libDoc, path)) {
+ Attr attr = src.getAttributeNodeNS(NS_URI, keyAttr);
+ String name = attr == null ? "" : attr.getNodeValue().trim(); //$NON-NLS-1$
+ if (name.length() == 0) {
+ if (alternateKeyAttr != null) {
+ attr = src.getAttributeNodeNS(NS_URI, alternateKeyAttr);
+ String s = attr == null ? "" : attr.getNodeValue().trim(); //$NON-NLS-1$
+ if (s.length() != 0) {
+ // This element lacks the keyAttr but has the alternateKeyAttr. Skip it.
+ continue;
+ }
+ }
+
+ mLog.error(Severity.ERROR,
+ xmlFileAndLine(src),
+ "Undefined '%1$s' attribute in %2$s.",
+ keyAttr, path);
+ success = false;
+ continue;
+ }
+
+ // Look for the same item in the destination
+ List<Element> dests = findElements(mMainDoc, path, keyAttr, name);
+ if (dests.size() > 1) {
+ // This should not be happening. We'll just use the first one found in this case.
+ mLog.error(Severity.WARNING,
+ xmlFileAndLine(dests.get(0)),
+ "Manifest has more than one %1$s[@%2$s=%3$s] element.",
+ path, keyAttr, name);
+ }
+ if (dests.size() > 0) {
+
+ attr = src.getAttributeNodeNS(NS_URI, requiredAttr);
+ String value = attr == null ? "true" : attr.getNodeValue(); //$NON-NLS-1$
+ if (value == null || !(value.equals("true") || value.equals("false"))) {
+ mLog.error(Severity.WARNING,
+ xmlFileAndLine(src),
+ "Invalid attribute '%1$s' in %2$s[@%3$s=%4$s] element:\nExpected 'true' or 'false' but found '%5$s'.",
+ requiredAttr, path, keyAttr, name, value);
+ continue;
+ }
+ boolean boolE = Boolean.parseBoolean(value);
+
+ for (Element dest : dests) {
+ // Don't try to merge this element since it has tools:merge=override|remove.
+ if (hasOverrideOrRemoveTag(dest)) {
+ continue;
+ }
+
+ // Compare the required attributes.
+ attr = dest.getAttributeNodeNS(NS_URI, requiredAttr);
+ value = attr == null ? "true" : attr.getNodeValue(); //$NON-NLS-1$
+ if (value == null || !(value.equals("true") || value.equals("false"))) {
+ mLog.error(Severity.WARNING,
+ xmlFileAndLine(dest),
+ "Invalid attribute '%1$s' in %2$s[@%3$s=%4$s] element:\nExpected 'true' or 'false' but found '%5$s'.",
+ requiredAttr, path, keyAttr, name, value);
+ continue;
+ }
+ boolean boolD = Boolean.parseBoolean(value);
+
+ if (!boolD && boolE) {
+ // Required attributes differ: destination is false and source was true
+ // so we need to change the destination to true.
+
+ // If attribute was already in the destination, change it in place
+ if (attr != null) {
+ attr.setNodeValue("true"); //$NON-NLS-1$
+ } else {
+ // Otherwise, do nothing. The destination doesn't have the
+ // required=true attribute, and true is the default value.
+ // Consequently not setting is the right thing to do.
+
+ // -- code snippet for reference --
+ // If we wanted to create a new attribute, we'd use the code
+ // below. There's a simpler call to d.setAttributeNS(ns, name, value)
+ // but experience shows that it would create a new prefix out of the
+ // blue instead of looking it up.
+ //
+ // Attr a=d.getOwnerDocument().createAttributeNS(NS_URI, requiredAttr);
+ // String prefix = d.lookupPrefix(NS_URI);
+ // if (prefix != null) {
+ // a.setPrefix(prefix);
+ // }
+ // a.setValue("true"); //$NON-NLS-1$
+ // d.setAttributeNodeNS(attr);
+ }
+ }
+ }
+ } else {
+ // Destination doesn't exist. We simply merge the source element.
+ // Select which previous siblings to merge.
+ Node start = selectPreviousSiblings(src);
+
+ Node node = insertAtEndOf(parent, start, src);
+
+ NamedNodeMap attrs = node.getAttributes();
+ if (attrs != null) {
+ for (int i = 0; i < attrs.getLength(); i++) {
+ Node a = attrs.item(i);
+ if (a.getNodeType() == Node.ATTRIBUTE_NODE) {
+ boolean keep = NS_URI.equals(a.getNamespaceURI());
+ if (keep) {
+ name = a.getLocalName();
+ keep = keyAttr.equals(name) || requiredAttr.equals(name);
+ }
+ if (!keep) {
+ attrs.removeNamedItemNS(NS_URI, name);
+ // Restart the loop from index 0 since there's no
+ // guarantee on the order of the nodes in the "map".
+ // This makes it O(n+2n) at most, where n is [2..3] in
+ // a typical case.
+ i = -1;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return success;
+ }
+
+
+
+ /**
+ * Checks (but does not merge) uses-feature glEsVersion attribute using the following rules:
+ * <pre>
+ * - Error if defined in lib+dest with dest<lib.
+ * - Never automatically change dest.
+ * - Default implied value is 1.0 (0x00010000).
+ * </pre>
+ *
+ * @param libDoc The library document to merge from. Must not be null.
+ * @return True on success, false if any error occurred (printed to the {@link IMergerLog}).
+ */
+ private boolean checkGlEsVersion(Document libDoc) {
+
+ String parentPath = "/manifest"; //$NON-NLS-1$
+ Element parent = findFirstElement(mMainDoc, parentPath);
+ assert parent != null;
+ if (parent == null) {
+ mLog.error(Severity.ERROR,
+ xmlFileAndLine(mMainDoc),
+ "Could not find element %1$s.",
+ parentPath);
+ return false;
+ }
+
+ // Find the max glEsVersion on the destination side
+ String path = "/manifest/uses-feature"; //$NON-NLS-1$
+ String keyAttr = "glEsVersion"; //$NON-NLS-1$
+ long destGlEsVersion = 0x00010000L; // default minimum is 1.0
+ Element destNode = null;
+ boolean result = true;
+ for (Element dest : findElements(mMainDoc, path)) {
+ Attr attr = dest.getAttributeNodeNS(NS_URI, keyAttr);
+ String value = attr == null ? "" : attr.getNodeValue().trim(); //$NON-NLS-1$
+ if (value.length() != 0) {
+ try {
+ // Note that the value can be an hex number such as 0x00020001 so we
+ // need Integer.decode instead of Integer.parseInt.
+ // Note: Integer.decode cannot handle "ffffffff", see JDK issue 6624867
+ // so we just treat the version as a long and test like this, ignoring
+ // the fact that a value of 0xFFFF/.0xFFFF is probably invalid anyway
+ // in the context of glEsVersion.
+ long version = Long.decode(value);
+ if (version >= destGlEsVersion) {
+ destGlEsVersion = version;
+ destNode = dest;
+ } else if (version < 0x00010000) {
+ mLog.error(Severity.WARNING,
+ xmlFileAndLine(dest),
+ "Ignoring <uses-feature android:glEsVersion='%1$s'> because it's smaller than 1.0.",
+ value);
+ }
+ } catch (NumberFormatException e) {
+ // Note: NumberFormatException.toString() has no interesting information
+ // so we don't output it.
+ mLog.error(Severity.ERROR,
+ xmlFileAndLine(dest),
+ "Failed to parse <uses-feature android:glEsVersion='%1$s'>: must be an integer in the form 0x00020001.",
+ value);
+ result = false;
+ }
+ }
+ }
+
+ // If we found at least one valid with no error, use that, otherwise bail out.
+ if (!result && destNode == null) {
+ return false;
+ }
+
+ // Now find the max glEsVersion on the source side.
+
+ long srcGlEsVersion = 0x00010000L; // default minimum is 1.0
+ Element srcNode = null;
+ result = true;
+ for (Element src : findElements(libDoc, path)) {
+ Attr attr = src.getAttributeNodeNS(NS_URI, keyAttr);
+ String value = attr == null ? "" : attr.getNodeValue().trim(); //$NON-NLS-1$
+ if (value.length() != 0) {
+ try {
+ // See comment on Long.decode above.
+ long version = Long.decode(value);
+ if (version >= srcGlEsVersion) {
+ srcGlEsVersion = version;
+ srcNode = src;
+ } else if (version < 0x00010000) {
+ mLog.error(Severity.WARNING,
+ xmlFileAndLine(src),
+ "Ignoring <uses-feature android:glEsVersion='%1$s'> because it's smaller than 1.0.",
+ value);
+ }
+ } catch (NumberFormatException e) {
+ // Note: NumberFormatException.toString() has no interesting information
+ // so we don't output it.
+ mLog.error(Severity.ERROR,
+ xmlFileAndLine(src),
+ "Failed to parse <uses-feature android:glEsVersion='%1$s'>: must be an integer in the form 0x00020001.",
+ value);
+ result = false;
+ }
+ }
+ }
+
+ if (srcNode != null && destGlEsVersion < srcGlEsVersion) {
+ mLog.conflict(Severity.WARNING,
+ xmlFileAndLine(destNode == null ? mMainDoc : destNode),
+ xmlFileAndLine(srcNode),
+ "Main manifest has <uses-feature android:glEsVersion='0x%1$08x'> but library uses glEsVersion='0x%2$08x'%3$s",
+ destGlEsVersion,
+ srcGlEsVersion,
+ destNode != null ? "" : //$NON-NLS-1$
+ "\nNote: main manifest lacks a <uses-feature android:glEsVersion> declaration, and thus defaults to glEsVersion=0x00010000."
+ );
+ result = false;
+ }
+
+ return result;
+ }
+
+ /**
+ * Checks (but does not merge) uses-sdk attributes using the following rules:
+ * <pre>
+ * - {@code @minSdkVersion}: error if dest<lib. Never automatically change dest minsdk.
+ * - {@code @targetSdkVersion}: warning if dest<lib. Never automatically change destination.
+ * - {@code @maxSdkVersion}: obsolete, ignored. Not used in comparisons and not merged.
+ * - The API level can be a codename if we have a callback that can convert it to an integer.
+ * </pre>
+ * @param libDoc The library document to merge from. Must not be null.
+ * @return True on success, false if any error occurred (printed to the {@link IMergerLog}).
+ */
+ private boolean checkSdkVersion(Document libDoc) {
+
+ boolean result = true;
+
+ Element destUsesSdk = findFirstElement(mMainDoc, "/manifest/uses-sdk"); //$NON-NLS-1$
+
+ if (hasOverrideOrRemoveTag(destUsesSdk)) {
+ // Don't try to check this element since it has tools:merge=override|remove.
+ return true;
+ }
+
+ Element srcUsesSdk = findFirstElement(libDoc, "/manifest/uses-sdk"); //$NON-NLS-1$
+
+ AtomicInteger destValue = new AtomicInteger(1);
+ AtomicInteger srcValue = new AtomicInteger(1);
+ AtomicBoolean destImplied = new AtomicBoolean(true);
+ AtomicBoolean srcImplied = new AtomicBoolean(true);
+
+ // Check minSdkVersion
+ int destMinSdk = 1;
+ result = extractSdkVersionAttribute(
+ libDoc,
+ destUsesSdk, srcUsesSdk,
+ "min", //$NON-NLS-1$
+ destValue, srcValue,
+ destImplied, srcImplied);
+
+ if (result) {
+ // Make it an error for an application to use a library with a greater
+ // minSdkVersion. This means the library code may crash unexpectedly.
+ // TODO it would be nice to be able to work around this in case the
+ // user think s/he knows what s/he's doing.
+ // We could define a simple XML comment flag: <!-- @NoMinSdkVersionMergeError -->
+
+ destMinSdk = destValue.get();
+
+ if (destMinSdk < srcValue.get()) {
+ mLog.conflict(Severity.ERROR,
+ xmlFileAndLine(destUsesSdk == null ? mMainDoc : destUsesSdk),
+ xmlFileAndLine(srcUsesSdk == null ? libDoc : srcUsesSdk),
+ "Main manifest has <uses-sdk android:minSdkVersion='%1$d'> but library uses minSdkVersion='%2$d'%3$s",
+ destMinSdk,
+ srcValue.get(),
+ !destImplied.get() ? "" : //$NON-NLS-1$
+ "\nNote: main manifest lacks a <uses-sdk android:minSdkVersion> declaration, which defaults to value 1."
+ );
+ result = false;
+ }
+ }
+
+ // Check targetSdkVersion.
+
+ // Note that destValue/srcValue purposely defaults to whatever minSdkVersion was last read
+ // since that's their definition when missing.
+ destImplied.set(true);
+ srcImplied.set(true);
+
+ boolean result2 = extractSdkVersionAttribute(
+ libDoc,
+ destUsesSdk, srcUsesSdk,
+ "target", //$NON-NLS-1$
+ destValue, srcValue,
+ destImplied, srcImplied);
+
+ result &= result2;
+ if (result2) {
+ // Make it a warning for an application to use a library with a greater
+ // targetSdkVersion.
+
+ int destTargetSdk = destImplied.get() ? destMinSdk : destValue.get();
+
+ if (destTargetSdk < srcValue.get()) {
+ mLog.conflict(Severity.WARNING,
+ xmlFileAndLine(destUsesSdk == null ? mMainDoc : destUsesSdk),
+ xmlFileAndLine(srcUsesSdk == null ? libDoc : srcUsesSdk),
+ "Main manifest has <uses-sdk android:targetSdkVersion='%1$d'> but library uses targetSdkVersion='%2$d'%3$s",
+ destTargetSdk,
+ srcValue.get(),
+ !destImplied.get() ? "" : //$NON-NLS-1$
+ "\nNote: main manifest lacks a <uses-sdk android:targetSdkVersion> declaration, which defaults to value minSdkVersion or 1."
+ );
+ result = false;
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Implementation detail for {@link #checkSdkVersion(Document)}.
+ * Note that the various atomic out-variables must be preset to their default before
+ * the call.
+ * <p/>
+ * destValue/srcValue will be filled with the integer value of the field, if present
+ * and a correct number, in which case destImplied/destImplied are also set to true.
+ * Otherwise the values and the implied variables are left untouched.
+ */
+ private boolean extractSdkVersionAttribute(
+ Document libDoc,
+ Element destUsesSdk,
+ Element srcUsesSdk,
+ String attr,
+ AtomicInteger destValue,
+ AtomicInteger srcValue,
+ AtomicBoolean destImplied,
+ AtomicBoolean srcImplied) {
+ String s = destUsesSdk == null ? "" //$NON-NLS-1$
+ : destUsesSdk.getAttributeNS(NS_URI, attr + "SdkVersion"); //$NON-NLS-1$
+
+ boolean result = true;
+ assert s != null;
+ s = s.trim();
+ try {
+ if (s.length() > 0) {
+ destValue.set(Integer.parseInt(s));
+ destImplied.set(false);
+ }
+ } catch (NumberFormatException e) {
+ boolean error = true;
+ if (mCallback != null) {
+ // Versions can contain codenames such as "JellyBean".
+ // We'll accept it only if have a callback that can give us the API level for it.
+ int apiLevel = mCallback.queryCodenameApiLevel(s);
+ if (apiLevel > ICallback.UNKNOWN_CODENAME) {
+ destValue.set(apiLevel);
+ destImplied.set(false);
+ error = false;
+ }
+ }
+ if (error) {
+ // Note: NumberFormatException.toString() has no interesting information
+ // so we don't output it.
+ mLog.error(Severity.ERROR,
+ xmlFileAndLine(destUsesSdk == null ? mMainDoc : destUsesSdk),
+ "Failed to parse <uses-sdk %1$sSdkVersion='%2$s'>: must be an integer number or codename.",
+ attr,
+ s);
+ result = false;
+ }
+ }
+
+ s = srcUsesSdk == null ? "" //$NON-NLS-1$
+ : srcUsesSdk.getAttributeNS(NS_URI, attr + "SdkVersion"); //$NON-NLS-1$
+ assert s != null;
+ s = s.trim();
+ try {
+ if (s.length() > 0) {
+ srcValue.set(Integer.parseInt(s));
+ srcImplied.set(false);
+ }
+ } catch (NumberFormatException e) {
+ boolean error = true;
+ if (mCallback != null) {
+ // Versions can contain codenames such as "JellyBean".
+ // We'll accept it only if have a callback that can give us the API level for it.
+ int apiLevel = mCallback.queryCodenameApiLevel(s);
+ if (apiLevel > ICallback.UNKNOWN_CODENAME) {
+ srcValue.set(apiLevel);
+ srcImplied.set(false);
+ error = false;
+ }
+ }
+ if (error) {
+ mLog.error(Severity.ERROR,
+ xmlFileAndLine(srcUsesSdk == null ? libDoc : srcUsesSdk),
+ "Failed to parse <uses-sdk %1$sSdkVersion='%2$s'>: must be an integer number or codename.",
+ attr,
+ s);
+ result = false;
+ }
+ }
+
+ return result;
+ }
+
+
+ // -----
+
+
+ /**
+ * Given an element E, select which previous siblings we want to merge.
+ * We want to include any whitespace up to the closing of the previous element.
+ * We also want to include up preceding comment nodes and their preceding whitespace.
+ * <p/>
+ * This may returns either {@code end} or a previous sibling. Never returns null.
+ */
+ @NonNull
+ private Node selectPreviousSiblings(Node end) {
+
+ Node start = end;
+ Node prev = start.getPreviousSibling();
+ while (prev != null) {
+ short t = prev.getNodeType();
+ if (t == Node.TEXT_NODE) {
+ String text = prev.getNodeValue();
+ if (text == null || text.trim().length() != 0) {
+ // Not whitespace, we don't want it.
+ break;
+ }
+ } else if (t == Node.COMMENT_NODE) {
+ // It's a comment. We'll take it.
+ } else {
+ // Not a comment node nor a whitespace text. We don't want it.
+ break;
+ }
+ start = prev;
+ prev = start.getPreviousSibling();
+ }
+
+ return start;
+ }
+
+ /**
+ * Inserts all siblings from {@code start} to {@code end} at the end
+ * of the given destination element.
+ * <p/>
+ * Implementation detail: this clones the source nodes into the destination.
+ *
+ * @param dest The destination at the end of which to insert. Cannot be null.
+ * @param start The first element to insert. Must not be null.
+ * @param end The last element to insert (included). Must not be null.
+ * Must be a direct "next sibling" of the start node.
+ * Can be equal to the start node to insert just that one node.
+ * @return The copy of the {@code end} node in the destination document or null
+ * if no such copy was created and added to the destination.
+ */
+ private Node insertAtEndOf(Element dest, Node start, Node end) {
+ // Check whether we'll need to adjust URI prefixes
+ String destPrefix = XmlUtils.lookupNamespacePrefix(mMainDoc, NS_URI);
+ String srcPrefix = XmlUtils.lookupNamespacePrefix(start.getOwnerDocument(), NS_URI);
+ boolean needPrefixChange = destPrefix != null && !destPrefix.equals(srcPrefix);
+
+ // First let's figure out the insertion point.
+ // We want the end of the last 'content' element of the
+ // destination element and basically we want to insert right
+ // before the last whitespace of the destination element.
+ Node target = dest.getLastChild();
+ while (target != null) {
+ if (target.getNodeType() == Node.TEXT_NODE) {
+ String text = target.getNodeValue();
+ if (text == null || text.trim().length() != 0) {
+ // Not whitespace, insert after.
+ break;
+ }
+ } else {
+ // Not text. Insert after
+ break;
+ }
+ target = target.getPreviousSibling();
+ }
+ if (target != null) {
+ target = target.getNextSibling();
+ }
+
+ // Destination and start..end must not be part of the same document
+ // because we try to import below. If they were, it would mess the
+ // structure.
+ assert dest.getOwnerDocument() == mMainDoc;
+ assert dest.getOwnerDocument() != start.getOwnerDocument();
+ assert start.getOwnerDocument() == end.getOwnerDocument();
+
+ while (start != null) {
+ Node node = mMainDoc.importNode(start, true /*deep*/);
+ if (needPrefixChange) {
+ changePrefix(node, srcPrefix, destPrefix);
+ }
+
+ if (mInsertSourceMarkers) {
+ // Duplicate source node attribute
+ File file = MergerXmlUtils.getFileFor(start);
+ if (file != null) {
+ MergerXmlUtils.setFileFor(node, file);
+ }
+ }
+
+ dest.insertBefore(node, target);
+
+ if (start == end) {
+ return node;
+ }
+ start = start.getNextSibling();
+ }
+ return null;
+ }
+
+ /**
+ * Changes the namespace prefix of all nodes, recursively.
+ *
+ * @param node The node to process, as well as all it's descendants. Can be null.
+ * @param srcPrefix The prefix to match.
+ * @param destPrefix The new prefix to replace with.
+ */
+ private void changePrefix(Node node, String srcPrefix, String destPrefix) {
+ for (; node != null; node = node.getNextSibling()) {
+ if (srcPrefix.equals(node.getPrefix())) {
+ node.setPrefix(destPrefix);
+ }
+ Node child = node.getFirstChild();
+ if (child != null) {
+ changePrefix(child, srcPrefix, destPrefix);
+ }
+ }
+ }
+
+ /**
+ * Compares two {@link Element}s recursively.
+ * They must be identical with the same structure.
+ * Order should not matter.
+ * Whitespace and comments are ignored.
+ *
+ * @param expected The first element to compare.
+ * @param actual The second element to compare with.
+ * @param nextSiblings If true, will also compare the following siblings.
+ * If false, it will just compare the given node.
+ * @param diff An optional {@link StringBuilder} where to accumulate a diff output.
+ * @param keyAttr An optional key attribute to always add to elements when dumping a diff.
+ * @return True if {@code e1} and {@code e2} are equal.
+ */
+ private boolean compareElements(
+ @NonNull Node expected,
+ @NonNull Node actual,
+ boolean nextSiblings,
+ @Nullable StringBuilder diff,
+ @Nullable String keyAttr) {
+ Map<String, String> nsPrefixE = new HashMap<String, String>();
+ Map<String, String> nsPrefixA = new HashMap<String, String>();
+ String sE = MergerXmlUtils.printElement(expected, nsPrefixE, ""); //$NON-NLS-1$
+ String sA = MergerXmlUtils.printElement(actual, nsPrefixA, ""); //$NON-NLS-1$
+ if (sE.equals(sA)) {
+ return true;
+ } else {
+ if (diff != null) {
+ MergerXmlUtils.printXmlDiff(diff, sE, sA, nsPrefixE, nsPrefixA, NS_URI + ':' + keyAttr);
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Finds the first element matching the given XPath expression in the given document.
+ *
+ * @param doc The document where to find the expression.
+ * @param path The XPath expression. It must yield an {@link Element} node type.
+ * @return The {@link Element} found or null.
+ */
+ @Nullable
+ private Element findFirstElement(
+ @NonNull Document doc,
+ @NonNull String path) {
+ Node result;
+ try {
+ result = (Node) mXPath.evaluate(path, doc, XPathConstants.NODE);
+ if (result instanceof Element) {
+ return (Element) result;
+ }
+
+ if (result != null) {
+ mLog.error(Severity.ERROR,
+ xmlFileAndLine(doc),
+ "Unexpected Node type %s when evaluating %s", //$NON-NLS-1$
+ result.getClass().getName(), path);
+ }
+ } catch (XPathExpressionException e) {
+ mLog.error(Severity.ERROR,
+ xmlFileAndLine(doc),
+ "XPath error on expr %s: %s", //$NON-NLS-1$
+ path, e.toString());
+ }
+ return null;
+ }
+
+ /**
+ * Finds zero or more elements matching the given XPath expression in the given document.
+ *
+ * @param doc The document where to find the expression.
+ * @param path The XPath expression. Only {@link Element}s nodes will be returned.
+ * @return A list of {@link Element} found, possibly empty but never null.
+ */
+ private List<Element> findElements(
+ @NonNull Document doc,
+ @NonNull String path) {
+ return findElements(doc, path, null, null);
+ }
+
+
+ /**
+ * Finds zero or more elements matching the given XPath expression in the given document.
+ * <p/>
+ * Furthermore, the elements must have an attribute matching the given attribute name
+ * and value if provided. (If you don't need to match an attribute, use the other version.)
+ * <p/>
+ * Note that if you provide {@code attrName} as non-null then the {@code attrValue}
+ * must be non-null too. In this case the XPath expression will be modified to add
+ * the check by naively appending a "[name='value']" filter.
+ *
+ * @param doc The document where to find the expression.
+ * @param path The XPath expression. Only {@link Element}s nodes will be returned.
+ * @param attrName The name of the optional attribute to match. Can be null.
+ * @param attrValue The value of the optional attribute to match.
+ * Can be null if {@code attrName} is null, otherwise must be non-null.
+ * @return A list of {@link Element} found, possibly empty but never null.
+ *
+ * @see #findElements(Document, String)
+ */
+ private List<Element> findElements(
+ @NonNull Document doc,
+ @NonNull String path,
+ @Nullable String attrName,
+ @Nullable String attrValue) {
+ List<Element> elements = new ArrayList<Element>();
+
+ if (attrName != null) {
+ assert attrValue != null;
+ // Generate expression /manifest/application/activity[@android:name='my.fqcn']
+ path = String.format("%1$s[@%2$s:%3$s='%4$s']", //$NON-NLS-1$
+ path, NS_PREFIX, attrName, attrValue);
+ }
+
+ try {
+ NodeList results = (NodeList) mXPath.evaluate(path, doc, XPathConstants.NODESET);
+ if (results != null && results.getLength() > 0) {
+ for (int i = 0; i < results.getLength(); i++) {
+ Node n = results.item(i);
+ assert n instanceof Element;
+ if (n instanceof Element) {
+ elements.add((Element) n);
+ } else {
+ mLog.error(Severity.ERROR,
+ xmlFileAndLine(doc),
+ "Unexpected Node type %s when evaluating %s", //$NON-NLS-1$
+ n.getClass().getName(), path);
+ }
+ }
+ }
+
+ } catch (XPathExpressionException e) {
+ mLog.error(Severity.ERROR,
+ xmlFileAndLine(doc),
+ "XPath error on expr %s: %s", //$NON-NLS-1$
+ path, e.toString());
+ }
+
+ return elements;
+ }
+
+ /**
+ * Returns a new {@link FileAndLine} structure that identifies
+ * the base filename & line number from which the XML node was parsed.
+ * <p/>
+ * When the line number is unknown (e.g. if a {@link Document} instance is given)
+ * then line number 0 will be used.
+ *
+ * @param node The node or document where the error occurs. Must not be null.
+ * @return A new non-null {@link FileAndLine} combining the file name and line number.
+ */
+ @NonNull
+ private FileAndLine xmlFileAndLine(@NonNull Node node) {
+ return MergerXmlUtils.xmlFileAndLine(node);
+ }
+
+ /**
+ * Checks whether the given element has a tools:merge=override or tools:merge=remove attribute.
+ * @param node The node to check.
+ * @return True if the element has a tools:merge=override or tools:merge=remove attribute.
+ */
+ private boolean hasOverrideOrRemoveTag(@Nullable Node node) {
+ if (node == null || node.getNodeType() != Node.ELEMENT_NODE) {
+ return false;
+ }
+ NamedNodeMap attrs = node.getAttributes();
+ Node merge = attrs.getNamedItemNS(TOOLS_URI, MERGE_ATTR);
+ String value = merge == null ? null : merge.getNodeValue();
+ return MERGE_OVERRIDE.equals(value) || MERGE_REMOVE.equals(value);
+ }
+
+ /**
+ * Cleans up all tools attributes from the given node hierarchy.
+ * <p/>
+ * If an element is marked with tools:merge=override, this attribute is removed.
+ * If an element is marked with tools:merge=remove, the <em>whole</em> element is removed.
+ *
+ * @param root The root node to parse and edit, recursively.
+ */
+ private void cleanupToolsAttributes(@Nullable Node root) {
+ if (root == null) {
+ return;
+ }
+ NamedNodeMap attrs = root.getAttributes();
+ if (attrs != null) {
+ for (int i = attrs.getLength() - 1; i >= 0; i--) {
+ Node attr = attrs.item(i);
+ if (SdkConstants.XMLNS_URI.equals(attr.getNamespaceURI()) &&
+ TOOLS_URI.equals(attr.getNodeValue())) {
+ attrs.removeNamedItem(attr.getNodeName());
+ } else if (TOOLS_URI.equals(attr.getNamespaceURI()) &&
+ MERGE_ATTR.equals(attr.getLocalName())) {
+ attrs.removeNamedItem(attr.getNodeName());
+ }
+ }
+ assert attrs.getNamedItemNS(TOOLS_URI, MERGE_ATTR) == null;
+ }
+
+ for (Node child = root.getFirstChild(); child != null; ) {
+ if (child.getNodeType() != Node.ELEMENT_NODE) {
+ child = child.getNextSibling();
+ continue;
+ }
+ attrs = child.getAttributes();
+ Node merge = attrs == null ? null : attrs.getNamedItemNS(TOOLS_URI, MERGE_ATTR);
+ String value = merge == null ? null : merge.getNodeValue();
+ Node sibling = child.getNextSibling();
+ if (MERGE_REMOVE.equals(value)) {
+ // Note: save the previous sibling since removing the child will clear its siblings.
+ Node prev = child.getPreviousSibling();
+ root.removeChild(child);
+ // If there's some whitespace just before that element, clean it up too.
+ while (prev != null && prev.getNodeType() == Node.TEXT_NODE) {
+ if (prev.getNodeValue().trim().length() == 0) {
+ Node prevPrev = prev.getPreviousSibling();
+ root.removeChild(prev);
+ prev = prevPrev;
+ } else {
+ break;
+ }
+ }
+ } else {
+ cleanupToolsAttributes(child);
+ }
+ child = sibling;
+ }
+ }
+
+ /**
+ * @see #cleanupToolsAttributes(Node)
+ */
+ private Document cleanupToolsAttributes(@NonNull Document doc) {
+ cleanupToolsAttributes(doc.getFirstChild());
+ return doc;
+ }
+
+ /**
+ * Sets whether this manifest merger will insert source markers into the merged source
+ *
+ * @param insertSourceMarkers if true, insert source markers
+ */
+ public void setInsertSourceMarkers(boolean insertSourceMarkers) {
+ mInsertSourceMarkers = insertSourceMarkers;
+ }
+
+ /**
+ * Returns whether this manifest merger will insert source markers into the merged source
+ *
+ * @return whether this manifest merger will insert source markers into the merged source
+ */
+ public boolean isInsertSourceMarkers() {
+ return mInsertSourceMarkers;
+ }
+
+ /** Inserts source markers in the given document */
+ private static void insertSourceMarkers(@NonNull Document mainDoc) {
+ Element root = mainDoc.getDocumentElement();
+ if (root != null) {
+ File file = MergerXmlUtils.getFileFor(root);
+ if (file != null) {
+ insertSourceMarker(mainDoc, root, file, false);
+ }
+
+ insertSourceMarkers(root, file);
+ }
+ }
+
+ private static File insertSourceMarkers(@NonNull Node node, @Nullable File currentFile) {
+ for (int i = 0; i < node.getChildNodes().getLength(); i++) {
+ Node child = node.getChildNodes().item(i);
+ short nodeType = child.getNodeType();
+ if (nodeType == Node.ELEMENT_NODE
+ || nodeType == Node.COMMENT_NODE
+ || nodeType == Node.DOCUMENT_NODE
+ || nodeType == Node.CDATA_SECTION_NODE) {
+ File file = MergerXmlUtils.getFileFor(child);
+ if (file != null && !file.equals(currentFile)) {
+ i += insertSourceMarker(node, child, file, false);
+ currentFile = file;
+ }
+
+ currentFile = insertSourceMarkers(child, currentFile);
+ }
+ }
+
+ Node lastElement = node.getLastChild();
+ while (lastElement != null && lastElement.getNodeType() == Node.TEXT_NODE) {
+ lastElement = lastElement.getPreviousSibling();
+ }
+ if (lastElement != null && lastElement.getNodeType() == Node.ELEMENT_NODE) {
+ File parentFile = MergerXmlUtils.getFileFor(node);
+ File lastFile = MergerXmlUtils.getFileFor(lastElement);
+ if (lastFile != null && parentFile != null && !parentFile.equals(lastFile)) {
+ insertSourceMarker(node, lastElement, parentFile, true);
+ currentFile = parentFile;
+ }
+ }
+
+ return currentFile;
+ }
+
+ private static int insertSourceMarker(@NonNull Node parent, @NonNull Node node,
+ @NonNull File file, boolean after) {
+ int insertCount = 0;
+ Document doc = parent.getNodeType() ==
+ Node.DOCUMENT_NODE ? (Document) parent : parent.getOwnerDocument();
+
+ String comment;
+ try {
+ comment = SdkUtils.createPathComment(file, true);
+ } catch (MalformedURLException e) {
+ return insertCount;
+ }
+
+ Node prev = node.getPreviousSibling();
+ String newline;
+ if (prev != null && prev.getNodeType() == Node.TEXT_NODE) {
+ // Duplicate indentation from previous line. Once we switch the merger
+ // over to using the XmlPrettyPrinter, we won't need this.
+ newline = prev.getNodeValue();
+ int index = newline.lastIndexOf('\n');
+ if (index != -1) {
+ newline = newline.substring(index);
+ }
+ } else {
+ newline = "\n";
+ }
+
+ if (after) {
+ node = node.getNextSibling();
+ }
+
+ parent.insertBefore(doc.createComment(comment), node);
+ insertCount++;
+
+ // Can't add text nodes at the document level in Xerces, even though
+ // it will happily parse these
+ if (parent.getNodeType() != Node.DOCUMENT_NODE) {
+ parent.insertBefore(doc.createTextNode(newline), node);
+ insertCount++;
+ }
+
+ return insertCount;
+ }
+}
diff --git a/manifest-merger/src/main/java/com/android/manifmerger/MergerLog.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/MergerLog.java
similarity index 100%
rename from manifest-merger/src/main/java/com/android/manifmerger/MergerLog.java
rename to build-system/manifest-merger/src/main/java/com/android/manifmerger/MergerLog.java
diff --git a/build-system/manifest-merger/src/main/java/com/android/manifmerger/MergerXmlUtils.java b/build-system/manifest-merger/src/main/java/com/android/manifmerger/MergerXmlUtils.java
new file mode 100755
index 0000000..46093ba
--- /dev/null
+++ b/build-system/manifest-merger/src/main/java/com/android/manifmerger/MergerXmlUtils.java
@@ -0,0 +1,959 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.manifmerger;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.manifmerger.IMergerLog.FileAndLine;
+import com.android.manifmerger.IMergerLog.Severity;
+import com.android.utils.ILogger;
+import com.android.utils.XmlUtils;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXParseException;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+/**
+ * A few XML handling utilities.
+ */
+class MergerXmlUtils {
+
+ private static final String DATA_ORIGIN_FILE = "manif.merger.file"; //$NON-NLS-1$
+ private static final String DATA_FILE_NAME = "manif.merger.filename"; //$NON-NLS-1$
+ private static final String DATA_LINE_NUMBER = "manif.merger.line#"; //$NON-NLS-1$
+
+ /**
+ * Parses the given XML file as a DOM document.
+ * The parser does not validate the DTD nor any kind of schema.
+ * It is namespace aware.
+ * <p/>
+ * This adds a user tag with the original {@link File} to the returned document.
+ * You can retrieve this file later by using {@link #extractXmlFilename(Node)}.
+ *
+ * @param xmlFile The XML {@link File} to parse. Must not be null.
+ * @param log An {@link ILogger} for reporting errors. Must not be null.
+ * @param merger The {@link ManifestMerger} this document is intended for
+ * @return A new DOM {@link Document}, or null.
+ */
+ @Nullable
+ static Document parseDocument(@NonNull final File xmlFile, @NonNull final IMergerLog log,
+ @NonNull ManifestMerger merger) {
+ try {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ Reader reader = new BufferedReader(new FileReader(xmlFile));
+ InputSource is = new InputSource(reader);
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ DocumentBuilder builder = factory.newDocumentBuilder();
+
+ // We don't want the default handler which prints errors to stderr.
+ builder.setErrorHandler(new ErrorHandler() {
+ @Override
+ public void warning(SAXParseException e) {
+ log.error(Severity.WARNING,
+ new FileAndLine(xmlFile.getAbsolutePath(), 0),
+ "Warning when parsing: %1$s",
+ e.toString());
+ }
+ @Override
+ public void fatalError(SAXParseException e) {
+ log.error(Severity.ERROR,
+ new FileAndLine(xmlFile.getAbsolutePath(), 0),
+ "Fatal error when parsing: %1$s",
+ xmlFile.getName(), e.toString());
+ }
+ @Override
+ public void error(SAXParseException e) {
+ log.error(Severity.ERROR,
+ new FileAndLine(xmlFile.getAbsolutePath(), 0),
+ "Error when parsing: %1$s",
+ e.toString());
+ }
+ });
+
+ Document doc = builder.parse(is);
+ doc.setUserData(DATA_ORIGIN_FILE, xmlFile, null /*handler*/);
+ findLineNumbers(doc, 1);
+
+ if (merger.isInsertSourceMarkers()) {
+ setSource(doc, xmlFile);
+ }
+
+ return doc;
+
+ } catch (FileNotFoundException e) {
+ log.error(Severity.ERROR,
+ new FileAndLine(xmlFile.getAbsolutePath(), 0),
+ "XML file not found");
+
+ } catch (Exception e) {
+ log.error(Severity.ERROR,
+ new FileAndLine(xmlFile.getAbsolutePath(), 0),
+ "Failed to parse XML file: %1$s",
+ e.toString());
+ }
+
+ return null;
+ }
+
+ /**
+ * Parses the given XML string as a DOM document.
+ * The parser does not validate the DTD nor any kind of schema.
+ * It is namespace aware.
+ *
+ * @param xml The XML string to parse. Must not be null.
+ * @param log An {@link ILogger} for reporting errors. Must not be null.
+ * @return A new DOM {@link Document}, or null.
+ */
+ @VisibleForTesting
+ @Nullable
+ static Document parseDocument(@NonNull String xml,
+ @NonNull IMergerLog log,
+ @NonNull FileAndLine errorContext) {
+ try {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ InputSource is = new InputSource(new StringReader(xml));
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ Document doc = builder.parse(is);
+ findLineNumbers(doc, 1);
+ if (errorContext.getFileName() != null) {
+ setSource(doc, new File(errorContext.getFileName()));
+ }
+ return doc;
+ } catch (Exception e) {
+ log.error(Severity.ERROR, errorContext, "Failed to parse XML string");
+ }
+
+ return null;
+ }
+
+ /**
+ * Decorates the document with the specified file name, which can be
+ * retrieved later by calling {@link #extractLineNumber(Node)}.
+ * <p/>
+ * It also tries to add line number information, with the caveat that the
+ * current implementation is a gross approximation.
+ * <p/>
+ * There is no need to call this after calling one of the {@code parseDocument()}
+ * methods since they already decorated their own document.
+ *
+ * @param doc The document to decorate.
+ * @param fileName The name to retrieve later for that document.
+ */
+ static void decorateDocument(@NonNull Document doc, @NonNull String fileName) {
+ doc.setUserData(DATA_FILE_NAME, fileName, null /*handler*/);
+ findLineNumbers(doc, 1);
+ }
+
+ /**
+ * Returns a new {@link FileAndLine} structure that identifies
+ * the base filename & line number from which the XML node was parsed.
+ * <p/>
+ * When the line number is unknown (e.g. if a {@link Document} instance is given)
+ * then line number 0 will be used.
+ *
+ * @param node The node or document where the error occurs. Must not be null.
+ * @return A new non-null {@link FileAndLine} combining the file name and line number.
+ */
+ @NonNull
+ static FileAndLine xmlFileAndLine(@NonNull Node node) {
+ String name = extractXmlFilename(node);
+ int line = extractLineNumber(node); // 0 in case of error or unknown
+ return new FileAndLine(name, line);
+ }
+
+ /**
+ * Extracts the origin {@link File} that {@link #parseDocument(File, IMergerLog,
+ * ManifestMerger)} added to the XML document or the string added by
+ *
+ * @param xmlNode Any node from a document returned by {@link #parseDocument(File, IMergerLog,
+ * ManifestMerger)}.
+ * @return The {@link File} object used to create the document or null.
+ */
+ @Nullable
+ static String extractXmlFilename(@Nullable Node xmlNode) {
+ if (xmlNode != null && xmlNode.getNodeType() != Node.DOCUMENT_NODE) {
+ xmlNode = xmlNode.getOwnerDocument();
+ }
+ if (xmlNode != null) {
+ Object data = xmlNode.getUserData(DATA_ORIGIN_FILE);
+ if (data instanceof File) {
+ return ((File) data).getPath();
+ }
+ data = xmlNode.getUserData(DATA_FILE_NAME);
+ if (data instanceof String) {
+ return (String) data;
+ }
+ }
+
+ return null;
+ }
+
+ public static void setSource(@NonNull Node node, @NonNull File source) {
+ //noinspection ConstantConditions
+ for (; node != null; node = node.getNextSibling()) {
+ short nodeType = node.getNodeType();
+ if (nodeType == Node.ELEMENT_NODE
+ || nodeType == Node.COMMENT_NODE
+ || nodeType == Node.DOCUMENT_NODE
+ || nodeType == Node.CDATA_SECTION_NODE) {
+ node.setUserData(DATA_ORIGIN_FILE, source, null);
+ }
+ Node child = node.getFirstChild();
+ setSource(child, source);
+ }
+ }
+
+ /**
+ * This is a CRUDE INEXACT HACK to decorate the DOM with some kind of line number
+ * information for elements. It's inexact because by the time we get the DOM we
+ * already have lost all the information about whitespace between attributes.
+ * <p/>
+ * Also we don't even try to deal with \n vs \r vs \r\n insanity. This only counts
+ * the \n occurring in text nodes to determine line advances, which is clearly flawed.
+ * <p/>
+ * However it's good enough for testing, and we'll replace it by a PositionXmlParser
+ * once it's moved into com.android.util.
+ */
+ private static int findLineNumbers(Node node, int line) {
+ for (; node != null; node = node.getNextSibling()) {
+ node.setUserData(DATA_LINE_NUMBER, Integer.valueOf(line), null /*handler*/);
+
+ if (node.getNodeType() == Node.TEXT_NODE) {
+ String text = node.getNodeValue();
+ if (text.length() > 0) {
+ for (int pos = 0; (pos = text.indexOf('\n', pos)) != -1; pos++) {
+ ++line;
+ }
+ }
+ }
+
+ Node child = node.getFirstChild();
+ if (child != null) {
+ line = findLineNumbers(child, line);
+ }
+ }
+ return line;
+ }
+
+ /**
+ * Extracts the line number that {@link #findLineNumbers} added to the XML nodes.
+ *
+ * @param xmlNode Any node from a document returned by {@link #parseDocument(File, IMergerLog,
+ * ManifestMerger)}.
+ * @return The line number if found or 0.
+ */
+ static int extractLineNumber(@Nullable Node xmlNode) {
+ if (xmlNode != null) {
+ Object data = xmlNode.getUserData(DATA_LINE_NUMBER);
+ if (data instanceof Integer) {
+ return ((Integer) data).intValue();
+ }
+ }
+
+ return 0;
+ }
+
+ /**
+ * Outputs the given XML {@link Document} to the file {@code outFile}.
+ *
+ * TODO right now reformats the document. Needs to output as-is, respecting white-space.
+ *
+ * @param doc The document to output. Must not be null.
+ * @param outFile The {@link File} where to write the document.
+ * @param log A log in case of error.
+ * @return True if the file was written, false in case of error.
+ */
+ static boolean printXmlFile(
+ @NonNull Document doc,
+ @NonNull File outFile,
+ @NonNull IMergerLog log) {
+ // Quick thing based on comments from http://stackoverflow.com/questions/139076
+ try {
+ Transformer tf = TransformerFactory.newInstance().newTransformer();
+ tf.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); //$NON-NLS-1$
+ tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); //$NON-NLS-1$
+ tf.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$
+ tf.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", //$NON-NLS-1$
+ "4"); //$NON-NLS-1$
+ tf.transform(new DOMSource(doc), new StreamResult(outFile));
+ return true;
+ } catch (TransformerException e) {
+ log.error(Severity.ERROR,
+ new FileAndLine(outFile.getName(), 0),
+ "Failed to write XML file: %1$s",
+ e.toString());
+ return false;
+ }
+ }
+
+ /**
+ * Outputs the given XML {@link Document} as a string.
+ *
+ * TODO right now reformats the document. Needs to output as-is, respecting white-space.
+ *
+ * @param doc The document to output. Must not be null.
+ * @param log A log in case of error.
+ * @return A string representation of the XML. Null in case of error.
+ */
+ static String printXmlString(
+ @NonNull Document doc,
+ @NonNull IMergerLog log) {
+ try {
+ Transformer tf = TransformerFactory.newInstance().newTransformer();
+ tf.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); //$NON-NLS-1$
+ tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); //$NON-NLS-1$
+ tf.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$
+ tf.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", //$NON-NLS-1$
+ "4"); //$NON-NLS-1$
+ StringWriter sw = new StringWriter();
+ tf.transform(new DOMSource(doc), new StreamResult(sw));
+ return sw.toString();
+ } catch (TransformerException e) {
+ log.error(Severity.ERROR,
+ new FileAndLine(extractXmlFilename(doc), 0),
+ "Failed to write XML file: %1$s",
+ e.toString());
+ return null;
+ }
+ }
+
+ /**
+ * Dumps the structure of the DOM to a simple text string.
+ *
+ * @param node The first node to dump (recursively). Can be null.
+ * @param nextSiblings If true, will also dump the following siblings.
+ * If false, it will just process the given node.
+ * @return A string representation of the Node structure, useful for debugging.
+ */
+ @NonNull
+ static String dump(@Nullable Node node, boolean nextSiblings) {
+ return dump(node, 0 /*offset*/, nextSiblings, true /*deep*/, null /*keyAttr*/);
+ }
+
+
+ /**
+ * Dumps the structure of the DOM to a simple text string.
+ * Each line is terminated with a \n separator.
+ *
+ * @param node The first node to dump. Can be null.
+ * @param offsetIndex The offset to add at the begining of each line. Each offset is
+ * converted into 2 space characters.
+ * @param nextSiblings If true, will also dump the following siblings.
+ * If false, it will just process the given node.
+ * @param deep If true, this will recurse into children.
+ * @param keyAttr An optional attribute *local* name to insert when writing an element.
+ * For example when writing an Activity, it helps to always insert "name" attribute.
+ * @return A string representation of the Node structure, useful for debugging.
+ */
+ @NonNull
+ static String dump(
+ @Nullable Node node,
+ int offsetIndex,
+ boolean nextSiblings,
+ boolean deep,
+ @Nullable String keyAttr) {
+ StringBuilder sb = new StringBuilder();
+
+ String offset = ""; //$NON-NLS-1$
+ for (int i = 0; i < offsetIndex; i++) {
+ offset += " "; //$NON-NLS-1$
+ }
+
+ if (node == null) {
+ sb.append(offset).append("(end reached)\n");
+
+ } else {
+ for (; node != null; node = node.getNextSibling()) {
+ String type = null;
+ short t = node.getNodeType();
+ switch(t) {
+ case Node.ELEMENT_NODE:
+ String attr = "";
+ if (keyAttr != null) {
+ for (Node a : sortedAttributeList(node.getAttributes())) {
+ if (a != null && keyAttr.equals(a.getLocalName())) {
+ attr = String.format(" %1$s=%2$s",
+ a.getNodeName(), a.getNodeValue());
+ break;
+ }
+ }
+ }
+ sb.append(String.format("%1$s<%2$s%3$s>\n",
+ offset, node.getNodeName(), attr));
+ break;
+ case Node.COMMENT_NODE:
+ sb.append(String.format("%1$s<!-- %2$s -->\n",
+ offset, node.getNodeValue()));
+ break;
+ case Node.TEXT_NODE:
+ String txt = node.getNodeValue().trim();
+ if (txt.length() == 0) {
+ // Keep this for debugging. TODO make it a flag
+ // to dump whitespace on debugging. Otherwise ignore it.
+ // txt = "[whitespace]";
+ break;
+ }
+ sb.append(String.format("%1$s%2$s\n", offset, txt));
+ break;
+ case Node.ATTRIBUTE_NODE:
+ sb.append(String.format("%1$s @%2$s = %3$s\n",
+ offset, node.getNodeName(), node.getNodeValue()));
+ break;
+ case Node.CDATA_SECTION_NODE:
+ type = "cdata"; //$NON-NLS-1$
+ break;
+ case Node.DOCUMENT_NODE:
+ type = "document"; //$NON-NLS-1$
+ break;
+ case Node.PROCESSING_INSTRUCTION_NODE:
+ type = "PI"; //$NON-NLS-1$
+ break;
+ default:
+ type = Integer.toString(t);
+ }
+
+ if (type != null) {
+ sb.append(String.format("%1$s[%2$s] <%3$s> %4$s\n",
+ offset, type, node.getNodeName(), node.getNodeValue()));
+ }
+
+ if (deep) {
+ for (Attr attr : sortedAttributeList(node.getAttributes())) {
+ sb.append(String.format("%1$s @%2$s = %3$s\n",
+ offset, attr.getNodeName(), attr.getNodeValue()));
+ }
+
+ Node child = node.getFirstChild();
+ if (child != null) {
+ sb.append(dump(child, offsetIndex+1, true, true, keyAttr));
+ }
+ }
+
+ if (!nextSiblings) {
+ break;
+ }
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Returns a sorted list of attributes.
+ * The list is never null and does not contain null items.
+ *
+ * @param attrMap A Node map as returned by {@link Node#getAttributes()}.
+ * Can be null, in which case an empty list is returned.
+ * @return A non-null, possible empty, list of all nodes that are actual {@link Attr},
+ * sorted by increasing attribute name.
+ */
+ @NonNull
+ static List<Attr> sortedAttributeList(@Nullable NamedNodeMap attrMap) {
+ List<Attr> list = new ArrayList<Attr>();
+
+ if (attrMap != null) {
+ for (int i = 0; i < attrMap.getLength(); i++) {
+ Node attr = attrMap.item(i);
+ if (attr instanceof Attr) {
+ list.add((Attr) attr);
+ }
+ }
+ }
+
+ if (list.size() > 1) {
+ // Sort it by attribute name
+ Collections.sort(list, getAttrComparator());
+ }
+
+ return list;
+ }
+
+ /**
+ * Returns a comparator for {@link Attr}, alphabetically sorted by name.
+ * The "name" attribute is special and always sorted to the front.
+ */
+ @NonNull
+ static Comparator<? super Attr> getAttrComparator() {
+ return new Comparator<Attr>() {
+ @Override
+ public int compare(Attr a1, Attr a2) {
+ String s1 = a1 == null ? "" : a1.getNodeName(); //$NON-NLS-1$
+ String s2 = a2 == null ? "" : a2.getNodeName(); //$NON-NLS-1$
+
+ boolean name1 = s1.equals("name"); //$NON-NLS-1$
+ boolean name2 = s2.equals("name"); //$NON-NLS-1$
+
+ if (name1 && name2) {
+ return 0;
+ } else if (name1) {
+ return -1; // name is always first
+ } else if (name2) {
+ return 1; // name is always first
+ } else {
+ return s1.compareTo(s2);
+ }
+ }
+ };
+ }
+
+ /**
+ * Inject attributes into an existing document.
+ * <p/>
+ * The map keys are "/manifest/elements...|attribute-ns-uri attribute-local-name",
+ * for example "/manifest/uses-sdk|http://schemas.android.com/apk/res/android minSdkVersion".
+ * (note the space separator between the attribute URI and its local name.)
+ * The elements will be created if they don't exists. Existing attributes will be modified.
+ * The replacement is done on the main document <em>before</em> merging.
+ * The value can be null to remove an existing attribute.
+ *
+ * @param doc The document to modify in-place.
+ * @param attributeMap A map of attributes to inject in the form [pseudo-xpath] => value.
+ * @param log A log in case of error.
+ */
+ static void injectAttributes(
+ @Nullable Document doc,
+ @Nullable Map<String, String> attributeMap,
+ @NonNull IMergerLog log) {
+ if (doc == null || attributeMap == null || attributeMap.isEmpty()) {
+ return;
+ }
+
+ // 1=path 2=URI 3=local name
+ final Pattern keyRx = Pattern.compile("^/([^\\|]+)\\|([^ ]*) +(.+)$"); //$NON-NLS-1$
+ final FileAndLine docInfo = xmlFileAndLine(doc);
+
+ nextAttribute: for (Entry<String, String> entry : attributeMap.entrySet()) {
+ String key = entry.getKey();
+ String value = entry.getValue();
+ if (key == null || key.isEmpty()) {
+ continue;
+ }
+
+ Matcher m = keyRx.matcher(key);
+ if (!m.matches()) {
+ log.error(Severity.WARNING, docInfo, "Invalid injected attribute key: %s", key);
+ continue;
+ }
+ String path = m.group(1);
+ String attrNsUri = m.group(2);
+ String attrName = m.group(3);
+
+ String[] segment = path.split(Pattern.quote("/")); //$NON-NLS-1$
+
+ // Get the path elements. Create them as needed if they don't exist.
+ Node element = doc;
+ nextSegment: for (int i = 0; i < segment.length; i++) {
+ // Find a child with the segment's name
+ String name = segment[i];
+ for (Node child = element.getFirstChild();
+ child != null;
+ child = child.getNextSibling()) {
+ if (child.getNodeType() == Node.ELEMENT_NODE &&
+ child.getNamespaceURI() == null &&
+ child.getNodeName().equals(name)) {
+ // Found it. Continue to the next inner segment.
+ element = child;
+ continue nextSegment;
+ }
+ }
+ // No such element. Create it.
+ if (value == null) {
+ // If value is null, we want to remove, not create and if can't find the
+ // element, then we're done: there's no such attribute to remove.
+ break nextAttribute;
+ }
+
+ Element child = doc.createElement(name);
+ element = element.insertBefore(child, element.getFirstChild());
+ }
+
+ if (element == null) {
+ log.error(Severity.WARNING, docInfo, "Invalid injected attribute path: %s", path);
+ return;
+ }
+
+ NamedNodeMap attrs = element.getAttributes();
+ if (attrs != null) {
+
+
+ if (attrNsUri != null && attrNsUri.isEmpty()) {
+ attrNsUri = null;
+ }
+ Node attr = attrs.getNamedItemNS(attrNsUri, attrName);
+
+ if (value == null) {
+ // We want to remove the attribute from the attribute map.
+ if (attr != null) {
+ attrs.removeNamedItemNS(attrNsUri, attrName);
+ }
+
+ } else {
+ // We want to add or replace the attribute.
+ if (attr == null) {
+ attr = doc.createAttributeNS(attrNsUri, attrName);
+ if (attrNsUri != null) {
+ attr.setPrefix(XmlUtils.lookupNamespacePrefix(element, attrNsUri));
+ }
+ attrs.setNamedItemNS(attr);
+ }
+ attr.setNodeValue(value);
+ }
+ }
+ }
+ }
+
+ // -------
+
+ /**
+ * Flatten the element to a string. This "pretty prints" the XML tree starting
+ * from the given node and all its children and attributes.
+ * <p/>
+ * The output is designed to be printed using {@link #printXmlDiff}.
+ *
+ * @param node The root node to print.
+ * @param nsPrefix A map that is filled with all the URI=>prefix found.
+ * The internal string only contains the expanded URIs but this is rather verbose
+ * so when printing the diff these will be replaced by the prefixes collected here.
+ * @param prefix A "space" prefix added at the beginning of each line for indentation
+ * purposes. The diff printer later relies on this to find out the structure.
+ */
+ @NonNull
+ static String printElement(
+ @NonNull Node node,
+ @NonNull Map<String, String> nsPrefix,
+ @NonNull String prefix) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(prefix).append('<');
+ String uri = node.getNamespaceURI();
+ if (uri != null) {
+ sb.append(uri).append(':');
+ nsPrefix.put(uri, node.getPrefix());
+ }
+ sb.append(node.getLocalName());
+ printAttributes(sb, node, nsPrefix, prefix);
+ sb.append(">\n"); //$NON-NLS-1$
+ printChildren(sb, node.getFirstChild(), true, nsPrefix, prefix + " "); //$NON-NLS-1$
+
+ sb.append(prefix).append("</"); //$NON-NLS-1$
+ if (uri != null) {
+ sb.append(uri).append(':');
+ }
+ sb.append(node.getLocalName());
+ sb.append(">\n"); //$NON-NLS-1$
+
+ return sb.toString();
+ }
+
+ /**
+ * Flatten several children elements to a string.
+ * This is an implementation detail for {@link #printElement(Node, Map, String)}.
+ * <p/>
+ * If {@code nextSiblings} is false, the string conversion takes only the given
+ * child element and stops there.
+ * <p/>
+ * If {@code nextSiblings} is true, the string conversion also takes _all_ the siblings
+ * after the given element. The idea is the caller can call this with the first child
+ * of a parent and get a string showing all the children at the same time. They are
+ * sorted to avoid the ordering issue.
+ */
+ @NonNull
+ private static StringBuilder printChildren(
+ @NonNull StringBuilder sb,
+ @NonNull Node child,
+ boolean nextSiblings,
+ @NonNull Map<String, String> nsPrefix,
+ @NonNull String prefix) {
+ ArrayList<String> children = new ArrayList<String>();
+
+ boolean hasText = false;
+ for (; child != null; child = child.getNextSibling()) {
+ short t = child.getNodeType();
+ if (nextSiblings && t == Node.TEXT_NODE) {
+ // We don't typically have meaningful text nodes in an Android manifest.
+ // If there are, just dump them as-is into the element representation.
+ // We do trim whitespace and ignore all-whitespace or empty text nodes.
+ String s = child.getNodeValue().trim();
+ if (s.length() > 0) {
+ sb.append(s);
+ hasText = true;
+ }
+ } else if (t == Node.ELEMENT_NODE) {
+ children.add(printElement(child, nsPrefix, prefix));
+ if (!nextSiblings) {
+ break;
+ }
+ }
+ }
+
+ if (hasText) {
+ sb.append('\n');
+ }
+
+ if (!children.isEmpty()) {
+ Collections.sort(children);
+ for (String s : children) {
+ sb.append(s);
+ }
+ }
+
+ return sb;
+ }
+
+ /**
+ * Flatten several attributes to a string using their alphabetical order.
+ * This is an implementation detail for {@link #printElement(Node, Map, String)}.
+ */
+ @NonNull
+ private static StringBuilder printAttributes(
+ @NonNull StringBuilder sb,
+ @NonNull Node node,
+ @NonNull Map<String, String> nsPrefix,
+ @NonNull String prefix) {
+ ArrayList<String> attrs = new ArrayList<String>();
+
+ NamedNodeMap attrMap = node.getAttributes();
+ if (attrMap != null) {
+ StringBuilder sb2 = new StringBuilder();
+ for (int i = 0; i < attrMap.getLength(); i++) {
+ Node attr = attrMap.item(i);
+ if (attr instanceof Attr) {
+ sb2.setLength(0);
+ sb2.append('@');
+ String uri = attr.getNamespaceURI();
+ if (uri != null) {
+ sb2.append(uri).append(':');
+ nsPrefix.put(uri, attr.getPrefix());
+ }
+ sb2.append(attr.getLocalName());
+ sb2.append("=\"").append(attr.getNodeValue()).append('\"'); //$NON-NLS-1$
+ attrs.add(sb2.toString());
+ }
+ }
+ }
+
+ Collections.sort(attrs);
+
+ for(String attr : attrs) {
+ sb.append('\n');
+ sb.append(prefix).append(" ").append(attr); //$NON-NLS-1$
+ }
+ return sb;
+ }
+
+ //------------
+
+ /**
+ * Computes a quick diff between two strings generated by
+ * {@link #printElement(Node, Map, String)}.
+ * <p/>
+ * This is a <em>not</em> designed to be a full contextual diff.
+ * It just stops at the first difference found, printing up to 3 lines of diff
+ * and backtracking to add prior contextual information to understand the
+ * structure of the element where the first diff line occurred (by printing
+ * each parent found till the root one as well as printing the attribute
+ * named by {@code keyAttr}).
+ *
+ * @param sb The string builder where to output is written.
+ * @param expected The expected XML tree (as generated by {@link #printElement}.)
+ * For best result this would be the "destination" XML we're merging into,
+ * e.g. the main manifest.
+ * @param actual The actual XML tree (as generated by {@link #printElement}.)
+ * For best result this would be the "source" XML we're merging from,
+ * e.g. a library manifest.
+ * @param nsPrefixE The map of URI=>prefix for the expected XML tree.
+ * @param nsPrefixA The map of URI=>prefix for the actual XML tree.
+ * @param keyAttr An optional attribute *full* name (uri:local name) to always
+ * insert when writing the contextual lines before a diff line.
+ * For example when writing an Activity, it helps to always insert
+ * the "name" attribute since that's the key element to help the user
+ * identify which node is being dumped.
+ */
+ static void printXmlDiff(
+ StringBuilder sb,
+ String expected,
+ String actual,
+ Map<String, String> nsPrefixE,
+ Map<String, String> nsPrefixA,
+ String keyAttr) {
+ String[] aE = expected.split("\n");
+ String[] aA = actual.split("\n");
+ int lE = aE.length;
+ int lA = aA.length;
+ int lm = lE < lA ? lA : lE;
+ boolean eofE = false;
+ boolean eofA = false;
+ boolean contextE = true;
+ boolean contextA = true;
+ int numDiff = 0;
+
+ StringBuilder sE = new StringBuilder();
+ StringBuilder sA = new StringBuilder();
+
+ outerLoop: for (int i = 0, iE = 0, iA = 0; i < lm; i++) {
+ if (iE < lE && iA < lA && aE[iE].equals(aA[iA])) {
+ if (numDiff > 0) {
+ // If we found a difference, stop now.
+ break outerLoop;
+ }
+ iE++;
+ iA++;
+ continue;
+ } else {
+ // Try to print some context for each side based on previous lines's space prefix.
+ if (contextE) {
+ if (iE > 0) {
+ String p = diffGetPrefix(aE[iE]);
+ for (int kE = iE-1; kE >= 0; kE--) {
+ if (!aE[kE].startsWith(p)) {
+ sE.insert(0, '\n').insert(0, diffReplaceNs(aE[kE], nsPrefixE)).insert(0, " ");
+ if (p.length() == 0) {
+ break;
+ }
+ p = diffGetPrefix(aE[kE]);
+ } else if (aE[kE].contains(keyAttr) || kE == 0) {
+ sE.insert(0, '\n').insert(0, diffReplaceNs(aE[kE], nsPrefixE)).insert(0, " ");
+ }
+ }
+ }
+ contextE = false;
+ }
+ if (iE >= lE) {
+ if (!eofE) {
+ sE.append("--(end reached)\n");
+ eofE = true;
+ }
+ } else {
+ sE.append("--").append(diffReplaceNs(aE[iE++], nsPrefixE)).append('\n');
+ }
+
+ if (contextA) {
+ if (iA > 0) {
+ String p = diffGetPrefix(aA[iA]);
+ for (int kA = iA-1; kA >= 0; kA--) {
+ if (!aA[kA].startsWith(p)) {
+ sA.insert(0, '\n').insert(0, diffReplaceNs(aA[kA], nsPrefixA)).insert(0, " ");
+ p = diffGetPrefix(aA[kA]);
+ if (p.length() == 0) {
+ break;
+ }
+ } else if (aA[kA].contains(keyAttr) || kA == 0) {
+ sA.insert(0, '\n').insert(0, diffReplaceNs(aA[kA], nsPrefixA)).insert(0, " ");
+ }
+ }
+ }
+ contextA = false;
+ }
+ if (iA >= lA) {
+ if (!eofA) {
+ sA.append("++(end reached)\n");
+ eofA = true;
+ }
+ } else {
+ sA.append("++").append(diffReplaceNs(aA[iA++], nsPrefixA)).append('\n');
+ }
+
+ // Dump up to 3 lines of difference
+ numDiff++;
+ if (numDiff == 3) {
+ break outerLoop;
+ }
+ }
+ }
+
+ sb.append(sE);
+ sb.append(sA);
+ }
+
+ /**
+ * Returns all the whitespace at the beginning of a string.
+ * Implementation details for {@link #printXmlDiff} used to find the "parent"
+ * element and include it in the context of the diff.
+ */
+ private static String diffGetPrefix(String str) {
+ int pos = 0;
+ int len = str.length();
+ while (pos < len && str.charAt(pos) == ' ') {
+ pos++;
+ }
+ return str.substring(0, pos);
+ }
+
+ /**
+ * Simplifies a diff line by replacing NS URIs by their prefix.
+ * Implementation details for {@link #printXmlDiff}.
+ */
+ private static String diffReplaceNs(String str, Map<String, String> nsPrefix) {
+ for (Entry<String, String> entry : nsPrefix.entrySet()) {
+ String uri = entry.getKey();
+ String prefix = entry.getValue();
+ if (prefix != null && str.contains(uri)) {
+ str = str.replaceAll(Pattern.quote(uri), Matcher.quoteReplacement(prefix));
+ }
+ }
+ return str;
+ }
+
+ /**
+ * Returns the file associated with the given specific node, if any.
+ * Note that this will not search upwards for parent nodes; it returns a
+ * file associated with this specific node, if any.
+ */
+ @Nullable
+ public static File getFileFor(@NonNull Node node) {
+ return (File) node.getUserData(DATA_ORIGIN_FILE);
+ }
+
+ /**
+ * Sets the file associated with the given node, if any
+ */
+ public static void setFileFor(Node node, File file) {
+ node.setUserData(MergerXmlUtils.DATA_ORIGIN_FILE, file, null);
+ }
+}
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMergerSourceLinkTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMergerSourceLinkTest.java
new file mode 100755
index 0000000..bf2de43
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMergerSourceLinkTest.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.manifmerger;
+
+import com.android.annotations.NonNull;
+import com.android.manifmerger.IMergerLog.FileAndLine;
+import com.android.sdklib.mock.MockLog;
+
+import junit.framework.TestCase;
+
+import org.w3c.dom.Document;
+
+import java.io.File;
+
+public class ManifestMergerSourceLinkTest extends TestCase {
+ public void testSourceLinks() throws Exception {
+ MockLog log = new MockLog();
+ IMergerLog mergerLog = MergerLog.wrapSdkLog(log);
+ ManifestMerger merger = new ManifestMerger(mergerLog, new ICallback() {
+ @Override
+ public int queryCodenameApiLevel(@NonNull String codename) {
+ if ("ApiCodename1".equals(codename)) {
+ return 1;
+ } else if ("ApiCodename10".equals(codename)) {
+ return 10;
+ }
+ return ICallback.UNKNOWN_CODENAME;
+ }
+ });
+ merger.setInsertSourceMarkers(true);
+
+ Document mainDoc = MergerXmlUtils.parseDocument(""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ + " package=\"com.example.app1\"\n"
+ + " android:versionCode=\"100\"\n"
+ + " android:versionName=\"1.0.0\">\n"
+ + "\n"
+ + " <uses-sdk android:minSdkVersion=\"3\" android:targetSdkVersion=\"11\"/>\n"
+ + "\n"
+ + " <application\n"
+ + " android:name=\"TheApp\"\n"
+ + " android:backupAgent=\".MyBackupAgent\" >\n"
+ + " <activity android:name=\".MainActivity\" />\n"
+ + " <receiver android:name=\"AppReceiver\" />\n"
+ + " <activity android:name=\"com.example.lib2.LibActivity\" />\n"
+ + "\n"
+ + " <!-- This key is defined in the main application. -->\n"
+ + " <meta-data\n"
+ + " android:name=\"name.for.yet.another.api.key\"\n"
+ + " android:value=\"your_yet_another_api_key\"/>\n"
+ + "\n"
+ + " <!-- Merged elements will be appended here at the end. -->\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>",
+ mergerLog, new FileAndLine("main", 1));
+ assertNotNull(mainDoc);
+ Document library1 = MergerXmlUtils.parseDocument(""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.app1\">\n"
+ + "\n"
+ + " <application android:name=\"TheApp\" >\n"
+ + " <activity android:name=\".Library1\" />\n"
+ + "\n"
+ + " <!-- The library maps API key gets merged in the main application. -->\n"
+ + " <meta-data\n"
+ + " android:name=\"name.for.maps.api.key\"\n"
+ + " android:value=\"your_maps_api_key\"/>\n"
+ + "\n"
+ + " <!-- The library backup key gets merged in the main application. -->\n"
+ + " <meta-data\n"
+ + " android:name=\"name.for.backup.api.key\"\n"
+ + " android:value=\"your_backup_api_key\" />\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>",
+ mergerLog, new FileAndLine("library1", 1));
+ assertNotNull(library1);
+ Document library2 = MergerXmlUtils.parseDocument(""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <!-- This comment is ignored. -->\n"
+ + "\n"
+ + " <application android:label=\"@string/lib_name\" >\n"
+ + "\n"
+ + " <!-- The first comment just before the element\n"
+ + " is carried over as-is.\n"
+ + " -->\n"
+ + " <!-- Formatting is preserved. -->\n"
+ + " <!-- All consecutive comments are taken together. -->\n"
+ + "\n"
+ + " <activity-alias\n"
+ + " android:name=\"com.example.alias.MyActivity\"\n"
+ + " android:targetActivity=\"com.example.MainActivity\"\n"
+ + " android:label=\"@string/alias_name\"\n"
+ + " android:icon=\"@drawable/alias_icon\"\n"
+ + " >\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.MAIN\" />\n"
+ + " <category android:name=\"android.intent.category.LAUNCHER\" />\n"
+ + " </intent-filter>\n"
+ + " </activity-alias>\n"
+ + "\n"
+ + " <!-- This is a dup of the 2nd activity in lib2 -->\n"
+ + " <activity\n"
+ + " android:name=\"com.example.LibActivity2\"\n"
+ + " android:label=\"@string/lib_activity_name\"\n"
+ + " android:icon=\"@drawable/lib_activity_icon\"\n"
+ + " android:theme=\"@style/Lib.Theme\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.MAIN\" />\n"
+ + " <category android:name=\"android.intent.category.LAUNCHER\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + "\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>",
+ mergerLog, new FileAndLine("library2", 1));
+ assertNotNull(library2);
+
+ Document library3 = MergerXmlUtils.parseDocument(""
+ + "<manifest\n"
+ + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " package=\"com.example.lib3\">\n"
+ + "\n"
+ + " <application android:label=\"@string/lib_name\" >\n"
+ + " <activity\n"
+ + " android:name=\"com.example.LibActivity3\"\n"
+ + " android:label=\"@string/lib_activity_name3\"\n"
+ + " android:icon=\"@drawable/lib_activity_icon3\"\n"
+ + " android:theme=\"@style/Lib.Theme\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.MAIN\" />\n"
+ + " <category android:name=\"android.intent.category.LAUNCHER\" />\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + "\n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>",
+ mergerLog, new FileAndLine("library3", 1));
+ assertNotNull(library3);
+
+ MergerXmlUtils.setSource(mainDoc, new File("/path/to/main/doc"));
+ MergerXmlUtils.setSource(library1, new File("/path/to/library1"));
+ MergerXmlUtils.setSource(library2, new File("/path/to/library2"));
+ MergerXmlUtils.setSource(library3, new File("/path/to/library3"));
+
+ boolean ok = merger.process(mainDoc, library1, library2, library3);
+ assertTrue(ok);
+ String actual = MergerXmlUtils.printXmlString(mainDoc, mergerLog);
+ assertEquals("Encountered unexpected errors/warnings", "[]", log.toString());
+ String expected = ""
+ + "<!-- From: file:/path/to/main/doc -->\n"
+ + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" android:versionCode=\"100\" android:versionName=\"1.0.0\" package=\"com.example.app1\">\n"
+ + "\n"
+ + " <uses-sdk android:minSdkVersion=\"3\" android:targetSdkVersion=\"11\"/>\n"
+ + "\n"
+ + " <application android:backupAgent=\"com.example.app1.MyBackupAgent\" android:name=\"com.example.app1.TheApp\">\n"
+ + " <activity android:name=\"com.example.app1.MainActivity\"/>\n"
+ + " <receiver android:name=\"com.example.app1.AppReceiver\"/>\n"
+ + " <activity android:name=\"com.example.lib2.LibActivity\"/>\n"
+ + "\n"
+ + " <!-- This key is defined in the main application. -->\n"
+ + " <meta-data android:name=\"name.for.yet.another.api.key\" android:value=\"your_yet_another_api_key\"/>\n"
+ + "\n"
+ + " <!-- Merged elements will be appended here at the end. -->\n"
+ + " <!-- From: file:/path/to/library1 -->\n"
+ + " <activity android:name=\"com.example.app1.Library1\"/>\n"
+ + "\n"
+ + " <!-- The library maps API key gets merged in the main application. -->\n"
+ + " <meta-data android:name=\"name.for.maps.api.key\" android:value=\"your_maps_api_key\"/>\n"
+ + "\n"
+ + " <!-- The library backup key gets merged in the main application. -->\n"
+ + " <meta-data android:name=\"name.for.backup.api.key\" android:value=\"your_backup_api_key\"/>\n"
+ + "\n"
+ + " <!-- From: file:/path/to/library2 -->\n"
+ + " <!-- This is a dup of the 2nd activity in lib2 -->\n"
+ + " <activity android:icon=\"@drawable/lib_activity_icon\" android:label=\"@string/lib_activity_name\" android:name=\"com.example.LibActivity2\" android:theme=\"@style/Lib.Theme\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.MAIN\"/>\n"
+ + " <category android:name=\"android.intent.category.LAUNCHER\"/>\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + "\n"
+ + " <!-- The first comment just before the element\n"
+ + " is carried over as-is.\n"
+ + " -->\n"
+ + " <!-- Formatting is preserved. -->\n"
+ + " <!-- All consecutive comments are taken together. -->\n"
+ + "\n"
+ + " <activity-alias android:icon=\"@drawable/alias_icon\" android:label=\"@string/alias_name\" android:name=\"com.example.alias.MyActivity\" android:targetActivity=\"com.example.MainActivity\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.MAIN\"/>\n"
+ + " <category android:name=\"android.intent.category.LAUNCHER\"/>\n"
+ + " </intent-filter>\n"
+ + " </activity-alias>\n"
+ + " <!-- From: file:/path/to/library3 -->\n"
+ + " <activity android:icon=\"@drawable/lib_activity_icon3\" android:label=\"@string/lib_activity_name3\" android:name=\"com.example.LibActivity3\" android:theme=\"@style/Lib.Theme\">\n"
+ + " <intent-filter>\n"
+ + " <action android:name=\"android.intent.action.MAIN\"/>\n"
+ + " <category android:name=\"android.intent.category.LAUNCHER\"/>\n"
+ + " </intent-filter>\n"
+ + " </activity>\n"
+ + " <!-- From: file:/path/to/main/doc -->\n"
+ + " \n"
+ + " </application>\n"
+ + "\n"
+ + "</manifest>\n";
+
+ if (!expected.equals(actual)) {
+ // DOM implementations vary slightly whether they'll insert a newline for comment
+ // inserted outside document
+ // JDK 7 doesn't, JDK 6 does
+ int index = expected.indexOf('\n');
+ assertTrue(index != -1);
+ expected = expected.substring(0, index) + expected.substring(index + 1);
+ }
+
+ assertEquals(expected, actual);
+ }
+}
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMergerTest.java b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMergerTest.java
new file mode 100755
index 0000000..a8473b2
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/ManifestMergerTest.java
@@ -0,0 +1,633 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.manifmerger;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.manifmerger.IMergerLog.FileAndLine;
+import com.android.sdklib.mock.MockLog;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import org.w3c.dom.Document;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Some utilities to reduce repetitions in the {@link ManifestMergerTest}s.
+ * <p/>
+ * See {@link #loadTestData(String)} for an explanation of the data file format.
+ */
+public class ManifestMergerTest extends TestCase {
+
+ /**
+ * Delimiter that indicates the test must fail.
+ * An XML output and errors are still generated and checked.
+ */
+ private static final String DELIM_FAILS = "fails";
+ /**
+ * Delimiter that starts a library XML content.
+ * The delimiter name must be in the form {@code @libSomeName} and it will be
+ * used as the base for the test file name. Using separate lib names is encouraged
+ * since it makes the error output easier to read.
+ */
+ private static final String DELIM_LIB = "lib";
+ /**
+ * Delimiter that starts the main manifest XML content.
+ */
+ private static final String DELIM_MAIN = "main";
+ /**
+ * Delimiter that starts the resulting XML content, whatever is generated by the merge.
+ */
+ private static final String DELIM_RESULT = "result";
+ /**
+ * Delimiter that starts the SdkLog output.
+ * The logger prints each entry on its lines, prefixed with E for errors,
+ * W for warnings and P for regular printfs.
+ */
+ private static final String DELIM_ERRORS = "errors";
+ /**
+ * Delimiter for starts a section that declares how to inject an attribute.
+ * The section is composed of one or more lines with the
+ * syntax: "/node/node|attr-URI attrName=attrValue".
+ * This is essentially a pseudo XPath-like expression that is described in
+ * {@link ManifestMerger#process(Document, File[], Map, String)}.
+ */
+ private static final String DELIM_INJECT_ATTR = "inject";
+ /**
+ * Delimiter for a section that declares how to toggle a ManifMerger option.
+ * The section is composed of one or more lines with the
+ * syntax: "functionName=false|true".
+ */
+ private static final String DELIM_FEATURES = "features";
+
+ /**
+ * Delimiter for a section that declares how to override the package.
+ * The section is composed of one line containing the new package name.
+ */
+ private static final String DELIM_PACKAGE = "package";
+
+
+ /*
+ * Wait, I hear you, where are the tests?
+ *
+ * processTestFiles() uses loadTestData(), which uses one of the data filename
+ * indicated below.
+ *
+ * We could simplify this even further by dynamically finding the data
+ * files to use; however there's some value in having tests break when out
+ * of sync with the known data file set.
+ */
+ private static String[] sDataFiles = new String[] {
+ "00_noop",
+ "01_ignore_app_attr",
+ "02_ignore_instrumentation",
+ "03_inject_attributes",
+ "04_inject_attributes",
+ "05_inject_package",
+ "10_activity_merge",
+ "11_activity_dup",
+ "12_alias_dup",
+ "13_service_dup",
+ "14_receiver_dup",
+ "15_provider_dup",
+ "16_fqcn_merge",
+ "17_fqcn_conflict",
+ "20_uses_lib_merge",
+ "21_uses_lib_errors",
+ "25_permission_merge",
+ "26_permission_dup",
+ "28_uses_perm_merge",
+ "30_uses_sdk_ok",
+ "32_uses_sdk_minsdk_ok",
+ "33_uses_sdk_minsdk_conflict",
+ "36_uses_sdk_targetsdk_warning",
+ "40_uses_feat_merge",
+ "41_uses_feat_errors",
+ "45_uses_feat_gles_once",
+ "47_uses_feat_gles_conflict",
+ "50_uses_conf_warning",
+ "52_support_screens_warning",
+ "54_compat_screens_warning",
+ "56_support_gltext_warning",
+ "60_merge_order",
+ "65_override_app",
+ "66_remove_app",
+ "67_override_activities",
+ "68_override_uses",
+ "69_remove_uses",
+ "70_expand_fqcns",
+ "71_extract_package_prefix",
+ "75_app_metadata_merge",
+ "76_app_metadata_ignore",
+ "77_app_metadata_conflict",
+ };
+
+ /**
+ * This overrides the default test suite created by junit.
+ * The test suite is a bland TestSuite with a dedicated name.
+ * We inject as many instances of {@link ManifestMergerTest} in the suite
+ * as we have declared data files above.
+ *
+ * @return A new {@link TestSuite}.
+ */
+ public static Test suite() {
+ TestSuite suite = new TestSuite();
+ // Give a non-generic name to our test suite, for better unit reports.
+ suite.setName("ManifestMergerTestSuite");
+
+ for (String fileName : sDataFiles) {
+ suite.addTest(TestSuite.createTest(ManifestMergerTest.class, fileName));
+ }
+
+ return suite;
+ }
+
+
+ /**
+ * Default constructor invoked by {@link TestSuite#createTest(Class, String)}.
+ *
+ * @param testName The test name provided to {@code TestSuite.createTest()}.
+ * This is later accessible via {@link #getName()}.
+ */
+ public ManifestMergerTest(String testName) {
+ super(testName);
+ }
+
+ /**
+ * Invoked by the test framework to run the specific test which name
+ * has been passed to the constructor.
+ * Note that we create one instance of this class per test to run in
+ * the associated {@link TestSuite}.
+ */
+ @Override
+ protected void runTest() throws Throwable {
+ String testName = getName();
+ assertNotNull(testName);
+ processTestFiles(loadTestData(testName));
+ }
+
+
+ static class TestFiles {
+ private final File mMain;
+ private final File[] mLibs;
+ private final Map<String, String> mInjectAttributes;
+ private final String mPackageOverride;
+ private final File mActualResult;
+ private final String mExpectedResult;
+ private final String mExpectedErrors;
+ private final boolean mShouldFail;
+ private final Map<String, Boolean> mFeatures;
+
+ /** Files used by a given test case. */
+ public TestFiles(
+ boolean shouldFail,
+ @NonNull File main,
+ @NonNull File[] libs,
+ @NonNull Map<String, Boolean> features,
+ @NonNull Map<String, String> injectAttributes,
+ @Nullable String packageOverride,
+ @NonNull File actualResult,
+ @NonNull String expectedResult,
+ @NonNull String expectedErrors) {
+ mShouldFail = shouldFail;
+ mMain = main;
+ mLibs = libs;
+ mFeatures = features;
+ mPackageOverride = packageOverride;
+ mInjectAttributes = injectAttributes;
+ mActualResult = actualResult;
+ mExpectedResult = expectedResult;
+ mExpectedErrors = expectedErrors;
+ }
+
+ public boolean getShouldFail() {
+ return mShouldFail;
+ }
+
+ @NonNull
+ public File getMain() {
+ return mMain;
+ }
+
+ @NonNull
+ public File[] getLibs() {
+ return mLibs;
+ }
+
+ @NonNull
+ public Map<String, Boolean> getFeatures() {
+ return mFeatures;
+ }
+
+ @NonNull
+ public Map<String, String> getInjectAttributes() {
+ return mInjectAttributes;
+ }
+
+ @Nullable
+ public String getPackageOverride() {
+ return mPackageOverride;
+ }
+
+ @NonNull
+ public File getActualResult() {
+ return mActualResult;
+ }
+
+ @NonNull
+ public String getExpectedResult() {
+ return mExpectedResult;
+ }
+
+ public String getExpectedErrors() {
+ return mExpectedErrors;
+ }
+
+ // Try to delete any temp file potentially created.
+ public void cleanup() {
+ if (mMain != null && mMain.isFile()) {
+ mMain.delete();
+ }
+
+ if (mActualResult != null && mActualResult.isFile()) {
+ mActualResult.delete();
+ }
+
+ for (File f : mLibs) {
+ if (f != null && f.isFile()) {
+ f.delete();
+ }
+ }
+ }
+ }
+
+ /**
+ * Calls {@link #loadTestData(String)} by
+ * inferring the data filename from the caller's method name.
+ * <p/>
+ * The caller method name must be composed of "test" + the leaf filename.
+ * Extensions ".xml" or ".txt" are implied.
+ * <p/>
+ * E.g. to use the data file "12_foo.xml", simply call this from a method
+ * named "test12_foo".
+ *
+ * @return A new {@link TestFiles} instance. Never null.
+ * @throws Exception when things go wrong.
+ * @see #loadTestData(String)
+ */
+ @NonNull
+ TestFiles loadTestData() throws Exception {
+ StackTraceElement[] stack = Thread.currentThread().getStackTrace();
+ for (int i = 0, n = stack.length; i < n; i++) {
+ StackTraceElement caller = stack[i];
+ String name = caller.getMethodName();
+ if (name.startsWith("test")) {
+ return loadTestData(name.substring(4));
+ }
+ }
+
+ throw new IllegalArgumentException("No caller method found which name started with 'test'");
+ }
+
+ /**
+ * Loads test data for a given test case.
+ * The input (main + libs) are stored in temp files.
+ * A new destination temp file is created to store the actual result output.
+ * The expected result is actually kept in a string.
+ * <p/>
+ * Data File Syntax:
+ * <ul>
+ * <li> Lines starting with # are ignored (anywhere, as long as # is the first char).
+ * <li> Lines before the first {@code @delimiter} are ignored.
+ * <li> Empty lines just after the {@code @delimiter}
+ * and before the first < XML line are ignored.
+ * <li> Valid delimiters are {@code @main} for the XML of the main app manifest.
+ * <li> Following delimiters are {@code @libXYZ}, read in the order of definition.
+ * The name can be anything as long as it starts with "{@code @lib}".
+ * </ul>
+ *
+ * @param filename The test data filename. If no extension is provided, this will
+ * try with .xml or .txt. Must not be null.
+ * @return A new {@link TestFiles} instance. Must not be null.
+ * @throws Exception when things fail to load properly.
+ */
+ @NonNull
+ TestFiles loadTestData(@NonNull String filename) throws Exception {
+
+ String resName = "data" + File.separator + filename;
+ InputStream is = null;
+ BufferedReader reader = null;
+ BufferedWriter writer = null;
+
+ try {
+ is = this.getClass().getResourceAsStream(resName);
+ if (is == null && !filename.endsWith(".xml")) {
+ String resName2 = resName + ".xml";
+ is = this.getClass().getResourceAsStream(resName2);
+ if (is != null) {
+ filename = resName2;
+ }
+ }
+ if (is == null && !filename.endsWith(".txt")) {
+ String resName3 = resName + ".txt";
+ is = this.getClass().getResourceAsStream(resName3);
+ if (is != null) {
+ filename = resName3;
+ }
+ }
+ assertNotNull("Test data file not found for " + filename, is);
+
+ reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
+
+ // Get the temporary directory to use. Just create a temp file, extracts its
+ // directory and remove the file.
+ File tempFile = File.createTempFile(this.getClass().getSimpleName(), ".tmp");
+ File tempDir = tempFile.getParentFile();
+ if (!tempFile.delete()) {
+ tempFile.deleteOnExit();
+ }
+
+ String line = null;
+ String delimiter = null;
+ boolean skipEmpty = true;
+
+ boolean shouldFail = false;
+ Map<String, Boolean> features = new HashMap<String, Boolean>();
+ String packageOverride = null;
+ Map<String, String> injectAttributes = new HashMap<String, String>();
+ StringBuilder expectedResult = new StringBuilder();
+ StringBuilder expectedErrors = new StringBuilder();
+ File mainFile = null;
+ File actualResultFile = null;
+ List<File> libFiles = new ArrayList<File>();
+ int tempIndex = 0;
+
+ while ((line = reader.readLine()) != null) {
+ if (skipEmpty && line.trim().isEmpty()) {
+ continue;
+ }
+ if (!line.isEmpty() && line.charAt(0) == '#') {
+ continue;
+ }
+ if (!line.isEmpty() && line.charAt(0) == '@') {
+ delimiter = line.substring(1);
+ assertTrue(
+ "Unknown delimiter @" + delimiter + " in " + filename,
+ delimiter.startsWith(DELIM_LIB) ||
+ delimiter.equals(DELIM_MAIN) ||
+ delimiter.equals(DELIM_RESULT) ||
+ delimiter.equals(DELIM_ERRORS) ||
+ delimiter.equals(DELIM_FAILS) ||
+ delimiter.equals(DELIM_FEATURES) ||
+ delimiter.equals(DELIM_INJECT_ATTR) ||
+ delimiter.equals(DELIM_PACKAGE));
+
+ skipEmpty = true;
+
+ if (writer != null) {
+ try {
+ writer.close();
+ } catch (IOException ignore) {}
+ writer = null;
+ }
+
+ if (delimiter.equals(DELIM_FAILS)) {
+ shouldFail = true;
+
+ } else if (!delimiter.equals(DELIM_ERRORS) &&
+ !delimiter.equals(DELIM_FEATURES) &&
+ !delimiter.equals(DELIM_INJECT_ATTR) &&
+ !delimiter.equals(DELIM_PACKAGE)) {
+ tempFile = new File(tempDir, String.format("%1$s%2$d_%3$s.xml",
+ this.getClass().getSimpleName(),
+ tempIndex++,
+ delimiter.replaceAll("[^a-zA-Z0-9_-]", "")
+ ));
+ tempFile.deleteOnExit();
+
+ if (delimiter.startsWith(DELIM_LIB)) {
+ libFiles.add(tempFile);
+
+ } else if (delimiter.equals(DELIM_MAIN)) {
+ mainFile = tempFile;
+
+ } else if (delimiter.equals(DELIM_RESULT)) {
+ actualResultFile = tempFile;
+
+ } else {
+ fail("Unexpected data file delimiter @" + delimiter +
+ " in " + filename);
+ }
+
+ if (!delimiter.equals(DELIM_RESULT)) {
+ writer = new BufferedWriter(new FileWriter(tempFile));
+ }
+ }
+
+ continue;
+ }
+ if (delimiter != null &&
+ skipEmpty &&
+ !line.isEmpty() &&
+ line.charAt(0) != '#' &&
+ line.charAt(0) != '@') {
+ skipEmpty = false;
+ }
+ if (writer != null) {
+ writer.write(line);
+ writer.write('\n');
+ } else if (DELIM_RESULT.equals(delimiter)) {
+ expectedResult.append(line).append('\n');
+ } else if (DELIM_ERRORS.equals(delimiter)) {
+ expectedErrors.append(line).append('\n');
+ } else if (DELIM_INJECT_ATTR.equals(delimiter)) {
+ String[] in = line.split("=");
+ if (in != null && in.length == 2) {
+ injectAttributes.put(in[0], "null".equals(in[1]) ? null : in[1]);
+ }
+ } else if (DELIM_FEATURES.equals(delimiter)) {
+ String[] in = line.split("=");
+ if (in != null && in.length == 2) {
+ features.put(in[0], Boolean.parseBoolean(in[1]));
+ }
+ } else if (DELIM_PACKAGE.equals(delimiter)) {
+ if (packageOverride == null) {
+ packageOverride = line;
+ }
+ }
+ }
+
+ assertNotNull("Missing @" + DELIM_MAIN + " in " + filename, mainFile);
+ assertNotNull("Missing @" + DELIM_RESULT + " in " + filename, actualResultFile);
+
+ assert mainFile != null;
+ assert actualResultFile != null;
+
+ Collections.sort(libFiles);
+
+ return new TestFiles(
+ shouldFail,
+ mainFile,
+ libFiles.toArray(new File[libFiles.size()]),
+ features,
+ injectAttributes,
+ packageOverride,
+ actualResultFile,
+ expectedResult.toString(),
+ expectedErrors.toString());
+
+ } catch (UnsupportedEncodingException e) {
+ // BufferedReader failed to decode UTF-8, O'RLY?
+ throw e;
+
+ } finally {
+ if (writer != null) {
+ try {
+ writer.close();
+ } catch (IOException ignore) {}
+ }
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException ignore) {}
+ }
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException ignore) {}
+ }
+ }
+ }
+
+// /**
+// * Loads the data test files using {@link #loadTestData()} and then
+// * invokes {@link #processTestFiles(TestFiles)} to test them.
+// *
+// * @see #loadTestData()
+// * @see #processTestFiles(TestFiles)
+// */
+// void processTestFiles() throws Exception {
+// processTestFiles(loadTestData());
+// }
+
+ /**
+ * Processes the data from the given {@link TestFiles} by
+ * invoking {@link ManifestMerger#process(File, File, File[], Map, String)}:
+ * the given library files are applied consecutively to the main XML
+ * document and the output is generated.
+ * <p/>
+ * Then the expected and actual outputs are loaded into a DOM,
+ * dumped again to a String using an XML transform and compared.
+ * This makes sure only the structure is checked and that any
+ * formatting is ignored in the comparison.
+ *
+ * @param testFiles The test files to process. Must not be null.
+ * @throws Exception when this go wrong.
+ */
+ void processTestFiles(TestFiles testFiles) throws Exception {
+ MockLog log = new MockLog();
+ IMergerLog mergerLog = MergerLog.wrapSdkLog(log);
+ ManifestMerger merger = new ManifestMerger(mergerLog, new ICallback() {
+ @Override
+ public int queryCodenameApiLevel(@NonNull String codename) {
+ if ("ApiCodename1".equals(codename)) {
+ return 1;
+ } else if ("ApiCodename10".equals(codename)) {
+ return 10;
+ }
+ return ICallback.UNKNOWN_CODENAME;
+ }
+ });
+
+ for (Entry<String, Boolean> feature : testFiles.getFeatures().entrySet()) {
+ Method m = merger.getClass().getMethod(
+ feature.getKey(),
+ new Class<?>[] { boolean.class } );
+ m.invoke(merger, new Object[] { feature.getValue() } );
+ }
+
+ boolean processOK = merger.process(testFiles.getActualResult(),
+ testFiles.getMain(),
+ testFiles.getLibs(),
+ testFiles.getInjectAttributes(),
+ testFiles.getPackageOverride());
+
+ // Convert relative pathnames to absolute.
+ String expectedErrors = testFiles.getExpectedErrors().trim();
+ expectedErrors = expectedErrors.replaceAll(testFiles.getMain().getName(),
+ testFiles.getMain().getAbsolutePath());
+ for (File file : testFiles.getLibs()) {
+ expectedErrors = expectedErrors.replaceAll(file.getName(), file.getAbsolutePath());
+ }
+
+ StringBuilder actualErrors = new StringBuilder();
+ for (String s : log.getMessages()) {
+ actualErrors.append(s);
+ if (!s.endsWith("\n")) {
+ actualErrors.append('\n');
+ }
+ }
+ assertEquals("Error generated during merging",
+ expectedErrors, actualErrors.toString().trim());
+
+ if (testFiles.getShouldFail()) {
+ assertFalse("Merge process() returned true, expected false", processOK);
+ } else {
+ assertTrue("Merge process() returned false, expected true", processOK);
+ }
+
+ // Test result XML. There should always be one created
+ // since the process action does not stop on errors.
+ log.clear();
+ Document document = MergerXmlUtils.parseDocument(testFiles.getActualResult(), mergerLog,
+ merger);
+ assertNotNull(document);
+ assert document != null; // for Eclipse null analysis
+ String actual = MergerXmlUtils.printXmlString(document, mergerLog);
+ assertEquals("Error parsing actual result XML", "[]", log.toString());
+ log.clear();
+ document = MergerXmlUtils.parseDocument(
+ testFiles.getExpectedResult(),
+ mergerLog,
+ new FileAndLine("<expected-result>", 0));
+ assertNotNull("Failed to parse result document: " + testFiles.getExpectedResult(),document);
+ assert document != null;
+ String expected = MergerXmlUtils.printXmlString(document, mergerLog);
+ assertEquals("Error parsing expected result XML", "[]", log.toString());
+ assertEquals("Error comparing expected to actual result", expected, actual);
+
+ testFiles.cleanup();
+ }
+
+}
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/00_noop.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/00_noop.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/00_noop.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/00_noop.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/01_ignore_app_attr.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/01_ignore_app_attr.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/01_ignore_app_attr.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/01_ignore_app_attr.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/02_ignore_instrumentation.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/02_ignore_instrumentation.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/02_ignore_instrumentation.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/02_ignore_instrumentation.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/03_inject_attributes.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/03_inject_attributes.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/03_inject_attributes.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/03_inject_attributes.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/04_inject_attributes.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/04_inject_attributes.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/04_inject_attributes.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/04_inject_attributes.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/05_inject_package.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/05_inject_package.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/05_inject_package.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/05_inject_package.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/10_activity_merge.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/10_activity_merge.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/10_activity_merge.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/10_activity_merge.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/11_activity_dup.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/11_activity_dup.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/11_activity_dup.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/11_activity_dup.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/12_alias_dup.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/12_alias_dup.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/12_alias_dup.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/12_alias_dup.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/13_service_dup.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/13_service_dup.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/13_service_dup.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/13_service_dup.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/14_receiver_dup.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/14_receiver_dup.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/14_receiver_dup.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/14_receiver_dup.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/15_provider_dup.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/15_provider_dup.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/15_provider_dup.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/15_provider_dup.xml
diff --git a/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/16_fqcn_merge.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/16_fqcn_merge.xml
new file mode 100755
index 0000000..e5c3b00
--- /dev/null
+++ b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/16_fqcn_merge.xml
@@ -0,0 +1,137 @@
+#
+# Test how FQCN class names are expanded and handled:
+# - A library application can be merged doesn't have an app class name.
+# - A library application can be merged if it has the same class name as the app.
+# - A partial class name is expanded using the package name in a library or app.
+#
+
+@main
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <application
+ android:name="TheApp"
+ android:backupAgent=".MyBackupAgent" >
+ <activity
+ android:name=".MainActivity"
+ android:parentActivityName=".MainParentActivity" />
+ <receiver android:name="AppReceiver" />
+ <activity android:name="com.example.lib2.LibActivity" />
+ </application>
+</manifest>
+
+
+@lib1_widget
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib1">
+
+ <application android:name="com.example.app1.TheApp" >
+ <activity
+ android:name=".WidgetLibrary"
+ android:parentActivityName=".WidgetParentLibrary" />
+ <receiver android:name=".WidgetReceiver" />
+ <service android:name="AppService" />
+ <activity android:name="com.example.lib1.WidgetConfigurationUI" />
+ </application>
+</manifest>
+
+
+@lib2_activity
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib2">
+
+ <application>
+ <!-- This won't be merged because there's already an identical definition in the main. -->
+ <activity android:name="LibActivity" />
+
+ <!-- Provider extracted from ApiDemos -->
+ <provider android:name=".app.LoaderThrottle$SimpleProvider" />
+
+ <!-- This one does not conflict with the main -->
+ <activity android:name="com.example.lib2.LibActivity2" />
+
+ </application>
+</manifest>
+
+
+@lib3_alias
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.lib3" >
+ <!-- This manifest has a 'package' attribute and FQCNs get resolved. -->
+
+ <application
+ android:name="com.example.app1.TheApp"
+ android:backupAgent="com.example.app1.MyBackupAgent">
+ <activity-alias android:name="com.example.lib3.MyActivity"
+ android:targetActivity="com.example.app1.MainActivity" />
+
+ <!-- This is a dup of the 2nd activity in lib2 -->
+ <activity android:name="com.example.lib2.LibActivity2" />
+
+ <!-- These class name should be expanded. -->
+ <activity android:name=".LibActivity3" />
+ <service android:name=".LibService3" />
+ <receiver android:name=".LibReceiver3" />
+ <provider android:name=".LibProvider3" />
+
+ </application>
+
+</manifest>
+
+
+@result
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.app1"
+ android:versionCode="100"
+ android:versionName="1.0.0">
+
+ <application
+ android:name="com.example.app1.TheApp"
+ android:backupAgent="com.example.app1.MyBackupAgent" >
+ <activity
+ android:name="com.example.app1.MainActivity"
+ android:parentActivityName="com.example.app1.MainParentActivity" />
+ <receiver android:name="com.example.app1.AppReceiver" />
+ <activity android:name="com.example.lib2.LibActivity" />
+# from @lib1_widget
+ <activity
+ android:name="com.example.lib1.WidgetLibrary"
+ android:parentActivityName="com.example.lib1.WidgetParentLibrary"/>
+ <activity android:name="com.example.lib1.WidgetConfigurationUI" />
+ <service android:name="com.example.lib1.AppService" />
+ <receiver android:name="com.example.lib1.WidgetReceiver" />
+
+# from @lib2_activity
+ <!-- This one does not conflict with the main -->
+ <activity android:name="com.example.lib2.LibActivity2" />
+
+ <!-- Provider extracted from ApiDemos -->
+ <provider android:name="com.example.lib2.app.LoaderThrottle$SimpleProvider" />
+
+# from @lib3_alias
+ <!-- These class name should be expanded. -->
+ <activity android:name="com.example.lib3.LibActivity3" />
+ <activity-alias android:name="com.example.lib3.MyActivity"
+ android:targetActivity="com.example.app1.MainActivity" />
+ <service android:name="com.example.lib3.LibService3" />
+ <receiver android:name="com.example.lib3.LibReceiver3" />
+ <provider android:name="com.example.lib3.LibProvider3" />
+ </application>
+</manifest>
+
+@errors
+
+P [ManifestMergerTest0_main.xml:6, ManifestMergerTest2_lib2_activity.xml:5] Skipping identical /manifest/application/activity[@name=com.example.lib2.LibActivity] element.
+P [ManifestMergerTest0_main.xml, ManifestMergerTest3_lib3_alias.xml:8] Skipping identical /manifest/application/activity[@name=com.example.lib2.LibActivity2] element.
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/17_fqcn_conflict.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/17_fqcn_conflict.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/17_fqcn_conflict.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/17_fqcn_conflict.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/20_uses_lib_merge.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/20_uses_lib_merge.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/20_uses_lib_merge.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/20_uses_lib_merge.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/21_uses_lib_errors.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/21_uses_lib_errors.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/21_uses_lib_errors.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/21_uses_lib_errors.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/25_permission_merge.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/25_permission_merge.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/25_permission_merge.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/25_permission_merge.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/26_permission_dup.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/26_permission_dup.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/26_permission_dup.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/26_permission_dup.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/28_uses_perm_merge.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/28_uses_perm_merge.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/28_uses_perm_merge.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/28_uses_perm_merge.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/30_uses_sdk_ok.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/30_uses_sdk_ok.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/30_uses_sdk_ok.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/30_uses_sdk_ok.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/32_uses_sdk_minsdk_ok.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/32_uses_sdk_minsdk_ok.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/32_uses_sdk_minsdk_ok.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/32_uses_sdk_minsdk_ok.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/33_uses_sdk_minsdk_conflict.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/33_uses_sdk_minsdk_conflict.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/33_uses_sdk_minsdk_conflict.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/33_uses_sdk_minsdk_conflict.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/36_uses_sdk_targetsdk_warning.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/36_uses_sdk_targetsdk_warning.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/36_uses_sdk_targetsdk_warning.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/36_uses_sdk_targetsdk_warning.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/40_uses_feat_merge.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/40_uses_feat_merge.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/40_uses_feat_merge.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/40_uses_feat_merge.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/41_uses_feat_errors.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/41_uses_feat_errors.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/41_uses_feat_errors.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/41_uses_feat_errors.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/45_uses_feat_gles_once.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/45_uses_feat_gles_once.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/45_uses_feat_gles_once.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/45_uses_feat_gles_once.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/47_uses_feat_gles_conflict.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/47_uses_feat_gles_conflict.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/47_uses_feat_gles_conflict.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/47_uses_feat_gles_conflict.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/50_uses_conf_warning.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/50_uses_conf_warning.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/50_uses_conf_warning.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/50_uses_conf_warning.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/52_support_screens_warning.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/52_support_screens_warning.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/52_support_screens_warning.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/52_support_screens_warning.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/54_compat_screens_warning.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/54_compat_screens_warning.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/54_compat_screens_warning.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/54_compat_screens_warning.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/56_support_gltext_warning.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/56_support_gltext_warning.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/56_support_gltext_warning.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/56_support_gltext_warning.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/60_merge_order.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/60_merge_order.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/60_merge_order.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/60_merge_order.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/65_override_app.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/65_override_app.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/65_override_app.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/65_override_app.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/66_remove_app.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/66_remove_app.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/66_remove_app.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/66_remove_app.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/67_override_activities.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/67_override_activities.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/67_override_activities.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/67_override_activities.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/68_override_uses.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/68_override_uses.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/68_override_uses.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/68_override_uses.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/69_remove_uses.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/69_remove_uses.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/69_remove_uses.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/69_remove_uses.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/70_expand_fqcns.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/70_expand_fqcns.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/70_expand_fqcns.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/70_expand_fqcns.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/71_extract_package_prefix.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/71_extract_package_prefix.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/71_extract_package_prefix.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/71_extract_package_prefix.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/75_app_metadata_merge.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/75_app_metadata_merge.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/75_app_metadata_merge.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/75_app_metadata_merge.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/76_app_metadata_ignore.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/76_app_metadata_ignore.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/76_app_metadata_ignore.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/76_app_metadata_ignore.xml
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/77_app_metadata_conflict.xml b/build-system/manifest-merger/src/test/java/com/android/manifmerger/data/77_app_metadata_conflict.xml
similarity index 100%
rename from manifest-merger/src/test/java/com/android/manifmerger/data/77_app_metadata_conflict.xml
rename to build-system/manifest-merger/src/test/java/com/android/manifmerger/data/77_app_metadata_conflict.xml
diff --git a/build-system/tests/3rdPartyTests/app/build.gradle b/build-system/tests/3rdPartyTests/app/build.gradle
new file mode 100644
index 0000000..9d89e954
--- /dev/null
+++ b/build-system/tests/3rdPartyTests/app/build.gradle
@@ -0,0 +1,36 @@
+apply plugin: 'android'
+
+project.ext.fakeProvider = new com.android.tests.basic.buildscript.FakeProvider()
+project.ext.fakeServer = new com.android.tests.basic.buildscript.FakeServer()
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+ deviceProvider project.ext.fakeProvider
+ testServer project.ext.fakeServer
+}
+
+project.afterEvaluate {
+ configure(fakeInstrumentTest) {
+ doLast {
+ String error = project.ext.fakeProvider.isValid()
+ if (error != null) {
+ throw new GradleException("Failed DeviceProvider usage: " + error)
+ }
+ }
+ }
+
+ configure(fake2Upload) {
+ doLast {
+ String error = project.ext.fakeServer.isValid()
+ if (error != null) {
+ throw new GradleException("Failed TestServer usage: " + error)
+ }
+ }
+ }
+}
+
+dependencies {
+ compile project(':lib')
+}
diff --git a/build-system/tests/3rdPartyTests/app/src/instrumentTest/AndroidManifest.xml b/build-system/tests/3rdPartyTests/app/src/instrumentTest/AndroidManifest.xml
new file mode 100644
index 0000000..5252972
--- /dev/null
+++ b/build-system/tests/3rdPartyTests/app/src/instrumentTest/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.testprojecttest.test"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk android:minSdkVersion="15" />
+
+ <!--
+ We add an application tag here just so that we can indicate that
+ this package needs to link against the android.test library,
+ which is needed when building test cases.
+ -->
+ <application android:label="testProjectTest-testapp">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <!--
+ This declares that this app uses the instrumentation test runner targeting
+ the package of com.android.tests.testprojecttest.app. To run the tests use the command:
+ "adb shell am instrument -w com.android.tests.testprojecttest.test/android.test.InstrumentationTestRunner"
+ -->
+ <instrumentation
+ android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.android.tests.testprojecttest.app" />
+
+</manifest>
\ No newline at end of file
diff --git a/build-system/tests/3rdPartyTests/app/src/instrumentTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java b/build-system/tests/3rdPartyTests/app/src/instrumentTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
new file mode 100644
index 0000000..9be6f97
--- /dev/null
+++ b/build-system/tests/3rdPartyTests/app/src/instrumentTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.testprojecttest.lib;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+import com.android.tests.testprojecttest.app.R;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class LibActivityTest extends ActivityInstrumentationTestCase2<LibActivity> {
+
+ private TextView mTextView;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public LibActivityTest() {
+ super(LibActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final LibActivity a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+ mTextView = (TextView) a.findViewById(R.id.text);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView);
+ }
+}
diff --git a/build-system/tests/3rdPartyTests/app/src/instrumentTest/java/com/android/tests/testprojecttest/test/AllTests.java b/build-system/tests/3rdPartyTests/app/src/instrumentTest/java/com/android/tests/testprojecttest/test/AllTests.java
new file mode 100644
index 0000000..a77b53c
--- /dev/null
+++ b/build-system/tests/3rdPartyTests/app/src/instrumentTest/java/com/android/tests/testprojecttest/test/AllTests.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.testprojecttest.test;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import android.test.suitebuilder.TestSuiteBuilder;
+
+/**
+ * A test suite containing all tests for ApiDemos.
+ *
+ * To run all suites found in this apk:
+ * $ adb shell am instrument -w \
+ * com.example.android.apis.tests/android.test.InstrumentationTestRunner
+ *
+ * To run just this suite from the command line:
+ * $ adb shell am instrument -w \
+ * -e class com.example.android.apis.AllTests \
+ * com.example.android.apis.tests/android.test.InstrumentationTestRunner
+ *
+ * To run an individual test case, e.g. {@link com.example.android.apis.os.MorseCodeConverterTest}:
+ * $ adb shell am instrument -w \
+ * -e class com.example.android.apis.os.MorseCodeConverterTest \
+ * com.example.android.apis.tests/android.test.InstrumentationTestRunner
+ *
+ * To run an individual test, e.g. {@link com.example.android.apis.os.MorseCodeConverterTest#testCharacterS()}:
+ * $ adb shell am instrument -w \
+ * -e class com.example.android.apis.os.MorseCodeConverterTest#testCharacterS \
+ * com.example.android.apis.tests/android.test.InstrumentationTestRunner
+ */
+public class AllTests extends TestSuite {
+
+ public static Test suite() {
+ return new TestSuiteBuilder(AllTests.class)
+ .includeAllPackagesUnderHere()
+ .build();
+ }
+}
diff --git a/build-system/tests/3rdPartyTests/app/src/main/AndroidManifest.xml b/build-system/tests/3rdPartyTests/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..41e6b82
--- /dev/null
+++ b/build-system/tests/3rdPartyTests/app/src/main/AndroidManifest.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.testprojecttest.app"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk android:minSdkVersion="15" />
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/templates/gradle-projects/NewAndroidLibrary/root/res/drawable-hdpi/ic_launcher.png b/build-system/tests/3rdPartyTests/app/src/main/res/drawable-hdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
copy from templates/gradle-projects/NewAndroidLibrary/root/res/drawable-hdpi/ic_launcher.png
copy to build-system/tests/3rdPartyTests/app/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/3rdPartyTests/app/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/tests/3rdPartyTests/app/src/main/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..9923872
--- /dev/null
+++ b/build-system/tests/3rdPartyTests/app/src/main/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/templates/gradle-projects/NewAndroidLibrary/root/res/drawable-mdpi/ic_launcher.png b/build-system/tests/3rdPartyTests/app/src/main/res/drawable-mdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
copy from templates/gradle-projects/NewAndroidLibrary/root/res/drawable-mdpi/ic_launcher.png
copy to build-system/tests/3rdPartyTests/app/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/templates/gradle-projects/NewAndroidLibrary/root/res/drawable-xhdpi/ic_launcher.png b/build-system/tests/3rdPartyTests/app/src/main/res/drawable-xhdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
copy from templates/gradle-projects/NewAndroidLibrary/root/res/drawable-xhdpi/ic_launcher.png
copy to build-system/tests/3rdPartyTests/app/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/3rdPartyTests/app/src/main/res/values/strings.xml b/build-system/tests/3rdPartyTests/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..c933032
--- /dev/null
+++ b/build-system/tests/3rdPartyTests/app/src/main/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="app_name">TestProjectTest-app</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/3rdPartyTests/build.gradle b/build-system/tests/3rdPartyTests/build.gradle
new file mode 100644
index 0000000..83b3e0b
--- /dev/null
+++ b/build-system/tests/3rdPartyTests/build.gradle
@@ -0,0 +1,8 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
diff --git a/build-system/tests/3rdPartyTests/buildSrc/build.gradle b/build-system/tests/3rdPartyTests/buildSrc/build.gradle
new file mode 100644
index 0000000..d0d1b1d
--- /dev/null
+++ b/build-system/tests/3rdPartyTests/buildSrc/build.gradle
@@ -0,0 +1,10 @@
+apply plugin: 'java'
+apply plugin: 'idea'
+
+repositories {
+ maven { url '../../../../../../out/host/gradle/repo' }
+}
+
+dependencies {
+ compile 'com.android.tools.build:builder-test-api:0.7.0-SNAPSHOT'
+}
\ No newline at end of file
diff --git a/build-system/tests/3rdPartyTests/buildSrc/src/main/java/com/android/tests/basic/buildscript/FakeDevice.java b/build-system/tests/3rdPartyTests/buildSrc/src/main/java/com/android/tests/basic/buildscript/FakeDevice.java
new file mode 100644
index 0000000..df810fe
--- /dev/null
+++ b/build-system/tests/3rdPartyTests/buildSrc/src/main/java/com/android/tests/basic/buildscript/FakeDevice.java
@@ -0,0 +1,139 @@
+package com.android.tests.basic.buildscript;
+
+import com.android.annotations.NonNull;
+import com.android.builder.testing.api.DeviceConnector;
+import com.android.builder.testing.api.DeviceException;
+import com.android.ddmlib.AdbCommandRejectedException;
+import com.android.ddmlib.IShellOutputReceiver;
+import com.android.ddmlib.ShellCommandUnresponsiveException;
+import com.android.ddmlib.TimeoutException;
+import com.android.utils.ILogger;
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+
+public class FakeDevice extends DeviceConnector {
+
+ private final String name;
+ private boolean connectCalled = false;
+ private boolean disconnectCalled = false;
+ private boolean installCalled = false;
+ private boolean uninstallCalled = false;
+ private boolean execShellCalled = false;
+
+ private final List<File> installedApks = Lists.newArrayList();
+
+
+ FakeDevice(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public void connect(int timeOut, ILogger logger) throws TimeoutException {
+ logger.info("CONNECT(%S) CALLED", name);
+ connectCalled = true;
+ }
+
+ @Override
+ public void disconnect(int timeOut, ILogger logger) throws TimeoutException {
+ logger.info("DISCONNECTED(%S) CALLED", name);
+ disconnectCalled = true;
+ }
+
+ @Override
+ public void installPackage(@NonNull File apkFile, int timeout, ILogger logger) throws DeviceException {
+ logger.info("INSTALL(%S) CALLED", name);
+
+ if (apkFile == null) {
+ throw new NullPointerException("Null testApk");
+ }
+
+ System.out.println(String.format("\t(%s)ApkFile: %s", name, apkFile.getAbsolutePath()));
+
+ if (!apkFile.isFile()) {
+ throw new RuntimeException("Missing file: " + apkFile.getAbsolutePath());
+ }
+
+ if (!apkFile.getAbsolutePath().endsWith(".apk")) {
+ throw new RuntimeException("Wrong extension: " + apkFile.getAbsolutePath());
+ }
+
+ if (installedApks.contains(apkFile)) {
+ throw new RuntimeException("Already added: " + apkFile.getAbsolutePath());
+ }
+
+ installedApks.add(apkFile);
+
+ installCalled = true;
+ }
+
+ @Override
+ public void uninstallPackage(@NonNull String packageName, int timeout, ILogger logger) throws DeviceException {
+ logger.info("UNINSTALL(%S) CALLED", name);
+ uninstallCalled = true;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public void executeShellCommand(String command, IShellOutputReceiver receiver,
+ long maxTimeToOutputResponse, TimeUnit maxTimeUnits)
+ throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException,
+ IOException {
+ System.out.println(String.format("EXECSHELL(%S) CALLED", name));
+ execShellCalled = true;
+ }
+
+ public String isValid() {
+ if (!connectCalled) {
+ return "connect not called on " + name;
+ }
+
+ if (!disconnectCalled) {
+ return "disconnect not called on " + name;
+ }
+
+ if (!installCalled) {
+ return "installPackage not called on " + name;
+ }
+
+ if (!uninstallCalled) {
+ return "uninstallPackage not called on " + name;
+ }
+
+ if (!execShellCalled) {
+ return "executeShellCommand not called on " + name;
+ }
+
+ return null;
+ }
+
+ public int getApiLevel() {
+ return 99;
+ }
+
+ @NonNull
+ public List<String> getAbis() {
+ return Collections.singletonList("fake");
+ }
+
+ public int getDensity() {
+ return 160;
+ }
+
+ public int getHeight() {
+ return 800;
+ }
+
+ public int getWidth() {
+ return 480;
+ }
+}
diff --git a/build-system/tests/3rdPartyTests/buildSrc/src/main/java/com/android/tests/basic/buildscript/FakeProvider.java b/build-system/tests/3rdPartyTests/buildSrc/src/main/java/com/android/tests/basic/buildscript/FakeProvider.java
new file mode 100644
index 0000000..acd7228
--- /dev/null
+++ b/build-system/tests/3rdPartyTests/buildSrc/src/main/java/com/android/tests/basic/buildscript/FakeProvider.java
@@ -0,0 +1,70 @@
+package com.android.tests.basic.buildscript;
+
+import com.android.builder.testing.api.DeviceConnector;
+import com.android.builder.testing.api.DeviceException;
+import com.android.builder.testing.api.DeviceProvider;
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+public class FakeProvider extends DeviceProvider {
+
+ private boolean initCalled = false;
+ private boolean terminateCalled = false;
+ private List<FakeDevice> devices = Lists.newArrayList();
+
+ @Override
+ public String getName() {
+ return "fake";
+ }
+
+ @Override
+ public List<? extends DeviceConnector> getDevices() {
+ return devices;
+ }
+
+ @Override
+ public void init() throws DeviceException {
+ System.out.println("INIT CALLED");
+ initCalled = true;
+
+ devices.add(new FakeDevice("device1"));
+ devices.add(new FakeDevice("device2"));
+ }
+
+ @Override
+ public void terminate() throws DeviceException {
+ System.out.println("TERMINATE CALLED");
+ terminateCalled = true;
+ }
+
+ @Override
+ public int getTimeout() {
+ return 0;
+ }
+
+ public String isValid() {
+ if (!initCalled) {
+ return "init not called";
+ }
+
+ if (!terminateCalled) {
+ return "terminate not called";
+ }
+
+ for (FakeDevice device : devices) {
+ String error = device.isValid();
+ if (error != null) {
+ return error;
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean isConfigured() {
+ return true;
+ }
+
+}
\ No newline at end of file
diff --git a/build-system/tests/3rdPartyTests/buildSrc/src/main/java/com/android/tests/basic/buildscript/FakeServer.java b/build-system/tests/3rdPartyTests/buildSrc/src/main/java/com/android/tests/basic/buildscript/FakeServer.java
new file mode 100644
index 0000000..d1d7aeb
--- /dev/null
+++ b/build-system/tests/3rdPartyTests/buildSrc/src/main/java/com/android/tests/basic/buildscript/FakeServer.java
@@ -0,0 +1,70 @@
+package com.android.tests.basic.buildscript;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.builder.testing.api.TestServer;
+
+import java.io.File;
+
+public class FakeServer extends TestServer {
+
+ private boolean uploadCalled = false;
+
+ @Override
+ public String getName() {
+ return "fake2";
+ }
+
+ @Override
+ public void uploadApks(@NonNull String variantName,
+ @NonNull File testApk,
+ @Nullable File testedApk) {
+ System.out.println("uploadApks CALLED");
+
+ if (testApk == null) {
+ throw new NullPointerException("Null testApk");
+ }
+
+ if (!testApk.isFile()) {
+ throw new RuntimeException("Missing file: " + testApk.getAbsolutePath());
+ }
+
+ if (!testApk.getAbsolutePath().endsWith(".apk")) {
+ throw new RuntimeException("Wrong extension: " + testApk.getAbsolutePath());
+ }
+
+ System.out.println("\ttestApk: " + testApk.getAbsolutePath());
+
+ if (testedApk != null) {
+ if (!testedApk.isFile()) {
+ throw new RuntimeException("Missing file: " + testedApk.getAbsolutePath());
+ }
+
+ if (!testedApk.getAbsolutePath().endsWith(".apk")) {
+ throw new RuntimeException("Wrong extension: " + testedApk.getAbsolutePath());
+ }
+
+ System.out.println("\ttestedApk: " + testedApk.getAbsolutePath());
+
+ if (testApk.equals(testedApk)) {
+ throw new RuntimeException("Both APKs are the same!");
+ }
+ }
+
+ uploadCalled = true;
+ }
+
+ public String isValid() {
+ if (!uploadCalled) {
+ return "uploadApks not called";
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean isConfigured() {
+ return true;
+ }
+
+}
diff --git a/build-system/tests/3rdPartyTests/lib/build.gradle b/build-system/tests/3rdPartyTests/lib/build.gradle
new file mode 100644
index 0000000..75ef7d6
--- /dev/null
+++ b/build-system/tests/3rdPartyTests/lib/build.gradle
@@ -0,0 +1,32 @@
+apply plugin: 'android-library'
+
+project.ext.fakeProvider = new com.android.tests.basic.buildscript.FakeProvider()
+project.ext.fakeServer = new com.android.tests.basic.buildscript.FakeServer()
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+ deviceProvider project.ext.fakeProvider
+ testServer project.ext.fakeServer
+}
+
+project.afterEvaluate {
+ configure(fakeInstrumentTest) {
+ doLast {
+ String error = project.ext.fakeProvider.isValid()
+ if (error != null) {
+ throw new GradleException("Failed DeviceProvider usage: " + error)
+ }
+ }
+ }
+
+ configure(fake2Upload) {
+ doLast {
+ String error = project.ext.fakeServer.isValid()
+ if (error != null) {
+ throw new GradleException("Failed TestServer usage: " + error)
+ }
+ }
+ }
+}
diff --git a/build-system/tests/3rdPartyTests/lib/src/instrumentTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java b/build-system/tests/3rdPartyTests/lib/src/instrumentTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
new file mode 100644
index 0000000..6632c58
--- /dev/null
+++ b/build-system/tests/3rdPartyTests/lib/src/instrumentTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.testprojecttest.lib;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+import com.android.tests.testprojecttest.lib.R;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class LibActivityTest extends ActivityInstrumentationTestCase2<LibActivity> {
+
+ private TextView mTextView;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public LibActivityTest() {
+ super(LibActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final LibActivity a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+ mTextView = (TextView) a.findViewById(R.id.text);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView);
+ }
+}
diff --git a/build-system/tests/3rdPartyTests/lib/src/instrumentTest/java/com/android/tests/testprojecttest/test/AllTests.java b/build-system/tests/3rdPartyTests/lib/src/instrumentTest/java/com/android/tests/testprojecttest/test/AllTests.java
new file mode 100644
index 0000000..a77b53c
--- /dev/null
+++ b/build-system/tests/3rdPartyTests/lib/src/instrumentTest/java/com/android/tests/testprojecttest/test/AllTests.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.testprojecttest.test;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import android.test.suitebuilder.TestSuiteBuilder;
+
+/**
+ * A test suite containing all tests for ApiDemos.
+ *
+ * To run all suites found in this apk:
+ * $ adb shell am instrument -w \
+ * com.example.android.apis.tests/android.test.InstrumentationTestRunner
+ *
+ * To run just this suite from the command line:
+ * $ adb shell am instrument -w \
+ * -e class com.example.android.apis.AllTests \
+ * com.example.android.apis.tests/android.test.InstrumentationTestRunner
+ *
+ * To run an individual test case, e.g. {@link com.example.android.apis.os.MorseCodeConverterTest}:
+ * $ adb shell am instrument -w \
+ * -e class com.example.android.apis.os.MorseCodeConverterTest \
+ * com.example.android.apis.tests/android.test.InstrumentationTestRunner
+ *
+ * To run an individual test, e.g. {@link com.example.android.apis.os.MorseCodeConverterTest#testCharacterS()}:
+ * $ adb shell am instrument -w \
+ * -e class com.example.android.apis.os.MorseCodeConverterTest#testCharacterS \
+ * com.example.android.apis.tests/android.test.InstrumentationTestRunner
+ */
+public class AllTests extends TestSuite {
+
+ public static Test suite() {
+ return new TestSuiteBuilder(AllTests.class)
+ .includeAllPackagesUnderHere()
+ .build();
+ }
+}
diff --git a/build-system/tests/3rdPartyTests/lib/src/instrumentTest/res/values/strings.xml b/build-system/tests/3rdPartyTests/lib/src/instrumentTest/res/values/strings.xml
new file mode 100644
index 0000000..17781ee
--- /dev/null
+++ b/build-system/tests/3rdPartyTests/lib/src/instrumentTest/res/values/strings.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="ahoo">ahoo!</string>
+ <string name="hello">Hello World!</string>
+ <string name="app_name">TestProjectTest-testTest</string>
+ <string name="foo">foo!</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/3rdPartyTests/lib/src/main/AndroidManifest.xml b/build-system/tests/3rdPartyTests/lib/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..26598f0
--- /dev/null
+++ b/build-system/tests/3rdPartyTests/lib/src/main/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.testprojecttest.lib">
+ <application>
+ <activity
+ android:name="com.android.tests.testprojecttest.lib.LibActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <permission-group android:name="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+ <permission android:name="foo.permission.SEND_SMS"
+ android:permissionGroup="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+ <permission android:name="foo.blah.SEND_SMS"
+ android:permissionGroup="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+
+</manifest>
\ No newline at end of file
diff --git a/build-system/tests/3rdPartyTests/lib/src/main/aidl/com/android/tests/basicprojectwithaidl/ITest.aidl b/build-system/tests/3rdPartyTests/lib/src/main/aidl/com/android/tests/basicprojectwithaidl/ITest.aidl
new file mode 100644
index 0000000..9b81031
--- /dev/null
+++ b/build-system/tests/3rdPartyTests/lib/src/main/aidl/com/android/tests/basicprojectwithaidl/ITest.aidl
@@ -0,0 +1,7 @@
+package com.android.tests.basicprojectwithaidl;
+
+interface ITest {
+ Rect getRect();
+ int getInt();
+}
+
diff --git a/build-system/tests/3rdPartyTests/lib/src/main/aidl/com/android/tests/basicprojectwithaidl/Rect.aidl b/build-system/tests/3rdPartyTests/lib/src/main/aidl/com/android/tests/basicprojectwithaidl/Rect.aidl
new file mode 100644
index 0000000..734cf77
--- /dev/null
+++ b/build-system/tests/3rdPartyTests/lib/src/main/aidl/com/android/tests/basicprojectwithaidl/Rect.aidl
@@ -0,0 +1,5 @@
+package com.android.tests.basicprojectwithaidl;
+
+// Declare Rect so AIDL can find it and knows that it implements
+// the parcelable protocol.
+parcelable Rect;
\ No newline at end of file
diff --git a/build-system/tests/3rdPartyTests/lib/src/main/java/com/android/tests/testprojecttest/lib/LibActivity.java b/build-system/tests/3rdPartyTests/lib/src/main/java/com/android/tests/testprojecttest/lib/LibActivity.java
new file mode 100644
index 0000000..7d7f607
--- /dev/null
+++ b/build-system/tests/3rdPartyTests/lib/src/main/java/com/android/tests/testprojecttest/lib/LibActivity.java
@@ -0,0 +1,13 @@
+package com.android.tests.testprojecttest.lib;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class LibActivity extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/3rdPartyTests/lib/src/main/res/layout/main.xml b/build-system/tests/3rdPartyTests/lib/src/main/res/layout/main.xml
new file mode 100644
index 0000000..14a9c4b
--- /dev/null
+++ b/build-system/tests/3rdPartyTests/lib/src/main/res/layout/main.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="some string"
+ tools:ignore="HardcodedText" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/build-system/tests/3rdPartyTests/lib/src/main/res/values/strings.xml b/build-system/tests/3rdPartyTests/lib/src/main/res/values/strings.xml
new file mode 100644
index 0000000..fdb2272
--- /dev/null
+++ b/build-system/tests/3rdPartyTests/lib/src/main/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="app_name">TestProjectTest-lib</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/3rdPartyTests/settings.gradle b/build-system/tests/3rdPartyTests/settings.gradle
new file mode 100644
index 0000000..5ed7972
--- /dev/null
+++ b/build-system/tests/3rdPartyTests/settings.gradle
@@ -0,0 +1,2 @@
+include 'app'
+include 'lib'
\ No newline at end of file
diff --git a/build-system/tests/aidl/build.gradle b/build-system/tests/aidl/build.gradle
new file mode 100644
index 0000000..337d40fd
--- /dev/null
+++ b/build-system/tests/aidl/build.gradle
@@ -0,0 +1,15 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+apply plugin: 'android'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+}
\ No newline at end of file
diff --git a/build-system/tests/aidl/src/main/AndroidManifest.xml b/build-system/tests/aidl/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..1d6740d
--- /dev/null
+++ b/build-system/tests/aidl/src/main/AndroidManifest.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ android:versionCode="1"
+ android:versionName="1.0" package="com.android.tests.basicprojectwithaidl">
+ <application android:label="@string/app_name" android:icon="@drawable/icon">
+ <activity android:name="com.android.tests.basicprojectwithaidlwithaidl.Main"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/build-system/tests/aidl/src/main/aidl/com/android/tests/basicprojectwithaidl/ITest.aidl b/build-system/tests/aidl/src/main/aidl/com/android/tests/basicprojectwithaidl/ITest.aidl
new file mode 100644
index 0000000..9b81031
--- /dev/null
+++ b/build-system/tests/aidl/src/main/aidl/com/android/tests/basicprojectwithaidl/ITest.aidl
@@ -0,0 +1,7 @@
+package com.android.tests.basicprojectwithaidl;
+
+interface ITest {
+ Rect getRect();
+ int getInt();
+}
+
diff --git a/build-system/tests/aidl/src/main/aidl/com/android/tests/basicprojectwithaidl/Rect.aidl b/build-system/tests/aidl/src/main/aidl/com/android/tests/basicprojectwithaidl/Rect.aidl
new file mode 100644
index 0000000..734cf77
--- /dev/null
+++ b/build-system/tests/aidl/src/main/aidl/com/android/tests/basicprojectwithaidl/Rect.aidl
@@ -0,0 +1,5 @@
+package com.android.tests.basicprojectwithaidl;
+
+// Declare Rect so AIDL can find it and knows that it implements
+// the parcelable protocol.
+parcelable Rect;
\ No newline at end of file
diff --git a/build-system/tests/aidl/src/main/java/com/android/tests/basicprojectwithaidl/Main.java b/build-system/tests/aidl/src/main/java/com/android/tests/basicprojectwithaidl/Main.java
new file mode 100644
index 0000000..eaed510
--- /dev/null
+++ b/build-system/tests/aidl/src/main/java/com/android/tests/basicprojectwithaidl/Main.java
@@ -0,0 +1,15 @@
+package com.android.tests.basicprojectwithaidl;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class Main extends Activity
+{
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ }
+}
diff --git a/build-system/tests/aidl/src/main/java/com/android/tests/basicprojectwithaidl/Rect.java b/build-system/tests/aidl/src/main/java/com/android/tests/basicprojectwithaidl/Rect.java
new file mode 100644
index 0000000..8e16926
--- /dev/null
+++ b/build-system/tests/aidl/src/main/java/com/android/tests/basicprojectwithaidl/Rect.java
@@ -0,0 +1,52 @@
+package com.android.tests.basicprojectwithaidl;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class Rect implements Parcelable {
+ public int left;
+ public int top;
+ public int right;
+ public int bottom;
+
+ public static final Parcelable.Creator<Rect> CREATOR = new Parcelable.Creator<Rect>() {
+ public Rect createFromParcel(Parcel in) {
+ return new Rect(in);
+ }
+
+ public Rect[] newArray(int size) {
+ return new Rect[size];
+ }
+ };
+
+ public Rect() {
+ }
+
+ private Rect(Parcel in) {
+ readFromParcel(in);
+ }
+
+ public void writeToParcel(Parcel out) {
+ out.writeInt(left);
+ out.writeInt(top);
+ out.writeInt(right);
+ out.writeInt(bottom);
+ }
+
+ public void readFromParcel(Parcel in) {
+ left = in.readInt();
+ top = in.readInt();
+ right = in.readInt();
+ bottom = in.readInt();
+ }
+
+ public int describeContents() {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ public void writeToParcel(Parcel arg0, int arg1) {
+ // TODO Auto-generated method stub
+
+ }
+}
diff --git a/build-system/tests/aidl/src/main/res/drawable/icon.png b/build-system/tests/aidl/src/main/res/drawable/icon.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/aidl/src/main/res/drawable/icon.png
Binary files differ
diff --git a/build-system/tests/aidl/src/main/res/layout/main.xml b/build-system/tests/aidl/src/main/res/layout/main.xml
new file mode 100644
index 0000000..783e4a0
--- /dev/null
+++ b/build-system/tests/aidl/src/main/res/layout/main.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+<TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="Basic Project"
+ />
+</LinearLayout>
+
diff --git a/build-system/tests/aidl/src/main/res/values/strings.xml b/build-system/tests/aidl/src/main/res/values/strings.xml
new file mode 100644
index 0000000..a7322d3
--- /dev/null
+++ b/build-system/tests/aidl/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">basicProject</string>
+</resources>
diff --git a/build-system/tests/api/app/build.gradle b/build-system/tests/api/app/build.gradle
new file mode 100644
index 0000000..c307fc8
--- /dev/null
+++ b/build-system/tests/api/app/build.gradle
@@ -0,0 +1,35 @@
+apply plugin: 'android'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
+
+// query for all (non-test) variants and inject a new step in the builds
+android.applicationVariants.all { variant ->
+ // create a task that "handles" the compile classes
+ // does some processing (or not)
+ // and outputs a jar
+ def jarTask = tasks.create(name: "jar${variant.name.capitalize()}", type: Jar) {
+ from variant.javaCompile.destinationDir
+ destinationDir file("${buildDir}/jars/${variant.dirName}")
+ baseName "classes"
+ }
+
+ // this task depends on the compilation task
+ jarTask.dependsOn variant.javaCompile
+
+ // now make the dex task depend on it and use its output
+ variant.dex.dependsOn jarTask
+ variant.dex.sourceFiles = files(jarTask.archivePath).files
+}
+
+project.afterEvaluate {
+ if (android.applicationVariants.size() != 2) {
+ throw new GradleException("Wrong number of app variants!")
+ }
+
+ if (android.testVariants.size() != 1) {
+ throw new GradleException("Wrong number of test variants!")
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/api/app/src/instrumentTest/java/com/android/tests/basic/MainTest.java b/build-system/tests/api/app/src/instrumentTest/java/com/android/tests/basic/MainTest.java
new file mode 100644
index 0000000..7cf7329
--- /dev/null
+++ b/build-system/tests/api/app/src/instrumentTest/java/com/android/tests/basic/MainTest.java
@@ -0,0 +1,38 @@
+package com.android.tests.basic;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+public class MainTest extends ActivityInstrumentationTestCase2<Main> {
+
+ private TextView mTextView;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
+ */
+ public MainTest() {
+ super(Main.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final Main a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+ mTextView = (TextView) a.findViewById(R.id.text);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView);
+ }
+}
+
diff --git a/build-system/tests/api/app/src/main/AndroidManifest.xml b/build-system/tests/api/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..4f8d570
--- /dev/null
+++ b/build-system/tests/api/app/src/main/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.basic">
+ <application android:label="@string/app_name" android:icon="@drawable/icon">
+ <activity android:name=".Main"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <uses-permission android:name="com.blah" />
+
+ <permission-group android:name="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+ <permission android:name="foo.permission.SEND_SMS"
+ android:permissionGroup="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+ <permission android:name="foo.blah.SEND_SMS"
+ android:permissionGroup="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+</manifest>
diff --git a/build-system/tests/api/app/src/main/java/com/android/tests/basic/Main.java b/build-system/tests/api/app/src/main/java/com/android/tests/basic/Main.java
new file mode 100644
index 0000000..2b0e698
--- /dev/null
+++ b/build-system/tests/api/app/src/main/java/com/android/tests/basic/Main.java
@@ -0,0 +1,15 @@
+package com.android.tests.basic;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class Main extends Activity
+{
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ }
+}
diff --git a/build-system/tests/api/app/src/main/res/drawable/icon.png b/build-system/tests/api/app/src/main/res/drawable/icon.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/api/app/src/main/res/drawable/icon.png
Binary files differ
diff --git a/build-system/tests/api/app/src/main/res/layout/main.xml b/build-system/tests/api/app/src/main/res/layout/main.xml
new file mode 100644
index 0000000..b199751
--- /dev/null
+++ b/build-system/tests/api/app/src/main/res/layout/main.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+<TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="Test App - Basic"
+ android:id="@+id/text"
+ />
+</LinearLayout>
+
diff --git a/build-system/tests/api/app/src/main/res/values/strings.xml b/build-system/tests/api/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..60ea2d0
--- /dev/null
+++ b/build-system/tests/api/app/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">_Test-Basic</string>
+</resources>
diff --git a/build-system/tests/api/app/src/release/res/values/strings.xml b/build-system/tests/api/app/src/release/res/values/strings.xml
new file mode 100644
index 0000000..532909c
--- /dev/null
+++ b/build-system/tests/api/app/src/release/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">_Test-Basic-Release</string>
+</resources>
diff --git a/build-system/tests/api/build.gradle b/build-system/tests/api/build.gradle
new file mode 100644
index 0000000..83b3e0b
--- /dev/null
+++ b/build-system/tests/api/build.gradle
@@ -0,0 +1,8 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
diff --git a/build-system/tests/api/lib/blah/foo.txt b/build-system/tests/api/lib/blah/foo.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/build-system/tests/api/lib/blah/foo.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/build-system/tests/api/lib/build.gradle b/build-system/tests/api/lib/build.gradle
new file mode 100644
index 0000000..b2cbda8
--- /dev/null
+++ b/build-system/tests/api/lib/build.gradle
@@ -0,0 +1,24 @@
+apply plugin: 'android-library'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
+
+// query for all (non-test) variants and inject a new step in the builds
+android.libraryVariants.all { variant ->
+ // create a task that copies some additional data in the library bundle
+ def copyBlahTask = tasks.create(name: "copy${variant.name.capitalize()}Blah", type: Copy) {
+ from file("$project.projectDir/blah")
+ destinationDir file("${buildDir}/bundles/${variant.dirName}")
+ }
+
+ // now make the package task depend on it
+ variant.packageLibrary.dependsOn copyBlahTask
+}
+
+project.afterEvaluate {
+ if (android.libraryVariants.size() != 2) {
+ throw new GradleException("Wrong number of app variants!")
+ }
+}
diff --git a/build-system/tests/api/lib/src/instrumentTest/java/com/android/tests/libstest/lib2/MainActivityTest.java b/build-system/tests/api/lib/src/instrumentTest/java/com/android/tests/libstest/lib2/MainActivityTest.java
new file mode 100644
index 0000000..6ac4a5c
--- /dev/null
+++ b/build-system/tests/api/lib/src/instrumentTest/java/com/android/tests/libstest/lib2/MainActivityTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.libstest.lib2;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+import com.android.tests.libstest.lib2.R;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private TextView mTextView1;
+ private TextView mTextView2;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public MainActivityTest() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final MainActivity a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ mTextView1 = (TextView) a.findViewById(R.id.lib2_text1);
+ mTextView2 = (TextView) a.findViewById(R.id.lib2_text2);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView1);
+ assertNotNull(mTextView2);
+ }
+
+ @MediumTest
+ public void testAndroidStrings() {
+ assertEquals("SUCCESS-LIB2", mTextView1.getText());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB2", mTextView2.getText());
+ }
+}
diff --git a/build-system/tests/api/lib/src/main/AndroidManifest.xml b/build-system/tests/api/lib/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..9374b53
--- /dev/null
+++ b/build-system/tests/api/lib/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.libstest.lib2"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/lib2_name" >
+ <activity
+ android:name="MainActivity"
+ android:label="@string/lib2_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/build-system/tests/api/lib/src/main/java/com/android/tests/libstest/lib2/Lib2.java b/build-system/tests/api/lib/src/main/java/com/android/tests/libstest/lib2/Lib2.java
new file mode 100644
index 0000000..bb8e4db
--- /dev/null
+++ b/build-system/tests/api/lib/src/main/java/com/android/tests/libstest/lib2/Lib2.java
@@ -0,0 +1,43 @@
+package com.android.tests.libstest.lib2;
+
+import android.app.Activity;
+import android.widget.TextView;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+public class Lib2 {
+
+ public static void handleTextView(Activity a) {
+ TextView tv = (TextView) a.findViewById(R.id.lib2_text2);
+ if (tv != null) {
+ tv.setText(getContent());
+ }
+ }
+
+ private static String getContent() {
+ InputStream input = Lib2.class.getResourceAsStream("Lib2.txt");
+ if (input == null) {
+ return "FAILED TO FIND Lib2.txt";
+ }
+
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+
+ return reader.readLine();
+ } catch (IOException e) {
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ return "FAILED TO READ CONTENT";
+ }
+}
diff --git a/build-system/tests/api/lib/src/main/java/com/android/tests/libstest/lib2/MainActivity.java b/build-system/tests/api/lib/src/main/java/com/android/tests/libstest/lib2/MainActivity.java
new file mode 100644
index 0000000..012f203
--- /dev/null
+++ b/build-system/tests/api/lib/src/main/java/com/android/tests/libstest/lib2/MainActivity.java
@@ -0,0 +1,15 @@
+package com.android.tests.libstest.lib2;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class MainActivity extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.lib2_main);
+
+ Lib2.handleTextView(this);
+ }
+}
diff --git a/build-system/tests/api/lib/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/tests/api/lib/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..8074c4c
--- /dev/null
+++ b/build-system/tests/api/lib/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/api/lib/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/tests/api/lib/src/main/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..1095584
--- /dev/null
+++ b/build-system/tests/api/lib/src/main/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/api/lib/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/tests/api/lib/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/api/lib/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/api/lib/src/main/res/layout/lib2_main.xml b/build-system/tests/api/lib/src/main/res/layout/lib2_main.xml
new file mode 100644
index 0000000..bb639d1
--- /dev/null
+++ b/build-system/tests/api/lib/src/main/res/layout/lib2_main.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/lib2_text1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/lib2_string" />
+
+ <TextView
+ android:id="@+id/lib2_text2"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/build-system/tests/api/lib/src/main/res/values/strings.xml b/build-system/tests/api/lib/src/main/res/values/strings.xml
new file mode 100644
index 0000000..215b8fa
--- /dev/null
+++ b/build-system/tests/api/lib/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="lib2_name">LibsTest-lib2</string>
+ <string name="lib2_string">SUCCESS-LIB2</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/api/lib/src/main/resources/com/android/tests/libstest/lib2/Lib2.txt b/build-system/tests/api/lib/src/main/resources/com/android/tests/libstest/lib2/Lib2.txt
new file mode 100644
index 0000000..94cabe4
--- /dev/null
+++ b/build-system/tests/api/lib/src/main/resources/com/android/tests/libstest/lib2/Lib2.txt
@@ -0,0 +1 @@
+SUCCESS-LIB2
\ No newline at end of file
diff --git a/build-system/tests/api/settings.gradle b/build-system/tests/api/settings.gradle
new file mode 100644
index 0000000..eedb2a1
--- /dev/null
+++ b/build-system/tests/api/settings.gradle
@@ -0,0 +1,2 @@
+include 'app'
+include 'lib'
diff --git a/build-system/tests/applibtest/app/build.gradle b/build-system/tests/applibtest/app/build.gradle
new file mode 100644
index 0000000..39e5199
--- /dev/null
+++ b/build-system/tests/applibtest/app/build.gradle
@@ -0,0 +1,13 @@
+apply plugin: 'android'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
+
+//
+// A basic Android application split over a library and a main project.
+//
+dependencies {
+ compile project(':lib')
+}
diff --git a/build-system/tests/applibtest/app/proguard-project.txt b/build-system/tests/applibtest/app/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/build-system/tests/applibtest/app/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/build-system/tests/applibtest/app/src/instrumentTest/AndroidManifest.xml b/build-system/tests/applibtest/app/src/instrumentTest/AndroidManifest.xml
new file mode 100644
index 0000000..5252972
--- /dev/null
+++ b/build-system/tests/applibtest/app/src/instrumentTest/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.testprojecttest.test"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk android:minSdkVersion="15" />
+
+ <!--
+ We add an application tag here just so that we can indicate that
+ this package needs to link against the android.test library,
+ which is needed when building test cases.
+ -->
+ <application android:label="testProjectTest-testapp">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <!--
+ This declares that this app uses the instrumentation test runner targeting
+ the package of com.android.tests.testprojecttest.app. To run the tests use the command:
+ "adb shell am instrument -w com.android.tests.testprojecttest.test/android.test.InstrumentationTestRunner"
+ -->
+ <instrumentation
+ android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.android.tests.testprojecttest.app" />
+
+</manifest>
\ No newline at end of file
diff --git a/build-system/tests/applibtest/app/src/instrumentTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java b/build-system/tests/applibtest/app/src/instrumentTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
new file mode 100644
index 0000000..9be6f97
--- /dev/null
+++ b/build-system/tests/applibtest/app/src/instrumentTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.testprojecttest.lib;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+import com.android.tests.testprojecttest.app.R;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class LibActivityTest extends ActivityInstrumentationTestCase2<LibActivity> {
+
+ private TextView mTextView;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public LibActivityTest() {
+ super(LibActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final LibActivity a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+ mTextView = (TextView) a.findViewById(R.id.text);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView);
+ }
+}
diff --git a/build-system/tests/applibtest/app/src/instrumentTest/java/com/android/tests/testprojecttest/test/AllTests.java b/build-system/tests/applibtest/app/src/instrumentTest/java/com/android/tests/testprojecttest/test/AllTests.java
new file mode 100644
index 0000000..a77b53c
--- /dev/null
+++ b/build-system/tests/applibtest/app/src/instrumentTest/java/com/android/tests/testprojecttest/test/AllTests.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.testprojecttest.test;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import android.test.suitebuilder.TestSuiteBuilder;
+
+/**
+ * A test suite containing all tests for ApiDemos.
+ *
+ * To run all suites found in this apk:
+ * $ adb shell am instrument -w \
+ * com.example.android.apis.tests/android.test.InstrumentationTestRunner
+ *
+ * To run just this suite from the command line:
+ * $ adb shell am instrument -w \
+ * -e class com.example.android.apis.AllTests \
+ * com.example.android.apis.tests/android.test.InstrumentationTestRunner
+ *
+ * To run an individual test case, e.g. {@link com.example.android.apis.os.MorseCodeConverterTest}:
+ * $ adb shell am instrument -w \
+ * -e class com.example.android.apis.os.MorseCodeConverterTest \
+ * com.example.android.apis.tests/android.test.InstrumentationTestRunner
+ *
+ * To run an individual test, e.g. {@link com.example.android.apis.os.MorseCodeConverterTest#testCharacterS()}:
+ * $ adb shell am instrument -w \
+ * -e class com.example.android.apis.os.MorseCodeConverterTest#testCharacterS \
+ * com.example.android.apis.tests/android.test.InstrumentationTestRunner
+ */
+public class AllTests extends TestSuite {
+
+ public static Test suite() {
+ return new TestSuiteBuilder(AllTests.class)
+ .includeAllPackagesUnderHere()
+ .build();
+ }
+}
diff --git a/build-system/tests/applibtest/app/src/main/AndroidManifest.xml b/build-system/tests/applibtest/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..41e6b82
--- /dev/null
+++ b/build-system/tests/applibtest/app/src/main/AndroidManifest.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.testprojecttest.app"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk android:minSdkVersion="15" />
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/templates/gradle-projects/NewAndroidLibrary/root/res/drawable-hdpi/ic_launcher.png b/build-system/tests/applibtest/app/src/main/res/drawable-hdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
copy from templates/gradle-projects/NewAndroidLibrary/root/res/drawable-hdpi/ic_launcher.png
copy to build-system/tests/applibtest/app/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/applibtest/app/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/tests/applibtest/app/src/main/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..9923872
--- /dev/null
+++ b/build-system/tests/applibtest/app/src/main/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/templates/gradle-projects/NewAndroidLibrary/root/res/drawable-mdpi/ic_launcher.png b/build-system/tests/applibtest/app/src/main/res/drawable-mdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
copy from templates/gradle-projects/NewAndroidLibrary/root/res/drawable-mdpi/ic_launcher.png
copy to build-system/tests/applibtest/app/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/templates/gradle-projects/NewAndroidLibrary/root/res/drawable-xhdpi/ic_launcher.png b/build-system/tests/applibtest/app/src/main/res/drawable-xhdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
copy from templates/gradle-projects/NewAndroidLibrary/root/res/drawable-xhdpi/ic_launcher.png
copy to build-system/tests/applibtest/app/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/applibtest/app/src/main/res/values/strings.xml b/build-system/tests/applibtest/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..c933032
--- /dev/null
+++ b/build-system/tests/applibtest/app/src/main/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="app_name">TestProjectTest-app</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/applibtest/build.gradle b/build-system/tests/applibtest/build.gradle
new file mode 100644
index 0000000..83b3e0b
--- /dev/null
+++ b/build-system/tests/applibtest/build.gradle
@@ -0,0 +1,8 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
diff --git a/build-system/tests/applibtest/lib/build.gradle b/build-system/tests/applibtest/lib/build.gradle
new file mode 100644
index 0000000..c02b29c
--- /dev/null
+++ b/build-system/tests/applibtest/lib/build.gradle
@@ -0,0 +1,10 @@
+apply plugin: 'android-library'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+ defaultConfig {
+ testPackageName = "com.android.tests.testprojecttest.testlib"
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/applibtest/lib/proguard-project.txt b/build-system/tests/applibtest/lib/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/build-system/tests/applibtest/lib/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/build-system/tests/applibtest/lib/src/instrumentTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java b/build-system/tests/applibtest/lib/src/instrumentTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
new file mode 100644
index 0000000..6632c58
--- /dev/null
+++ b/build-system/tests/applibtest/lib/src/instrumentTest/java/com/android/tests/testprojecttest/lib/LibActivityTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.testprojecttest.lib;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+import com.android.tests.testprojecttest.lib.R;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class LibActivityTest extends ActivityInstrumentationTestCase2<LibActivity> {
+
+ private TextView mTextView;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public LibActivityTest() {
+ super(LibActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final LibActivity a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+ mTextView = (TextView) a.findViewById(R.id.text);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView);
+ }
+}
diff --git a/build-system/tests/applibtest/lib/src/instrumentTest/java/com/android/tests/testprojecttest/test/AllTests.java b/build-system/tests/applibtest/lib/src/instrumentTest/java/com/android/tests/testprojecttest/test/AllTests.java
new file mode 100644
index 0000000..a77b53c
--- /dev/null
+++ b/build-system/tests/applibtest/lib/src/instrumentTest/java/com/android/tests/testprojecttest/test/AllTests.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.testprojecttest.test;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import android.test.suitebuilder.TestSuiteBuilder;
+
+/**
+ * A test suite containing all tests for ApiDemos.
+ *
+ * To run all suites found in this apk:
+ * $ adb shell am instrument -w \
+ * com.example.android.apis.tests/android.test.InstrumentationTestRunner
+ *
+ * To run just this suite from the command line:
+ * $ adb shell am instrument -w \
+ * -e class com.example.android.apis.AllTests \
+ * com.example.android.apis.tests/android.test.InstrumentationTestRunner
+ *
+ * To run an individual test case, e.g. {@link com.example.android.apis.os.MorseCodeConverterTest}:
+ * $ adb shell am instrument -w \
+ * -e class com.example.android.apis.os.MorseCodeConverterTest \
+ * com.example.android.apis.tests/android.test.InstrumentationTestRunner
+ *
+ * To run an individual test, e.g. {@link com.example.android.apis.os.MorseCodeConverterTest#testCharacterS()}:
+ * $ adb shell am instrument -w \
+ * -e class com.example.android.apis.os.MorseCodeConverterTest#testCharacterS \
+ * com.example.android.apis.tests/android.test.InstrumentationTestRunner
+ */
+public class AllTests extends TestSuite {
+
+ public static Test suite() {
+ return new TestSuiteBuilder(AllTests.class)
+ .includeAllPackagesUnderHere()
+ .build();
+ }
+}
diff --git a/build-system/tests/applibtest/lib/src/instrumentTest/res/values/strings.xml b/build-system/tests/applibtest/lib/src/instrumentTest/res/values/strings.xml
new file mode 100644
index 0000000..17781ee
--- /dev/null
+++ b/build-system/tests/applibtest/lib/src/instrumentTest/res/values/strings.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="ahoo">ahoo!</string>
+ <string name="hello">Hello World!</string>
+ <string name="app_name">TestProjectTest-testTest</string>
+ <string name="foo">foo!</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/applibtest/lib/src/main/AndroidManifest.xml b/build-system/tests/applibtest/lib/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..26598f0
--- /dev/null
+++ b/build-system/tests/applibtest/lib/src/main/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.testprojecttest.lib">
+ <application>
+ <activity
+ android:name="com.android.tests.testprojecttest.lib.LibActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <permission-group android:name="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+ <permission android:name="foo.permission.SEND_SMS"
+ android:permissionGroup="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+ <permission android:name="foo.blah.SEND_SMS"
+ android:permissionGroup="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+
+</manifest>
\ No newline at end of file
diff --git a/build-system/tests/applibtest/lib/src/main/aidl/com/android/tests/basicprojectwithaidl/ITest.aidl b/build-system/tests/applibtest/lib/src/main/aidl/com/android/tests/basicprojectwithaidl/ITest.aidl
new file mode 100644
index 0000000..9b81031
--- /dev/null
+++ b/build-system/tests/applibtest/lib/src/main/aidl/com/android/tests/basicprojectwithaidl/ITest.aidl
@@ -0,0 +1,7 @@
+package com.android.tests.basicprojectwithaidl;
+
+interface ITest {
+ Rect getRect();
+ int getInt();
+}
+
diff --git a/build-system/tests/applibtest/lib/src/main/aidl/com/android/tests/basicprojectwithaidl/Rect.aidl b/build-system/tests/applibtest/lib/src/main/aidl/com/android/tests/basicprojectwithaidl/Rect.aidl
new file mode 100644
index 0000000..734cf77
--- /dev/null
+++ b/build-system/tests/applibtest/lib/src/main/aidl/com/android/tests/basicprojectwithaidl/Rect.aidl
@@ -0,0 +1,5 @@
+package com.android.tests.basicprojectwithaidl;
+
+// Declare Rect so AIDL can find it and knows that it implements
+// the parcelable protocol.
+parcelable Rect;
\ No newline at end of file
diff --git a/build-system/tests/applibtest/lib/src/main/java/com/android/tests/testprojecttest/lib/LibActivity.java b/build-system/tests/applibtest/lib/src/main/java/com/android/tests/testprojecttest/lib/LibActivity.java
new file mode 100644
index 0000000..7d7f607
--- /dev/null
+++ b/build-system/tests/applibtest/lib/src/main/java/com/android/tests/testprojecttest/lib/LibActivity.java
@@ -0,0 +1,13 @@
+package com.android.tests.testprojecttest.lib;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class LibActivity extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/applibtest/lib/src/main/res/layout/main.xml b/build-system/tests/applibtest/lib/src/main/res/layout/main.xml
new file mode 100644
index 0000000..14a9c4b
--- /dev/null
+++ b/build-system/tests/applibtest/lib/src/main/res/layout/main.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="some string"
+ tools:ignore="HardcodedText" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/build-system/tests/applibtest/lib/src/main/res/values/strings.xml b/build-system/tests/applibtest/lib/src/main/res/values/strings.xml
new file mode 100644
index 0000000..fdb2272
--- /dev/null
+++ b/build-system/tests/applibtest/lib/src/main/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="app_name">TestProjectTest-lib</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/applibtest/settings.gradle b/build-system/tests/applibtest/settings.gradle
new file mode 100644
index 0000000..5ed7972
--- /dev/null
+++ b/build-system/tests/applibtest/settings.gradle
@@ -0,0 +1,2 @@
+include 'app'
+include 'lib'
\ No newline at end of file
diff --git a/build-system/tests/artifactApi/build.gradle b/build-system/tests/artifactApi/build.gradle
new file mode 100644
index 0000000..4e26af5
--- /dev/null
+++ b/build-system/tests/artifactApi/build.gradle
@@ -0,0 +1,118 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+
+apply plugin: 'android'
+
+import com.android.builder.model.ArtifactMetaData
+import com.android.builder.model.SourceProvider
+
+// Register an artifact type and tie it to the name "__test__".
+// This name will show up in Studio. It must be unique
+android.registerArtifactType("__test__", false, ArtifactMetaData.TYPE_JAVA)
+
+// register a new SourceProvider per build type, and associate it with the artifact
+// registered above.
+android.buildTypes.all { type ->
+ project.android.registerBuildTypeSourceProvider(
+ "__test__", // registered name of the artifact type
+ type, // associate it with a BuildType
+ getProvider("buildType:$type.name")) // the source provider
+}
+
+// Same with ProductFlavor
+android.productFlavors.all { flavor ->
+ project.android.registerProductFlavorSourceProvider(
+ "__test__", // registered name of the artifact type
+ flavor, // associate it with a ProductFlavor
+ getProvider("productFlavor:$flavor.name")) // the source provider
+}
+
+// now register a new (java) artifact for each variant, still associated
+// with the artifact type registered above.
+android.applicationVariants.all { variant ->
+ project.android.registerJavaArtifact(
+ "__test__", // registered name of the artifact type
+ variant, // associate it with a variant
+ "assemble:$variant.name", // name of the assemble task for this artifact
+ "compile:$variant.name", // name of the java compile task for this artifact
+ new File("classesFolder:$variant.name"), // location of the classes folder (compile output)
+ getProvider("provider:$variant.name")) // source provider specific to this variant for the artifact.
+}
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+ flavorGroups "pricing", "releaseType"
+
+ productFlavors {
+
+ beta {
+ flavorGroup "releaseType"
+ }
+
+ normal {
+ flavorGroup "releaseType"
+ }
+
+ free {
+ flavorGroup "pricing"
+ }
+
+ paid {
+ flavorGroup "pricing"
+ }
+ }
+}
+
+public class SourceProviderImpl implements SourceProvider, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private final String name;
+
+ SourceProviderImpl(String name) {
+ this.name = name
+ }
+
+ File getManifestFile() {
+ return new File(name)
+ }
+
+ Collection<File> getJavaDirectories() {
+ return Collections.emptyList()
+ }
+
+ Collection<File> getResourcesDirectories() {
+ return Collections.emptyList()
+ }
+
+ Collection<File> getAidlDirectories() {
+ return Collections.emptyList()
+ }
+
+ Collection<File> getRenderscriptDirectories() {
+ return Collections.emptyList()
+ }
+
+ Collection<File> getJniDirectories() {
+ return Collections.emptyList()
+ }
+
+ Collection<File> getResDirectories() {
+ return Collections.emptyList()
+ }
+
+ Collection<File> getAssetsDirectories() {
+ return Collections.emptyList()
+ }
+}
+
+SourceProvider getProvider(String name) {
+ return new SourceProviderImpl(name)
+}
\ No newline at end of file
diff --git a/build-system/tests/artifactApi/src/main/AndroidManifest.xml b/build-system/tests/artifactApi/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..1c35a5b
--- /dev/null
+++ b/build-system/tests/artifactApi/src/main/AndroidManifest.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.overlay2">
+ <application android:label="@string/app_name" android:icon="@drawable/icon">
+ <activity android:name=".Main"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/build-system/tests/artifactApi/src/main/java/com/android/tests/overlay2/Main.java b/build-system/tests/artifactApi/src/main/java/com/android/tests/overlay2/Main.java
new file mode 100644
index 0000000..e8a0a83
--- /dev/null
+++ b/build-system/tests/artifactApi/src/main/java/com/android/tests/overlay2/Main.java
@@ -0,0 +1,15 @@
+package com.android.tests.overlay2;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class Main extends Activity
+{
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ }
+}
diff --git a/build-system/tests/artifactApi/src/main/res/drawable/icon.png b/build-system/tests/artifactApi/src/main/res/drawable/icon.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/artifactApi/src/main/res/drawable/icon.png
Binary files differ
diff --git a/build-system/tests/artifactApi/src/main/res/drawable/no_overlay.png b/build-system/tests/artifactApi/src/main/res/drawable/no_overlay.png
new file mode 100644
index 0000000..47e1adf
--- /dev/null
+++ b/build-system/tests/artifactApi/src/main/res/drawable/no_overlay.png
Binary files differ
diff --git a/build-system/tests/artifactApi/src/main/res/layout/main.xml b/build-system/tests/artifactApi/src/main/res/layout/main.xml
new file mode 100644
index 0000000..6cd0549
--- /dev/null
+++ b/build-system/tests/artifactApi/src/main/res/layout/main.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+ <ImageView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:src="@drawable/no_overlay"
+ android:id="@+id/no_overlay" />
+</LinearLayout>
+
diff --git a/build-system/tests/artifactApi/src/main/res/values/strings.xml b/build-system/tests/artifactApi/src/main/res/values/strings.xml
new file mode 100644
index 0000000..d1420e7
--- /dev/null
+++ b/build-system/tests/artifactApi/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">_Test-Overlay2</string>
+</resources>
diff --git a/build-system/tests/assets/app/build.gradle b/build-system/tests/assets/app/build.gradle
new file mode 100644
index 0000000..5a77673
--- /dev/null
+++ b/build-system/tests/assets/app/build.gradle
@@ -0,0 +1,10 @@
+apply plugin: 'android'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
+
+dependencies {
+ compile project(':lib')
+}
diff --git a/build-system/tests/assets/app/proguard-project.txt b/build-system/tests/assets/app/proguard-project.txt
new file mode 100644
index 0000000..349f80f
--- /dev/null
+++ b/build-system/tests/assets/app/proguard-project.txt
@@ -0,0 +1,22 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+-adaptclassstrings
\ No newline at end of file
diff --git a/build-system/tests/assets/app/src/instrumentTest/java/com/android/tests/assets/app/MainActivityTest.java b/build-system/tests/assets/app/src/instrumentTest/java/com/android/tests/assets/app/MainActivityTest.java
new file mode 100644
index 0000000..77ee434
--- /dev/null
+++ b/build-system/tests/assets/app/src/instrumentTest/java/com/android/tests/assets/app/MainActivityTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.assets.app;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private TextView mAppTextView1;
+ private TextView mAppTextView2;
+ private TextView mLibTextView1;
+ private TextView mLibTextView2;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public MainActivityTest() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final MainActivity a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ mAppTextView1 = (TextView) a.findViewById(R.id.app_text1);
+ mAppTextView2 = (TextView) a.findViewById(R.id.app_text1);
+ mLibTextView1 = (TextView) a.findViewById(R.id.lib_text1);
+ mLibTextView2 = (TextView) a.findViewById(R.id.lib_text2);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mAppTextView1);
+ assertNotNull(mAppTextView2);
+ assertNotNull(mLibTextView1);
+ assertNotNull(mLibTextView2);
+ }
+
+ @MediumTest
+ public void testAndroidStrings() {
+ assertEquals("SUCCESS-APP", mAppTextView1.getText());
+ assertEquals("SUCCESS-LIB", mLibTextView1.getText());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-APP", mAppTextView2.getText());
+ assertEquals("SUCCESS-LIB", mLibTextView2.getText());
+ }
+}
diff --git a/build-system/tests/assets/app/src/main/AndroidManifest.xml b/build-system/tests/assets/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..404f93f
--- /dev/null
+++ b/build-system/tests/assets/app/src/main/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.assets.app"
+ android:versionCode="1"
+ android:versionName="1.0" xmlns:tools="http://schemas.android.com/tools">
+
+ <uses-sdk
+ android:minSdkVersion="15"
+ tools:ignore="UsesMinSdkAttributes" />
+
+ <application
+ android:icon="@drawable/icon"
+ android:label="@string/app_name" >
+ <activity
+ android:name=".MainActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/build-system/tests/assets/app/src/main/assets/App.txt b/build-system/tests/assets/app/src/main/assets/App.txt
new file mode 100644
index 0000000..084e7d0
--- /dev/null
+++ b/build-system/tests/assets/app/src/main/assets/App.txt
@@ -0,0 +1 @@
+SUCCESS-APP
\ No newline at end of file
diff --git a/build-system/tests/assets/app/src/main/java/com/android/tests/assets/app/App.java b/build-system/tests/assets/app/src/main/java/com/android/tests/assets/app/App.java
new file mode 100644
index 0000000..31e40ff
--- /dev/null
+++ b/build-system/tests/assets/app/src/main/java/com/android/tests/assets/app/App.java
@@ -0,0 +1,46 @@
+package com.android.tests.assets.app;
+
+import android.app.Activity;
+import android.content.Context;
+import android.widget.TextView;
+import android.content.res.AssetManager;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+public class App {
+
+ public static void handleTextView(Activity a) {
+ TextView tv = (TextView) a.findViewById(R.id.app_text2);
+ if (tv != null) {
+ tv.setText(getContent(a));
+ }
+ }
+
+ private static String getContent(Context context) {
+ AssetManager assets = context.getAssets();
+
+ BufferedReader reader = null;
+ try {
+ InputStream input = assets.open("App.txt");
+ if (input == null) {
+ return "FAILED TO FIND App.txt";
+ }
+ reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+
+ return reader.readLine();
+ } catch (IOException e) {
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ return "FAILED TO READ CONTENT";
+ }
+}
diff --git a/build-system/tests/assets/app/src/main/java/com/android/tests/assets/app/MainActivity.java b/build-system/tests/assets/app/src/main/java/com/android/tests/assets/app/MainActivity.java
new file mode 100644
index 0000000..c20a401
--- /dev/null
+++ b/build-system/tests/assets/app/src/main/java/com/android/tests/assets/app/MainActivity.java
@@ -0,0 +1,18 @@
+package com.android.tests.assets.app;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.tests.assets.lib.Lib;
+
+public class MainActivity extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ App.handleTextView(this);
+ Lib.handleTextView(this);
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/assets/app/src/main/res/drawable-hdpi/icon.png b/build-system/tests/assets/app/src/main/res/drawable-hdpi/icon.png
new file mode 100644
index 0000000..8074c4c
--- /dev/null
+++ b/build-system/tests/assets/app/src/main/res/drawable-hdpi/icon.png
Binary files differ
diff --git a/build-system/tests/assets/app/src/main/res/drawable-ldpi/icon.png b/build-system/tests/assets/app/src/main/res/drawable-ldpi/icon.png
new file mode 100644
index 0000000..1095584
--- /dev/null
+++ b/build-system/tests/assets/app/src/main/res/drawable-ldpi/icon.png
Binary files differ
diff --git a/build-system/tests/assets/app/src/main/res/drawable-mdpi/icon.png b/build-system/tests/assets/app/src/main/res/drawable-mdpi/icon.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/assets/app/src/main/res/drawable-mdpi/icon.png
Binary files differ
diff --git a/build-system/tests/assets/app/src/main/res/layout/main.xml b/build-system/tests/assets/app/src/main/res/layout/main.xml
new file mode 100644
index 0000000..6761bef
--- /dev/null
+++ b/build-system/tests/assets/app/src/main/res/layout/main.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/app_text1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/app_string" />
+
+ <TextView
+ android:id="@+id/app_text2"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+ <include layout="@layout/lib_main" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/build-system/tests/assets/app/src/main/res/values/strings.xml b/build-system/tests/assets/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..190a400
--- /dev/null
+++ b/build-system/tests/assets/app/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="app_name">flavorlib-app</string>
+ <string name="app_string">SUCCESS-APP</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/assets/build.gradle b/build-system/tests/assets/build.gradle
new file mode 100644
index 0000000..a8fdb64
--- /dev/null
+++ b/build-system/tests/assets/build.gradle
@@ -0,0 +1,10 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+
+apply plugin: 'android-reporting'
\ No newline at end of file
diff --git a/build-system/tests/assets/lib/build.gradle b/build-system/tests/assets/lib/build.gradle
new file mode 100644
index 0000000..4b2a733
--- /dev/null
+++ b/build-system/tests/assets/lib/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'android-library'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
\ No newline at end of file
diff --git a/build-system/tests/assets/lib/proguard-project.txt b/build-system/tests/assets/lib/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/build-system/tests/assets/lib/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/build-system/tests/assets/lib/src/instrumentTest/java/com/android/tests/assets/lib/MainActivityTest.java b/build-system/tests/assets/lib/src/instrumentTest/java/com/android/tests/assets/lib/MainActivityTest.java
new file mode 100644
index 0000000..40ebe35
--- /dev/null
+++ b/build-system/tests/assets/lib/src/instrumentTest/java/com/android/tests/assets/lib/MainActivityTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.assets.lib;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+import com.android.tests.assets.lib.R;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private TextView mTextView1;
+ private TextView mTextView2;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public MainActivityTest() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final MainActivity a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ mTextView1 = (TextView) a.findViewById(R.id.lib_text1);
+ mTextView2 = (TextView) a.findViewById(R.id.lib_text2);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView1);
+ assertNotNull(mTextView2);
+ }
+
+ @MediumTest
+ public void testAndroidStrings() {
+ assertEquals("SUCCESS-LIB", mTextView1.getText());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB", mTextView2.getText());
+ }
+}
diff --git a/build-system/tests/assets/lib/src/main/AndroidManifest.xml b/build-system/tests/assets/lib/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..275eb6f
--- /dev/null
+++ b/build-system/tests/assets/lib/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.assets.lib"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/lib_name" >
+ <activity
+ android:name="MainActivity"
+ android:label="@string/lib_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/build-system/tests/assets/lib/src/main/assets/Lib.txt b/build-system/tests/assets/lib/src/main/assets/Lib.txt
new file mode 100644
index 0000000..731a6b4
--- /dev/null
+++ b/build-system/tests/assets/lib/src/main/assets/Lib.txt
@@ -0,0 +1 @@
+SUCCESS-LIB
\ No newline at end of file
diff --git a/build-system/tests/assets/lib/src/main/java/com/android/tests/assets/lib/Lib.java b/build-system/tests/assets/lib/src/main/java/com/android/tests/assets/lib/Lib.java
new file mode 100644
index 0000000..89ecfbb
--- /dev/null
+++ b/build-system/tests/assets/lib/src/main/java/com/android/tests/assets/lib/Lib.java
@@ -0,0 +1,46 @@
+package com.android.tests.assets.lib;
+
+import android.app.Activity;
+import android.content.Context;
+import android.widget.TextView;
+import android.content.res.AssetManager;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+public class Lib {
+
+ public static void handleTextView(Activity a) {
+ TextView tv = (TextView) a.findViewById(R.id.lib_text2);
+ if (tv != null) {
+ tv.setText(getContent(a));
+ }
+ }
+
+ private static String getContent(Context context) {
+ AssetManager assets = context.getAssets();
+
+ BufferedReader reader = null;
+ try {
+ InputStream input = assets.open("Lib.txt");
+ if (input == null) {
+ return "FAILED TO FIND Lib.txt";
+ }
+ reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+
+ return reader.readLine();
+ } catch (IOException e) {
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ return "FAILED TO READ CONTENT";
+ }
+}
diff --git a/build-system/tests/assets/lib/src/main/java/com/android/tests/assets/lib/MainActivity.java b/build-system/tests/assets/lib/src/main/java/com/android/tests/assets/lib/MainActivity.java
new file mode 100644
index 0000000..d8def1c
--- /dev/null
+++ b/build-system/tests/assets/lib/src/main/java/com/android/tests/assets/lib/MainActivity.java
@@ -0,0 +1,15 @@
+package com.android.tests.assets.lib;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class MainActivity extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.lib_main);
+
+ Lib.handleTextView(this);
+ }
+}
diff --git a/build-system/tests/assets/lib/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/tests/assets/lib/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..8074c4c
--- /dev/null
+++ b/build-system/tests/assets/lib/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/assets/lib/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/tests/assets/lib/src/main/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..1095584
--- /dev/null
+++ b/build-system/tests/assets/lib/src/main/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/assets/lib/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/tests/assets/lib/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/assets/lib/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/assets/lib/src/main/res/layout/lib_main.xml b/build-system/tests/assets/lib/src/main/res/layout/lib_main.xml
new file mode 100644
index 0000000..47e792a
--- /dev/null
+++ b/build-system/tests/assets/lib/src/main/res/layout/lib_main.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/lib_text1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/lib_string" />
+
+ <TextView
+ android:id="@+id/lib_text2"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/build-system/tests/assets/lib/src/main/res/values/strings.xml b/build-system/tests/assets/lib/src/main/res/values/strings.xml
new file mode 100644
index 0000000..a97de83
--- /dev/null
+++ b/build-system/tests/assets/lib/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="lib_name">assets-lib</string>
+ <string name="lib_string">SUCCESS-LIB</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/assets/settings.gradle b/build-system/tests/assets/settings.gradle
new file mode 100644
index 0000000..eedb2a1
--- /dev/null
+++ b/build-system/tests/assets/settings.gradle
@@ -0,0 +1,2 @@
+include 'app'
+include 'lib'
diff --git a/build-system/tests/attrOrder/app/build.gradle b/build-system/tests/attrOrder/app/build.gradle
new file mode 100644
index 0000000..98e423b
--- /dev/null
+++ b/build-system/tests/attrOrder/app/build.gradle
@@ -0,0 +1,11 @@
+apply plugin: 'android'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
+
+dependencies{
+ compile project(":lib")
+}
+
diff --git a/build-system/tests/attrOrder/app/src/instrumentTest/java/com/android/tests/basic/MainTest.java b/build-system/tests/attrOrder/app/src/instrumentTest/java/com/android/tests/basic/MainTest.java
new file mode 100644
index 0000000..41272ac
--- /dev/null
+++ b/build-system/tests/attrOrder/app/src/instrumentTest/java/com/android/tests/basic/MainTest.java
@@ -0,0 +1,45 @@
+package com.android.tests.basic;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+public class MainTest extends ActivityInstrumentationTestCase2<Main> {
+
+ private TextView mTextView;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
+ */
+ public MainTest() {
+ super(Main.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final Main a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+ mTextView = (TextView) a.findViewById(R.id.text);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView);
+ }
+
+ @MediumTest
+ public void testText() {
+ System.out.println("!!!! text at " + mTextView.getText());
+ assertTrue("Hello, world!".equals(mTextView.getText()));
+ }
+
+}
+
diff --git a/build-system/tests/attrOrder/app/src/main/AndroidManifest.xml b/build-system/tests/attrOrder/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..4f8d570
--- /dev/null
+++ b/build-system/tests/attrOrder/app/src/main/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.basic">
+ <application android:label="@string/app_name" android:icon="@drawable/icon">
+ <activity android:name=".Main"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <uses-permission android:name="com.blah" />
+
+ <permission-group android:name="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+ <permission android:name="foo.permission.SEND_SMS"
+ android:permissionGroup="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+ <permission android:name="foo.blah.SEND_SMS"
+ android:permissionGroup="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+</manifest>
diff --git a/build-system/tests/attrOrder/app/src/main/java/com/android/tests/basic/Main.java b/build-system/tests/attrOrder/app/src/main/java/com/android/tests/basic/Main.java
new file mode 100644
index 0000000..e6e1896
--- /dev/null
+++ b/build-system/tests/attrOrder/app/src/main/java/com/android/tests/basic/Main.java
@@ -0,0 +1,20 @@
+package com.android.tests.basic;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+import com.android.tests.libstest.lib.Lib;
+
+public class Main extends Activity
+{
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ TextView textView = (TextView) findViewById(R.id.text);
+ textView.setText(Lib.getStringFromStyle(this));
+ }
+}
diff --git a/build-system/tests/attrOrder/app/src/main/res/drawable/icon.png b/build-system/tests/attrOrder/app/src/main/res/drawable/icon.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/attrOrder/app/src/main/res/drawable/icon.png
Binary files differ
diff --git a/build-system/tests/attrOrder/app/src/main/res/layout/main.xml b/build-system/tests/attrOrder/app/src/main/res/layout/main.xml
new file mode 100644
index 0000000..b199751
--- /dev/null
+++ b/build-system/tests/attrOrder/app/src/main/res/layout/main.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+<TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="Test App - Basic"
+ android:id="@+id/text"
+ />
+</LinearLayout>
+
diff --git a/build-system/tests/attrOrder/app/src/main/res/values/strings.xml b/build-system/tests/attrOrder/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..60ea2d0
--- /dev/null
+++ b/build-system/tests/attrOrder/app/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">_Test-Basic</string>
+</resources>
diff --git a/build-system/tests/attrOrder/app/src/main/res/values/strings_additional.xml b/build-system/tests/attrOrder/app/src/main/res/values/strings_additional.xml
new file mode 100644
index 0000000..42d7785
--- /dev/null
+++ b/build-system/tests/attrOrder/app/src/main/res/values/strings_additional.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+<string name="a0">bb</string>
+<string name="a1">bb</string>
+<string name="a2">bb</string>
+<string name="a3">bb</string>
+<string name="a4">bb</string>
+<string name="a5">bb</string>
+<string name="a6">bb</string>
+<string name="a7">bb</string>
+<string name="a8">bb</string>
+<string name="a9">bb</string>
+</resources>
diff --git a/build-system/tests/attrOrder/build.gradle b/build-system/tests/attrOrder/build.gradle
new file mode 100644
index 0000000..83b3e0b
--- /dev/null
+++ b/build-system/tests/attrOrder/build.gradle
@@ -0,0 +1,8 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
diff --git a/build-system/tests/attrOrder/lib/build.gradle b/build-system/tests/attrOrder/lib/build.gradle
new file mode 100644
index 0000000..db660d9
--- /dev/null
+++ b/build-system/tests/attrOrder/lib/build.gradle
@@ -0,0 +1,7 @@
+apply plugin: 'android-library'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
+
diff --git a/build-system/tests/attrOrder/lib/src/main/AndroidManifest.xml b/build-system/tests/attrOrder/lib/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..8578d7a
--- /dev/null
+++ b/build-system/tests/attrOrder/lib/src/main/AndroidManifest.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.libstest.lib"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/lib2_name" >
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/build-system/tests/attrOrder/lib/src/main/java/com/android/tests/libstest/lib/Lib.java b/build-system/tests/attrOrder/lib/src/main/java/com/android/tests/libstest/lib/Lib.java
new file mode 100644
index 0000000..3eb0083
--- /dev/null
+++ b/build-system/tests/attrOrder/lib/src/main/java/com/android/tests/libstest/lib/Lib.java
@@ -0,0 +1,24 @@
+package com.android.tests.libstest.lib;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.widget.TextView;
+
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.Readable;
+import java.lang.String;
+
+public class Lib {
+
+ public static String getStringFromStyle(Context context){
+ TypedArray array = context.obtainStyledAttributes(R.style.Example, R.styleable.StyleableExample);
+ String result = array.getString(R.styleable.StyleableExample_d_common_attr);
+ array.recycle();
+ return result;
+ }
+}
diff --git a/build-system/tests/attrOrder/lib/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/tests/attrOrder/lib/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..8074c4c
--- /dev/null
+++ b/build-system/tests/attrOrder/lib/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/attrOrder/lib/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/tests/attrOrder/lib/src/main/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..1095584
--- /dev/null
+++ b/build-system/tests/attrOrder/lib/src/main/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/attrOrder/lib/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/tests/attrOrder/lib/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/attrOrder/lib/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/attrOrder/lib/src/main/res/layout/lib2_main.xml b/build-system/tests/attrOrder/lib/src/main/res/layout/lib2_main.xml
new file mode 100644
index 0000000..bb639d1
--- /dev/null
+++ b/build-system/tests/attrOrder/lib/src/main/res/layout/lib2_main.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/lib2_text1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/lib2_string" />
+
+ <TextView
+ android:id="@+id/lib2_text2"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/build-system/tests/attrOrder/lib/src/main/res/values/strings.xml b/build-system/tests/attrOrder/lib/src/main/res/values/strings.xml
new file mode 100644
index 0000000..215b8fa
--- /dev/null
+++ b/build-system/tests/attrOrder/lib/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="lib2_name">LibsTest-lib2</string>
+ <string name="lib2_string">SUCCESS-LIB2</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/attrOrder/lib/src/main/res/values/styleable.xml b/build-system/tests/attrOrder/lib/src/main/res/values/styleable.xml
new file mode 100644
index 0000000..bcf844a
--- /dev/null
+++ b/build-system/tests/attrOrder/lib/src/main/res/values/styleable.xml
@@ -0,0 +1,19 @@
+<resources>
+
+ <attr name="d_common_attr" format="string"/>
+
+ <declare-styleable name="StyleableExample">
+ <attr name="attr_int" format="integer"/>
+ <attr name="attr_bool" format="boolean"/>
+ <attr name="attr_str" format="string"/>
+ <attr name="d_common_attr"/>
+ </declare-styleable>
+
+ <style name="Example">
+ <item name="attr_int">10</item>
+ <item name="attr_bool">true</item>
+ <item name="attr_str">String2</item>
+ <item name="d_common_attr">Hello, world!</item>
+ </style>
+
+ </resources>
diff --git a/build-system/tests/attrOrder/settings.gradle b/build-system/tests/attrOrder/settings.gradle
new file mode 100644
index 0000000..eedb2a1
--- /dev/null
+++ b/build-system/tests/attrOrder/settings.gradle
@@ -0,0 +1,2 @@
+include 'app'
+include 'lib'
diff --git a/build-system/tests/autorepo/build.gradle b/build-system/tests/autorepo/build.gradle
new file mode 100644
index 0000000..b1e5a6d
--- /dev/null
+++ b/build-system/tests/autorepo/build.gradle
@@ -0,0 +1,17 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+apply plugin: 'android'
+
+android {
+ compileSdkVersion 15
+}
+
+dependencies {
+ compile 'com.android.support:android-support-v4:12'
+}
\ No newline at end of file
diff --git a/build-system/tests/autorepo/src/instrumentTest/java/com/android/tests/basic/MainTest.java b/build-system/tests/autorepo/src/instrumentTest/java/com/android/tests/basic/MainTest.java
new file mode 100644
index 0000000..7cf7329
--- /dev/null
+++ b/build-system/tests/autorepo/src/instrumentTest/java/com/android/tests/basic/MainTest.java
@@ -0,0 +1,38 @@
+package com.android.tests.basic;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+public class MainTest extends ActivityInstrumentationTestCase2<Main> {
+
+ private TextView mTextView;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
+ */
+ public MainTest() {
+ super(Main.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final Main a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+ mTextView = (TextView) a.findViewById(R.id.text);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView);
+ }
+}
+
diff --git a/build-system/tests/autorepo/src/main/AndroidManifest.xml b/build-system/tests/autorepo/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..4f8d570
--- /dev/null
+++ b/build-system/tests/autorepo/src/main/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.basic">
+ <application android:label="@string/app_name" android:icon="@drawable/icon">
+ <activity android:name=".Main"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <uses-permission android:name="com.blah" />
+
+ <permission-group android:name="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+ <permission android:name="foo.permission.SEND_SMS"
+ android:permissionGroup="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+ <permission android:name="foo.blah.SEND_SMS"
+ android:permissionGroup="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+</manifest>
diff --git a/build-system/tests/autorepo/src/main/assets/notice.txt b/build-system/tests/autorepo/src/main/assets/notice.txt
new file mode 100644
index 0000000..33ff961
--- /dev/null
+++ b/build-system/tests/autorepo/src/main/assets/notice.txt
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2005-2012, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/build-system/tests/autorepo/src/main/java/com/android/tests/basic/Main.java b/build-system/tests/autorepo/src/main/java/com/android/tests/basic/Main.java
new file mode 100644
index 0000000..2b0e698
--- /dev/null
+++ b/build-system/tests/autorepo/src/main/java/com/android/tests/basic/Main.java
@@ -0,0 +1,15 @@
+package com.android.tests.basic;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class Main extends Activity
+{
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ }
+}
diff --git a/build-system/tests/autorepo/src/main/res/drawable/icon.png b/build-system/tests/autorepo/src/main/res/drawable/icon.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/autorepo/src/main/res/drawable/icon.png
Binary files differ
diff --git a/build-system/tests/autorepo/src/main/res/layout/main.xml b/build-system/tests/autorepo/src/main/res/layout/main.xml
new file mode 100644
index 0000000..b199751
--- /dev/null
+++ b/build-system/tests/autorepo/src/main/res/layout/main.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+<TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="Test App - Basic"
+ android:id="@+id/text"
+ />
+</LinearLayout>
+
diff --git a/build-system/tests/autorepo/src/main/res/raw/notice.txt b/build-system/tests/autorepo/src/main/res/raw/notice.txt
new file mode 100644
index 0000000..33ff961
--- /dev/null
+++ b/build-system/tests/autorepo/src/main/res/raw/notice.txt
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2005-2012, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/build-system/tests/autorepo/src/main/res/values/strings.xml b/build-system/tests/autorepo/src/main/res/values/strings.xml
new file mode 100644
index 0000000..60ea2d0
--- /dev/null
+++ b/build-system/tests/autorepo/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">_Test-Basic</string>
+</resources>
diff --git a/build-system/tests/autorepo/src/release/res/values/strings.xml b/build-system/tests/autorepo/src/release/res/values/strings.xml
new file mode 100644
index 0000000..532909c
--- /dev/null
+++ b/build-system/tests/autorepo/src/release/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">_Test-Basic-Release</string>
+</resources>
diff --git a/build-system/tests/basic/build.gradle b/build-system/tests/basic/build.gradle
new file mode 100644
index 0000000..8f0cf2a
--- /dev/null
+++ b/build-system/tests/basic/build.gradle
@@ -0,0 +1,66 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+apply plugin: 'android'
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ compile 'com.android.support:support-v4:13.0.0'
+ debugCompile 'com.android.support:support-v13:13.0.0'
+
+ compile 'com.google.android.gms:play-services:3.1.36'
+}
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+ testBuildType "debug"
+
+ signingConfigs {
+ myConfig {
+ storeFile file("debug.keystore")
+ storePassword "android"
+ keyAlias "androiddebugkey"
+ keyPassword "android"
+ }
+ }
+
+ defaultConfig {
+ versionCode 12
+ versionName "2.0"
+ minSdkVersion 16
+ targetSdkVersion 16
+
+ testInstrumentationRunner "android.test.InstrumentationTestRunner"
+ testHandleProfiling false
+
+ buildConfigField "boolean", "DEFAULT", "true"
+ buildConfigField "String", "FOO", "\"foo\""
+
+ resConfig "en"
+ resConfigs "nodpi", "hdpi"
+ }
+
+ buildTypes {
+ debug {
+ packageNameSuffix ".debug"
+ signingConfig signingConfigs.myConfig
+
+ buildConfigField "String", "FOO", "\"bar\""
+ }
+ }
+
+ aaptOptions {
+ noCompress 'txt'
+ ignoreAssetsPattern "!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/basic/debug.keystore b/build-system/tests/basic/debug.keystore
new file mode 100644
index 0000000..389278e
--- /dev/null
+++ b/build-system/tests/basic/debug.keystore
Binary files differ
diff --git a/build-system/tests/basic/src/instrumentTest/java/com/android/tests/basic/MainTest.java b/build-system/tests/basic/src/instrumentTest/java/com/android/tests/basic/MainTest.java
new file mode 100644
index 0000000..9f4b2de
--- /dev/null
+++ b/build-system/tests/basic/src/instrumentTest/java/com/android/tests/basic/MainTest.java
@@ -0,0 +1,43 @@
+package com.android.tests.basic;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+public class MainTest extends ActivityInstrumentationTestCase2<Main> {
+
+ private TextView mTextView;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
+ */
+ public MainTest() {
+ super(Main.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final Main a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+ mTextView = (TextView) a.findViewById(R.id.text);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView);
+ }
+
+ @MediumTest
+ public void testBuildConfig() {
+ assertEquals("bar", BuildConfig.FOO);
+ }
+}
+
diff --git a/build-system/tests/basic/src/main/AndroidManifest.xml b/build-system/tests/basic/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..4f8d570
--- /dev/null
+++ b/build-system/tests/basic/src/main/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.basic">
+ <application android:label="@string/app_name" android:icon="@drawable/icon">
+ <activity android:name=".Main"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <uses-permission android:name="com.blah" />
+
+ <permission-group android:name="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+ <permission android:name="foo.permission.SEND_SMS"
+ android:permissionGroup="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+ <permission android:name="foo.blah.SEND_SMS"
+ android:permissionGroup="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+</manifest>
diff --git a/build-system/tests/basic/src/main/assets/notice.txt b/build-system/tests/basic/src/main/assets/notice.txt
new file mode 100644
index 0000000..33ff961
--- /dev/null
+++ b/build-system/tests/basic/src/main/assets/notice.txt
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2005-2012, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/build-system/tests/basic/src/main/java/com/android/tests/basic/Main.java b/build-system/tests/basic/src/main/java/com/android/tests/basic/Main.java
new file mode 100644
index 0000000..2b0e698
--- /dev/null
+++ b/build-system/tests/basic/src/main/java/com/android/tests/basic/Main.java
@@ -0,0 +1,15 @@
+package com.android.tests.basic;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class Main extends Activity
+{
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ }
+}
diff --git a/build-system/tests/basic/src/main/res/drawable/icon.png b/build-system/tests/basic/src/main/res/drawable/icon.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/basic/src/main/res/drawable/icon.png
Binary files differ
diff --git a/build-system/tests/basic/src/main/res/layout/main.xml b/build-system/tests/basic/src/main/res/layout/main.xml
new file mode 100644
index 0000000..b199751
--- /dev/null
+++ b/build-system/tests/basic/src/main/res/layout/main.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+<TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="Test App - Basic"
+ android:id="@+id/text"
+ />
+</LinearLayout>
+
diff --git a/build-system/tests/basic/src/main/res/raw/notice.txt b/build-system/tests/basic/src/main/res/raw/notice.txt
new file mode 100644
index 0000000..33ff961
--- /dev/null
+++ b/build-system/tests/basic/src/main/res/raw/notice.txt
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2005-2012, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/build-system/tests/basic/src/main/res/values/strings.xml b/build-system/tests/basic/src/main/res/values/strings.xml
new file mode 100644
index 0000000..60ea2d0
--- /dev/null
+++ b/build-system/tests/basic/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">_Test-Basic</string>
+</resources>
diff --git a/build-system/tests/basic/src/release/res/values/strings.xml b/build-system/tests/basic/src/release/res/values/strings.xml
new file mode 100644
index 0000000..532909c
--- /dev/null
+++ b/build-system/tests/basic/src/release/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">_Test-Basic-Release</string>
+</resources>
diff --git a/build-system/tests/basicMultiFlavors/build.gradle b/build-system/tests/basicMultiFlavors/build.gradle
new file mode 100644
index 0000000..877a268
--- /dev/null
+++ b/build-system/tests/basicMultiFlavors/build.gradle
@@ -0,0 +1,35 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+apply plugin: 'android'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+ flavorGroups "pricing", "releaseType"
+
+ productFlavors {
+
+ beta {
+ flavorGroup "releaseType"
+ }
+
+ normal {
+ flavorGroup "releaseType"
+ }
+
+ free {
+ flavorGroup "pricing"
+ }
+
+ paid {
+ flavorGroup "pricing"
+ }
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/basicMultiFlavors/src/f1/res/values/strings.xml b/build-system/tests/basicMultiFlavors/src/f1/res/values/strings.xml
new file mode 100644
index 0000000..7154c04
--- /dev/null
+++ b/build-system/tests/basicMultiFlavors/src/f1/res/values/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">_Test-Flavored-f1</string>
+ <string name="text">F1 text</string>
+</resources>
diff --git a/build-system/tests/basicMultiFlavors/src/f1Staging/res/values/strings.xml b/build-system/tests/basicMultiFlavors/src/f1Staging/res/values/strings.xml
new file mode 100644
index 0000000..782422e
--- /dev/null
+++ b/build-system/tests/basicMultiFlavors/src/f1Staging/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="text">F1-Staging text</string>
+</resources>
diff --git a/build-system/tests/basicMultiFlavors/src/f2/AndroidManifest.xml b/build-system/tests/basicMultiFlavors/src/f2/AndroidManifest.xml
new file mode 100644
index 0000000..ce0bb8d
--- /dev/null
+++ b/build-system/tests/basicMultiFlavors/src/f2/AndroidManifest.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="">
+ <application>
+ <activity android:name="com.android.tests.flavored.OtherActivity"
+ android:label="@string/other_activity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/build-system/tests/basicMultiFlavors/src/f2/java/com/android/tests/flavored/OtherActivity.java b/build-system/tests/basicMultiFlavors/src/f2/java/com/android/tests/flavored/OtherActivity.java
new file mode 100644
index 0000000..6ffac9c
--- /dev/null
+++ b/build-system/tests/basicMultiFlavors/src/f2/java/com/android/tests/flavored/OtherActivity.java
@@ -0,0 +1,15 @@
+package com.android.tests.flavored;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class OtherActivity extends Activity
+{
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main2);
+ }
+}
diff --git a/build-system/tests/basicMultiFlavors/src/f2/res/layout/main2.xml b/build-system/tests/basicMultiFlavors/src/f2/res/layout/main2.xml
new file mode 100644
index 0000000..90c3c43
--- /dev/null
+++ b/build-system/tests/basicMultiFlavors/src/f2/res/layout/main2.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+<TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="Test App - Flavored - f2"
+ android:id="@+id/text2"
+ />
+</LinearLayout>
+
diff --git a/build-system/tests/basicMultiFlavors/src/f2/res/values/strings.xml b/build-system/tests/basicMultiFlavors/src/f2/res/values/strings.xml
new file mode 100644
index 0000000..bb53fd4
--- /dev/null
+++ b/build-system/tests/basicMultiFlavors/src/f2/res/values/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">_Test-Flavored-f2</string>
+ <string name="other_activity">_Test-f2-act2</string>
+</resources>
diff --git a/build-system/tests/basicMultiFlavors/src/main/AndroidManifest.xml b/build-system/tests/basicMultiFlavors/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..0d1c338
--- /dev/null
+++ b/build-system/tests/basicMultiFlavors/src/main/AndroidManifest.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.flavored">
+ <application android:label="@string/app_name" android:icon="@drawable/icon">
+ <activity android:name=".Main"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/build-system/tests/basicMultiFlavors/src/main/java/com/android/tests/flavored/Main.java b/build-system/tests/basicMultiFlavors/src/main/java/com/android/tests/flavored/Main.java
new file mode 100644
index 0000000..26debd3
--- /dev/null
+++ b/build-system/tests/basicMultiFlavors/src/main/java/com/android/tests/flavored/Main.java
@@ -0,0 +1,15 @@
+package com.android.tests.flavored;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class Main extends Activity
+{
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ }
+}
diff --git a/build-system/tests/basicMultiFlavors/src/main/res/drawable/icon.png b/build-system/tests/basicMultiFlavors/src/main/res/drawable/icon.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/basicMultiFlavors/src/main/res/drawable/icon.png
Binary files differ
diff --git a/build-system/tests/basicMultiFlavors/src/main/res/layout/main.xml b/build-system/tests/basicMultiFlavors/src/main/res/layout/main.xml
new file mode 100644
index 0000000..9d4e976
--- /dev/null
+++ b/build-system/tests/basicMultiFlavors/src/main/res/layout/main.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+<TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/text"
+ android:id="@+id/text"
+ />
+</LinearLayout>
+
diff --git a/build-system/tests/basicMultiFlavors/src/main/res/values/strings.xml b/build-system/tests/basicMultiFlavors/src/main/res/values/strings.xml
new file mode 100644
index 0000000..46d8260
--- /dev/null
+++ b/build-system/tests/basicMultiFlavors/src/main/res/values/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">###</string>
+ <string name="text">default text</string>
+</resources>
diff --git a/build-system/tests/build.gradle b/build-system/tests/build.gradle
new file mode 100644
index 0000000..a492fa1
--- /dev/null
+++ b/build-system/tests/build.gradle
@@ -0,0 +1,10 @@
+task("printTasks") << {
+ project.tasks.each { t ->
+ print("${t.name} -> ")
+ t.dependsOn.each { t2 ->
+ if (t2 instanceof Task)
+ print("${t2.name}, ")
+ }
+ println("")
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/dependencies/build.gradle b/build-system/tests/dependencies/build.gradle
new file mode 100644
index 0000000..cf56a16
--- /dev/null
+++ b/build-system/tests/dependencies/build.gradle
@@ -0,0 +1,37 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+apply plugin: 'android'
+
+version='1.0'
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ compile 'com.google.guava:guava:11.0.2'
+ apk files('libs/jarProject.jar')
+}
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+ testBuildType "blah"
+
+ defaultConfig {
+ }
+
+ buildTypes {
+ blah {
+ packageNameSuffix ".blah"
+ signingConfig signingConfigs.debug
+ }
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/dependencies/debug.keystore b/build-system/tests/dependencies/debug.keystore
new file mode 100644
index 0000000..389278e
--- /dev/null
+++ b/build-system/tests/dependencies/debug.keystore
Binary files differ
diff --git a/build-system/tests/dependencies/jarProject/build.gradle b/build-system/tests/dependencies/jarProject/build.gradle
new file mode 100644
index 0000000..f3eca85
--- /dev/null
+++ b/build-system/tests/dependencies/jarProject/build.gradle
@@ -0,0 +1 @@
+apply plugin: 'java'
\ No newline at end of file
diff --git a/build-system/tests/dependencies/jarProject/src/main/java/com/android/tests/dependencies/jar/StringHelper.java b/build-system/tests/dependencies/jarProject/src/main/java/com/android/tests/dependencies/jar/StringHelper.java
new file mode 100644
index 0000000..4c36a4d
--- /dev/null
+++ b/build-system/tests/dependencies/jarProject/src/main/java/com/android/tests/dependencies/jar/StringHelper.java
@@ -0,0 +1,8 @@
+package com.android.tests.dependencies.jar;
+
+public class StringHelper {
+
+ public static String getString(String str) {
+ return str + "-helper";
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/dependencies/libs/jarProject.jar b/build-system/tests/dependencies/libs/jarProject.jar
new file mode 100644
index 0000000..6b03f1d
--- /dev/null
+++ b/build-system/tests/dependencies/libs/jarProject.jar
Binary files differ
diff --git a/build-system/tests/dependencies/src/instrumentTest/java/com/android/tests/dependencies/MainActivityTest.java b/build-system/tests/dependencies/src/instrumentTest/java/com/android/tests/dependencies/MainActivityTest.java
new file mode 100644
index 0000000..034b8f6
--- /dev/null
+++ b/build-system/tests/dependencies/src/instrumentTest/java/com/android/tests/dependencies/MainActivityTest.java
@@ -0,0 +1,44 @@
+package com.android.tests.dependencies;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.widget.TextView;
+
+public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private TextView mTextView;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link MainActivity} activity.
+ */
+ public MainActivityTest() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final MainActivity a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+ mTextView = (TextView) a.findViewById(R.id.text);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @SmallTest
+ public void testPreconditions() {
+ assertNotNull(mTextView);
+ }
+
+ @SmallTest
+ public void testValues() {
+ assertEquals("Foo-helper", mTextView.getText());
+ }
+}
+
diff --git a/build-system/tests/dependencies/src/main/AndroidManifest.xml b/build-system/tests/dependencies/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..5b71e12
--- /dev/null
+++ b/build-system/tests/dependencies/src/main/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.dependencies"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <application android:label="@string/app_name" android:icon="@drawable/ic_launcher">
+ <activity android:name="MainActivity"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity
+ android:name="ShowPeopleActivity"
+ android:label="@string/title_activity_display_message" >
+ <meta-data
+ android:name="android.support.PARENT_ACTIVITY"
+ android:value="org.gradle.sample.MainActivity" />
+ </activity>
+ </application>
+</manifest>
diff --git a/build-system/tests/dependencies/src/main/java/com/android/tests/dependencies/BuildType.java b/build-system/tests/dependencies/src/main/java/com/android/tests/dependencies/BuildType.java
new file mode 100644
index 0000000..40fdaa2
--- /dev/null
+++ b/build-system/tests/dependencies/src/main/java/com/android/tests/dependencies/BuildType.java
@@ -0,0 +1,5 @@
+package com.android.tests.dependencies;
+
+public interface BuildType {
+ String getBuildType();
+}
diff --git a/build-system/tests/dependencies/src/main/java/com/android/tests/dependencies/MainActivity.java b/build-system/tests/dependencies/src/main/java/com/android/tests/dependencies/MainActivity.java
new file mode 100644
index 0000000..3d617f6
--- /dev/null
+++ b/build-system/tests/dependencies/src/main/java/com/android/tests/dependencies/MainActivity.java
@@ -0,0 +1,45 @@
+package com.android.tests.dependencies;
+
+import android.app.Activity;
+import android.view.View;
+import android.content.Intent;
+import android.os.Bundle;
+
+import android.annotation.TargetApi;
+import android.widget.TextView;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class MainActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ TextView tv = (TextView) findViewById(R.id.text);
+
+ // use reflection since the jar project is in the packaging scope but not
+ // the compile scope.
+ try {
+ Class<?> clazz = getClassLoader().loadClass("com.android.tests.dependencies.jar.StringHelper");
+ Method getString = clazz.getMethod("getString", String.class);
+
+ tv.setText((String) getString.invoke(null, "Foo"));
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @TargetApi(10)
+ public void sendMessage(View view) {
+ Intent intent = new Intent(this, ShowPeopleActivity.class);
+ startActivity(intent);
+ }
+}
diff --git a/build-system/tests/dependencies/src/main/java/com/android/tests/dependencies/Person.java b/build-system/tests/dependencies/src/main/java/com/android/tests/dependencies/Person.java
new file mode 100644
index 0000000..fcd6f21
--- /dev/null
+++ b/build-system/tests/dependencies/src/main/java/com/android/tests/dependencies/Person.java
@@ -0,0 +1,13 @@
+package com.android.tests.dependencies;
+
+public class Person {
+ private final String name;
+
+ public Person(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/build-system/tests/dependencies/src/main/java/com/android/tests/dependencies/ShowPeopleActivity.java b/build-system/tests/dependencies/src/main/java/com/android/tests/dependencies/ShowPeopleActivity.java
new file mode 100644
index 0000000..3a1ff01
--- /dev/null
+++ b/build-system/tests/dependencies/src/main/java/com/android/tests/dependencies/ShowPeopleActivity.java
@@ -0,0 +1,29 @@
+package com.android.tests.dependencies;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+import com.google.common.collect.Lists;
+
+import java.lang.String;
+
+public class ShowPeopleActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ String message = "People:";
+
+ Iterable<Person> people = Lists.newArrayList(new Person("fred"));
+ for (Person person : people) {
+ message += "\n * ";
+ message += person.getName();
+ }
+
+ TextView textView = new TextView(this);
+ textView.setTextSize(20);
+ textView.setText(message);
+
+ setContentView(textView);
+ }
+}
diff --git a/templates/gradle-projects/NewAndroidLibrary/root/res/drawable-hdpi/ic_launcher.png b/build-system/tests/dependencies/src/main/res/drawable-hdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
copy from templates/gradle-projects/NewAndroidLibrary/root/res/drawable-hdpi/ic_launcher.png
copy to build-system/tests/dependencies/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/dependencies/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/tests/dependencies/src/main/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..9923872
--- /dev/null
+++ b/build-system/tests/dependencies/src/main/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/templates/gradle-projects/NewAndroidLibrary/root/res/drawable-mdpi/ic_launcher.png b/build-system/tests/dependencies/src/main/res/drawable-mdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
copy from templates/gradle-projects/NewAndroidLibrary/root/res/drawable-mdpi/ic_launcher.png
copy to build-system/tests/dependencies/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/templates/gradle-projects/NewAndroidLibrary/root/res/drawable-xhdpi/ic_launcher.png b/build-system/tests/dependencies/src/main/res/drawable-xhdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
copy from templates/gradle-projects/NewAndroidLibrary/root/res/drawable-xhdpi/ic_launcher.png
copy to build-system/tests/dependencies/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/dependencies/src/main/res/layout/main.xml b/build-system/tests/dependencies/src/main/res/layout/main.xml
new file mode 100644
index 0000000..1884ac9
--- /dev/null
+++ b/build-system/tests/dependencies/src/main/res/layout/main.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/button_send"
+ android:onClick="sendMessage" />
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="New Text"
+ android:id="@+id/text" android:layout_gravity="center_horizontal|top"/>
+</LinearLayout>
diff --git a/build-system/tests/dependencies/src/main/res/values/strings.xml b/build-system/tests/dependencies/src/main/res/values/strings.xml
new file mode 100644
index 0000000..8eda275
--- /dev/null
+++ b/build-system/tests/dependencies/src/main/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">Basic App</string>
+ <string name="button_send">Go</string>
+ <string name="title_activity_display_message">People</string>
+</resources>
diff --git a/build-system/tests/dependencyChecker/build.gradle b/build-system/tests/dependencyChecker/build.gradle
new file mode 100644
index 0000000..e1f83c7
--- /dev/null
+++ b/build-system/tests/dependencyChecker/build.gradle
@@ -0,0 +1,22 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+apply plugin: 'android'
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ compile 'org.apache.httpcomponents:httpclient:4.2.5'
+}
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
\ No newline at end of file
diff --git a/build-system/tests/dependencyChecker/src/instrumentTest/java/com/android/tests/basic/MainTest.java b/build-system/tests/dependencyChecker/src/instrumentTest/java/com/android/tests/basic/MainTest.java
new file mode 100644
index 0000000..7cf7329
--- /dev/null
+++ b/build-system/tests/dependencyChecker/src/instrumentTest/java/com/android/tests/basic/MainTest.java
@@ -0,0 +1,38 @@
+package com.android.tests.basic;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+public class MainTest extends ActivityInstrumentationTestCase2<Main> {
+
+ private TextView mTextView;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
+ */
+ public MainTest() {
+ super(Main.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final Main a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+ mTextView = (TextView) a.findViewById(R.id.text);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView);
+ }
+}
+
diff --git a/build-system/tests/dependencyChecker/src/main/AndroidManifest.xml b/build-system/tests/dependencyChecker/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..4f8d570
--- /dev/null
+++ b/build-system/tests/dependencyChecker/src/main/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.basic">
+ <application android:label="@string/app_name" android:icon="@drawable/icon">
+ <activity android:name=".Main"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <uses-permission android:name="com.blah" />
+
+ <permission-group android:name="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+ <permission android:name="foo.permission.SEND_SMS"
+ android:permissionGroup="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+ <permission android:name="foo.blah.SEND_SMS"
+ android:permissionGroup="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+</manifest>
diff --git a/build-system/tests/dependencyChecker/src/main/assets/notice.txt b/build-system/tests/dependencyChecker/src/main/assets/notice.txt
new file mode 100644
index 0000000..33ff961
--- /dev/null
+++ b/build-system/tests/dependencyChecker/src/main/assets/notice.txt
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2005-2012, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/build-system/tests/dependencyChecker/src/main/java/com/android/tests/basic/Main.java b/build-system/tests/dependencyChecker/src/main/java/com/android/tests/basic/Main.java
new file mode 100644
index 0000000..2b0e698
--- /dev/null
+++ b/build-system/tests/dependencyChecker/src/main/java/com/android/tests/basic/Main.java
@@ -0,0 +1,15 @@
+package com.android.tests.basic;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class Main extends Activity
+{
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ }
+}
diff --git a/build-system/tests/dependencyChecker/src/main/res/drawable/icon.png b/build-system/tests/dependencyChecker/src/main/res/drawable/icon.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/dependencyChecker/src/main/res/drawable/icon.png
Binary files differ
diff --git a/build-system/tests/dependencyChecker/src/main/res/layout/main.xml b/build-system/tests/dependencyChecker/src/main/res/layout/main.xml
new file mode 100644
index 0000000..b199751
--- /dev/null
+++ b/build-system/tests/dependencyChecker/src/main/res/layout/main.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+<TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="Test App - Basic"
+ android:id="@+id/text"
+ />
+</LinearLayout>
+
diff --git a/build-system/tests/dependencyChecker/src/main/res/raw/notice.txt b/build-system/tests/dependencyChecker/src/main/res/raw/notice.txt
new file mode 100644
index 0000000..33ff961
--- /dev/null
+++ b/build-system/tests/dependencyChecker/src/main/res/raw/notice.txt
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2005-2012, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/build-system/tests/dependencyChecker/src/main/res/values/strings.xml b/build-system/tests/dependencyChecker/src/main/res/values/strings.xml
new file mode 100644
index 0000000..60ea2d0
--- /dev/null
+++ b/build-system/tests/dependencyChecker/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">_Test-Basic</string>
+</resources>
diff --git a/build-system/tests/dependencyChecker/src/release/res/values/strings.xml b/build-system/tests/dependencyChecker/src/release/res/values/strings.xml
new file mode 100644
index 0000000..532909c
--- /dev/null
+++ b/build-system/tests/dependencyChecker/src/release/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">_Test-Basic-Release</string>
+</resources>
diff --git a/build-system/tests/flavored/build.gradle b/build-system/tests/flavored/build.gradle
new file mode 100644
index 0000000..bb26a71
--- /dev/null
+++ b/build-system/tests/flavored/build.gradle
@@ -0,0 +1,42 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+apply plugin: 'android'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+ testBuildType = "staging"
+
+ defaultConfig {
+ }
+
+ productFlavors {
+ f1 {
+ packageName = "com.android.tests.flavored.f1"
+ versionName = "1.0.0-f1"
+ }
+ f2 {
+ packageName = "com.android.tests.flavored.f2"
+ versionName = "1.0.0-f2"
+ }
+ }
+
+ buildTypes {
+ debug {
+ packageNameSuffix = ".debug"
+ versionNameSuffix = ".D"
+ }
+ staging {
+ packageNameSuffix = ".staging"
+ versionNameSuffix = ".S"
+ signingConfig signingConfigs.debug
+ }
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/flavored/debug.keystore b/build-system/tests/flavored/debug.keystore
new file mode 100644
index 0000000..389278e
--- /dev/null
+++ b/build-system/tests/flavored/debug.keystore
Binary files differ
diff --git a/build-system/tests/flavored/src/f1/res/values/strings.xml b/build-system/tests/flavored/src/f1/res/values/strings.xml
new file mode 100644
index 0000000..7154c04
--- /dev/null
+++ b/build-system/tests/flavored/src/f1/res/values/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">_Test-Flavored-f1</string>
+ <string name="text">F1 text</string>
+</resources>
diff --git a/build-system/tests/flavored/src/f1Staging/res/values/strings.xml b/build-system/tests/flavored/src/f1Staging/res/values/strings.xml
new file mode 100644
index 0000000..782422e
--- /dev/null
+++ b/build-system/tests/flavored/src/f1Staging/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="text">F1-Staging text</string>
+</resources>
diff --git a/build-system/tests/flavored/src/f2/AndroidManifest.xml b/build-system/tests/flavored/src/f2/AndroidManifest.xml
new file mode 100644
index 0000000..ce0bb8d
--- /dev/null
+++ b/build-system/tests/flavored/src/f2/AndroidManifest.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="">
+ <application>
+ <activity android:name="com.android.tests.flavored.OtherActivity"
+ android:label="@string/other_activity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/build-system/tests/flavored/src/f2/java/com/android/tests/flavored/OtherActivity.java b/build-system/tests/flavored/src/f2/java/com/android/tests/flavored/OtherActivity.java
new file mode 100644
index 0000000..6ffac9c
--- /dev/null
+++ b/build-system/tests/flavored/src/f2/java/com/android/tests/flavored/OtherActivity.java
@@ -0,0 +1,15 @@
+package com.android.tests.flavored;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class OtherActivity extends Activity
+{
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main2);
+ }
+}
diff --git a/build-system/tests/flavored/src/f2/res/layout/main2.xml b/build-system/tests/flavored/src/f2/res/layout/main2.xml
new file mode 100644
index 0000000..90c3c43
--- /dev/null
+++ b/build-system/tests/flavored/src/f2/res/layout/main2.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+<TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="Test App - Flavored - f2"
+ android:id="@+id/text2"
+ />
+</LinearLayout>
+
diff --git a/build-system/tests/flavored/src/f2/res/values/strings.xml b/build-system/tests/flavored/src/f2/res/values/strings.xml
new file mode 100644
index 0000000..bb53fd4
--- /dev/null
+++ b/build-system/tests/flavored/src/f2/res/values/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">_Test-Flavored-f2</string>
+ <string name="other_activity">_Test-f2-act2</string>
+</resources>
diff --git a/build-system/tests/flavored/src/instrumentTest/java/com/android/tests/flavored/MainTest.java b/build-system/tests/flavored/src/instrumentTest/java/com/android/tests/flavored/MainTest.java
new file mode 100644
index 0000000..b22c53d
--- /dev/null
+++ b/build-system/tests/flavored/src/instrumentTest/java/com/android/tests/flavored/MainTest.java
@@ -0,0 +1,47 @@
+package com.android.tests.flavored;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+public class MainTest extends ActivityInstrumentationTestCase2<Main> {
+
+ private TextView mTextView;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
+ */
+ public MainTest() {
+ super(Main.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final Main a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+ mTextView = (TextView) a.findViewById(R.id.text);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView);
+ }
+
+ @MediumTest
+ public void testStagingText() {
+ if ("f1".equals(BuildConfig.FLAVOR)) {
+ assertEquals("F1-Staging text", mTextView.getText());
+ } else {
+ assertEquals("default text", mTextView.getText());
+ }
+ }
+}
+
diff --git a/build-system/tests/flavored/src/instrumentTestF2/java/com/android/tests/flavored/OtherActivityTest.java b/build-system/tests/flavored/src/instrumentTestF2/java/com/android/tests/flavored/OtherActivityTest.java
new file mode 100644
index 0000000..11d8c64
--- /dev/null
+++ b/build-system/tests/flavored/src/instrumentTestF2/java/com/android/tests/flavored/OtherActivityTest.java
@@ -0,0 +1,38 @@
+package com.android.tests.flavored;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+public class OtherActivityTest extends ActivityInstrumentationTestCase2<OtherActivity> {
+
+ private TextView mTextView;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link OtherActivity} activity.
+ */
+ public OtherActivityTest() {
+ super(OtherActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final OtherActivity a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+ mTextView = (TextView) a.findViewById(R.id.text2);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView);
+ }
+}
+
diff --git a/build-system/tests/flavored/src/main/AndroidManifest.xml b/build-system/tests/flavored/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..0d1c338
--- /dev/null
+++ b/build-system/tests/flavored/src/main/AndroidManifest.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.flavored">
+ <application android:label="@string/app_name" android:icon="@drawable/icon">
+ <activity android:name=".Main"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/build-system/tests/flavored/src/main/java/com/android/tests/flavored/Main.java b/build-system/tests/flavored/src/main/java/com/android/tests/flavored/Main.java
new file mode 100644
index 0000000..26debd3
--- /dev/null
+++ b/build-system/tests/flavored/src/main/java/com/android/tests/flavored/Main.java
@@ -0,0 +1,15 @@
+package com.android.tests.flavored;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class Main extends Activity
+{
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ }
+}
diff --git a/build-system/tests/flavored/src/main/res/drawable/icon.png b/build-system/tests/flavored/src/main/res/drawable/icon.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/flavored/src/main/res/drawable/icon.png
Binary files differ
diff --git a/build-system/tests/flavored/src/main/res/layout/main.xml b/build-system/tests/flavored/src/main/res/layout/main.xml
new file mode 100644
index 0000000..9d4e976
--- /dev/null
+++ b/build-system/tests/flavored/src/main/res/layout/main.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+<TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/text"
+ android:id="@+id/text"
+ />
+</LinearLayout>
+
diff --git a/build-system/tests/flavored/src/main/res/values/strings.xml b/build-system/tests/flavored/src/main/res/values/strings.xml
new file mode 100644
index 0000000..46d8260
--- /dev/null
+++ b/build-system/tests/flavored/src/main/res/values/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">###</string>
+ <string name="text">default text</string>
+</resources>
diff --git a/build-system/tests/flavorlib/app/build.gradle b/build-system/tests/flavorlib/app/build.gradle
new file mode 100644
index 0000000..ce9946c
--- /dev/null
+++ b/build-system/tests/flavorlib/app/build.gradle
@@ -0,0 +1,25 @@
+apply plugin: 'android'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+ productFlavors {
+ flavor1 {
+ packageName = "com.android.tests.flavorlib.app.flavor1"
+ }
+ flavor2 {
+ packageName = "com.android.tests.flavorlib.app.flavor2"
+ }
+ }
+
+ testOptions {
+ resultsDir = "$project.buildDir/foo/results"
+ reportDir = "$project.buildDir/foo/report"
+ }
+}
+
+dependencies {
+ flavor1Compile project(':lib1')
+ flavor2Compile project(':lib2')
+}
diff --git a/build-system/tests/flavorlib/app/proguard-project.txt b/build-system/tests/flavorlib/app/proguard-project.txt
new file mode 100644
index 0000000..349f80f
--- /dev/null
+++ b/build-system/tests/flavorlib/app/proguard-project.txt
@@ -0,0 +1,22 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+-adaptclassstrings
\ No newline at end of file
diff --git a/build-system/tests/flavorlib/app/src/flavor1/res/values/strings.xml b/build-system/tests/flavorlib/app/src/flavor1/res/values/strings.xml
new file mode 100644
index 0000000..b402efb
--- /dev/null
+++ b/build-system/tests/flavorlib/app/src/flavor1/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="app_name">flavorlib-app-f1</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/flavorlib/app/src/flavor2/res/values/strings.xml b/build-system/tests/flavorlib/app/src/flavor2/res/values/strings.xml
new file mode 100644
index 0000000..af1b43c
--- /dev/null
+++ b/build-system/tests/flavorlib/app/src/flavor2/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="app_name">flavorlib-app-f2</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/flavorlib/app/src/instrumentTest/java/com/android/tests/flavorlib/app/MainActivityTest.java b/build-system/tests/flavorlib/app/src/instrumentTest/java/com/android/tests/flavorlib/app/MainActivityTest.java
new file mode 100644
index 0000000..2788b27
--- /dev/null
+++ b/build-system/tests/flavorlib/app/src/instrumentTest/java/com/android/tests/flavorlib/app/MainActivityTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.flavorlib.app;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private TextView mAppTextView1;
+ private TextView mAppTextView2;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public MainActivityTest() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final MainActivity a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ mAppTextView1 = (TextView) a.findViewById(R.id.app_text1);
+ mAppTextView2 = (TextView) a.findViewById(R.id.app_text1);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mAppTextView1);
+ assertNotNull(mAppTextView2);
+ }
+
+ @MediumTest
+ public void testAndroidStrings() {
+ assertEquals(mAppTextView1.getText(), "SUCCESS-APP");
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals(mAppTextView2.getText(), "SUCCESS-APP");
+ }
+}
diff --git a/build-system/tests/flavorlib/app/src/instrumentTestFlavor1/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java b/build-system/tests/flavorlib/app/src/instrumentTestFlavor1/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
new file mode 100644
index 0000000..6dd5088
--- /dev/null
+++ b/build-system/tests/flavorlib/app/src/instrumentTestFlavor1/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.flavorlib.app;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class MainActivityFlavorTest extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private TextView mLibTextView1;
+ private TextView mLibTextView2;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public MainActivityFlavorTest() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final MainActivity a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ mLibTextView1 = (TextView) a.findViewById(R.id.lib_text1);
+ mLibTextView2 = (TextView) a.findViewById(R.id.lib_text2);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mLibTextView1);
+ assertNotNull(mLibTextView2);
+ }
+
+ @MediumTest
+ public void testAndroidStrings() {
+ assertEquals(mLibTextView1.getText(), "SUCCESS-LIB1");
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals(mLibTextView2.getText(), "SUCCESS-LIB1");
+ }
+}
diff --git a/build-system/tests/flavorlib/app/src/instrumentTestFlavor2/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java b/build-system/tests/flavorlib/app/src/instrumentTestFlavor2/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
new file mode 100644
index 0000000..56988c0
--- /dev/null
+++ b/build-system/tests/flavorlib/app/src/instrumentTestFlavor2/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.flavorlib.app;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class MainActivityFlavorTest extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private TextView mLibTextView1;
+ private TextView mLibTextView2;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public MainActivityFlavorTest() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final MainActivity a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ mLibTextView1 = (TextView) a.findViewById(R.id.lib_text1);
+ mLibTextView2 = (TextView) a.findViewById(R.id.lib_text2);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mLibTextView1);
+ assertNotNull(mLibTextView2);
+ }
+
+ @MediumTest
+ public void testAndroidStrings() {
+ assertEquals(mLibTextView1.getText(), "SUCCESS-LIB2");
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals(mLibTextView2.getText(), "SUCCESS-LIB2");
+ }
+}
diff --git a/build-system/tests/flavorlib/app/src/main/AndroidManifest.xml b/build-system/tests/flavorlib/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..45758cc
--- /dev/null
+++ b/build-system/tests/flavorlib/app/src/main/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.flavorlib.app"
+ android:versionCode="1"
+ android:versionName="1.0" xmlns:tools="http://schemas.android.com/tools">
+
+ <uses-sdk
+ android:minSdkVersion="15"
+ tools:ignore="UsesMinSdkAttributes" />
+
+ <application
+ android:icon="@drawable/icon"
+ android:label="@string/app_name" >
+ <activity
+ android:name=".MainActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/build-system/tests/flavorlib/app/src/main/java/com/android/tests/flavorlib/app/App.java b/build-system/tests/flavorlib/app/src/main/java/com/android/tests/flavorlib/app/App.java
new file mode 100644
index 0000000..0312a6c
--- /dev/null
+++ b/build-system/tests/flavorlib/app/src/main/java/com/android/tests/flavorlib/app/App.java
@@ -0,0 +1,43 @@
+package com.android.tests.flavorlib.app;
+
+import android.app.Activity;
+import android.widget.TextView;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+public class App {
+
+ public static void handleTextView(Activity a) {
+ TextView tv = (TextView) a.findViewById(R.id.app_text2);
+ if (tv != null) {
+ tv.setText(getContent());
+ }
+ }
+
+ private static String getContent() {
+ InputStream input = App.class.getResourceAsStream("App.txt");
+ if (input == null) {
+ return "FAILED TO FIND App.txt";
+ }
+
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+
+ return reader.readLine();
+ } catch (IOException e) {
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ return "FAILED TO READ CONTENT";
+ }
+}
diff --git a/build-system/tests/flavorlib/app/src/main/java/com/android/tests/flavorlib/app/MainActivity.java b/build-system/tests/flavorlib/app/src/main/java/com/android/tests/flavorlib/app/MainActivity.java
new file mode 100644
index 0000000..0d6f8a6
--- /dev/null
+++ b/build-system/tests/flavorlib/app/src/main/java/com/android/tests/flavorlib/app/MainActivity.java
@@ -0,0 +1,18 @@
+package com.android.tests.flavorlib.app;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.tests.flavorlib.lib.Lib;
+
+public class MainActivity extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ App.handleTextView(this);
+ Lib.handleTextView(this);
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/flavorlib/app/src/main/res/drawable-hdpi/icon.png b/build-system/tests/flavorlib/app/src/main/res/drawable-hdpi/icon.png
new file mode 100644
index 0000000..8074c4c
--- /dev/null
+++ b/build-system/tests/flavorlib/app/src/main/res/drawable-hdpi/icon.png
Binary files differ
diff --git a/build-system/tests/flavorlib/app/src/main/res/drawable-ldpi/icon.png b/build-system/tests/flavorlib/app/src/main/res/drawable-ldpi/icon.png
new file mode 100644
index 0000000..1095584
--- /dev/null
+++ b/build-system/tests/flavorlib/app/src/main/res/drawable-ldpi/icon.png
Binary files differ
diff --git a/build-system/tests/flavorlib/app/src/main/res/drawable-mdpi/icon.png b/build-system/tests/flavorlib/app/src/main/res/drawable-mdpi/icon.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/flavorlib/app/src/main/res/drawable-mdpi/icon.png
Binary files differ
diff --git a/build-system/tests/flavorlib/app/src/main/res/layout/main.xml b/build-system/tests/flavorlib/app/src/main/res/layout/main.xml
new file mode 100644
index 0000000..6761bef
--- /dev/null
+++ b/build-system/tests/flavorlib/app/src/main/res/layout/main.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/app_text1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/app_string" />
+
+ <TextView
+ android:id="@+id/app_text2"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+ <include layout="@layout/lib_main" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/build-system/tests/flavorlib/app/src/main/res/values/strings.xml b/build-system/tests/flavorlib/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..190a400
--- /dev/null
+++ b/build-system/tests/flavorlib/app/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="app_name">flavorlib-app</string>
+ <string name="app_string">SUCCESS-APP</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/flavorlib/app/src/main/resources/com/android/tests/flavorlib/app/App.txt b/build-system/tests/flavorlib/app/src/main/resources/com/android/tests/flavorlib/app/App.txt
new file mode 100644
index 0000000..084e7d0
--- /dev/null
+++ b/build-system/tests/flavorlib/app/src/main/resources/com/android/tests/flavorlib/app/App.txt
@@ -0,0 +1 @@
+SUCCESS-APP
\ No newline at end of file
diff --git a/build-system/tests/flavorlib/build.gradle b/build-system/tests/flavorlib/build.gradle
new file mode 100644
index 0000000..a8fdb64
--- /dev/null
+++ b/build-system/tests/flavorlib/build.gradle
@@ -0,0 +1,10 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+
+apply plugin: 'android-reporting'
\ No newline at end of file
diff --git a/build-system/tests/flavorlib/lib1/build.gradle b/build-system/tests/flavorlib/lib1/build.gradle
new file mode 100644
index 0000000..4b2a733
--- /dev/null
+++ b/build-system/tests/flavorlib/lib1/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'android-library'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
\ No newline at end of file
diff --git a/build-system/tests/flavorlib/lib1/proguard-project.txt b/build-system/tests/flavorlib/lib1/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/build-system/tests/flavorlib/lib1/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/build-system/tests/flavorlib/lib1/src/instrumentTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java b/build-system/tests/flavorlib/lib1/src/instrumentTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java
new file mode 100644
index 0000000..970fcbe
--- /dev/null
+++ b/build-system/tests/flavorlib/lib1/src/instrumentTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.flavorlib.lib;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+import com.android.tests.flavorlib.lib.R;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private TextView mTextView1;
+ private TextView mTextView2;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public MainActivityTest() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final MainActivity a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ mTextView1 = (TextView) a.findViewById(R.id.lib_text1);
+ mTextView2 = (TextView) a.findViewById(R.id.lib_text2);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView1);
+ assertNotNull(mTextView2);
+ }
+
+ @MediumTest
+ public void testAndroidStrings() {
+ assertEquals("SUCCESS-LIB1", mTextView1.getText());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB1", mTextView2.getText());
+ }
+}
diff --git a/build-system/tests/flavorlib/lib1/src/main/AndroidManifest.xml b/build-system/tests/flavorlib/lib1/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..44bc277
--- /dev/null
+++ b/build-system/tests/flavorlib/lib1/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.flavorlib.lib"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/lib_name" >
+ <activity
+ android:name="MainActivity"
+ android:label="@string/lib_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/build-system/tests/flavorlib/lib1/src/main/java/com/android/tests/flavorlib/lib/Lib.java b/build-system/tests/flavorlib/lib1/src/main/java/com/android/tests/flavorlib/lib/Lib.java
new file mode 100644
index 0000000..1e981c2
--- /dev/null
+++ b/build-system/tests/flavorlib/lib1/src/main/java/com/android/tests/flavorlib/lib/Lib.java
@@ -0,0 +1,43 @@
+package com.android.tests.flavorlib.lib;
+
+import android.app.Activity;
+import android.widget.TextView;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+public class Lib {
+
+ public static void handleTextView(Activity a) {
+ TextView tv = (TextView) a.findViewById(R.id.lib_text2);
+ if (tv != null) {
+ tv.setText(getContent());
+ }
+ }
+
+ private static String getContent() {
+ InputStream input = Lib.class.getResourceAsStream("Lib.txt");
+ if (input == null) {
+ return "FAILED TO FIND Lib.txt";
+ }
+
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+
+ return reader.readLine();
+ } catch (IOException e) {
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ return "FAILED TO READ CONTENT";
+ }
+}
diff --git a/build-system/tests/flavorlib/lib1/src/main/java/com/android/tests/flavorlib/lib/MainActivity.java b/build-system/tests/flavorlib/lib1/src/main/java/com/android/tests/flavorlib/lib/MainActivity.java
new file mode 100644
index 0000000..8a13e9b
--- /dev/null
+++ b/build-system/tests/flavorlib/lib1/src/main/java/com/android/tests/flavorlib/lib/MainActivity.java
@@ -0,0 +1,15 @@
+package com.android.tests.flavorlib.lib;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class MainActivity extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.lib_main);
+
+ Lib.handleTextView(this);
+ }
+}
diff --git a/build-system/tests/flavorlib/lib1/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/tests/flavorlib/lib1/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..8074c4c
--- /dev/null
+++ b/build-system/tests/flavorlib/lib1/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/flavorlib/lib1/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/tests/flavorlib/lib1/src/main/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..1095584
--- /dev/null
+++ b/build-system/tests/flavorlib/lib1/src/main/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/flavorlib/lib1/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/tests/flavorlib/lib1/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/flavorlib/lib1/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/flavorlib/lib1/src/main/res/layout/lib_main.xml b/build-system/tests/flavorlib/lib1/src/main/res/layout/lib_main.xml
new file mode 100644
index 0000000..47e792a
--- /dev/null
+++ b/build-system/tests/flavorlib/lib1/src/main/res/layout/lib_main.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/lib_text1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/lib_string" />
+
+ <TextView
+ android:id="@+id/lib_text2"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/build-system/tests/flavorlib/lib1/src/main/res/values/strings.xml b/build-system/tests/flavorlib/lib1/src/main/res/values/strings.xml
new file mode 100644
index 0000000..ca7dcdb
--- /dev/null
+++ b/build-system/tests/flavorlib/lib1/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="lib_name">flavorlib-lib1</string>
+ <string name="lib_string">SUCCESS-LIB1</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/flavorlib/lib1/src/main/resources/com/android/tests/flavorlib/lib/Lib.txt b/build-system/tests/flavorlib/lib1/src/main/resources/com/android/tests/flavorlib/lib/Lib.txt
new file mode 100644
index 0000000..452e397
--- /dev/null
+++ b/build-system/tests/flavorlib/lib1/src/main/resources/com/android/tests/flavorlib/lib/Lib.txt
@@ -0,0 +1 @@
+SUCCESS-LIB1
\ No newline at end of file
diff --git a/build-system/tests/flavorlib/lib2/build.gradle b/build-system/tests/flavorlib/lib2/build.gradle
new file mode 100644
index 0000000..4b2a733
--- /dev/null
+++ b/build-system/tests/flavorlib/lib2/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'android-library'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
\ No newline at end of file
diff --git a/build-system/tests/flavorlib/lib2/proguard-project.txt b/build-system/tests/flavorlib/lib2/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/build-system/tests/flavorlib/lib2/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/build-system/tests/flavorlib/lib2/src/instrumentTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java b/build-system/tests/flavorlib/lib2/src/instrumentTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java
new file mode 100644
index 0000000..05a12e5
--- /dev/null
+++ b/build-system/tests/flavorlib/lib2/src/instrumentTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.flavorlib.lib;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+import com.android.tests.flavorlib.lib.R;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private TextView mTextView1;
+ private TextView mTextView2;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public MainActivityTest() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final MainActivity a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ mTextView1 = (TextView) a.findViewById(R.id.lib_text1);
+ mTextView2 = (TextView) a.findViewById(R.id.lib_text2);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView1);
+ assertNotNull(mTextView2);
+ }
+
+ @MediumTest
+ public void testAndroidStrings() {
+ assertEquals("SUCCESS-LIB2", mTextView1.getText());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB2", mTextView2.getText());
+ }
+}
diff --git a/build-system/tests/flavorlib/lib2/src/main/AndroidManifest.xml b/build-system/tests/flavorlib/lib2/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..44bc277
--- /dev/null
+++ b/build-system/tests/flavorlib/lib2/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.flavorlib.lib"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/lib_name" >
+ <activity
+ android:name="MainActivity"
+ android:label="@string/lib_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/build-system/tests/flavorlib/lib2/src/main/java/com/android/tests/flavorlib/lib/Lib.java b/build-system/tests/flavorlib/lib2/src/main/java/com/android/tests/flavorlib/lib/Lib.java
new file mode 100644
index 0000000..4d8503c
--- /dev/null
+++ b/build-system/tests/flavorlib/lib2/src/main/java/com/android/tests/flavorlib/lib/Lib.java
@@ -0,0 +1,43 @@
+package com.android.tests.flavorlib.lib;
+
+import android.app.Activity;
+import android.widget.TextView;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+public class Lib {
+
+ public static void handleTextView(Activity a) {
+ TextView tv = (TextView) a.findViewById(R.id.lib_text2);
+ if (tv != null) {
+ tv.setText(getContent());
+ }
+ }
+
+ private static String getContent() {
+ InputStream input = Lib.class.getResourceAsStream("Lib.txt");
+ if (input == null) {
+ return "FAILED TO FIND Lib2.txt";
+ }
+
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+
+ return reader.readLine();
+ } catch (IOException e) {
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ return "FAILED TO READ CONTENT";
+ }
+}
diff --git a/build-system/tests/flavorlib/lib2/src/main/java/com/android/tests/flavorlib/lib/MainActivity.java b/build-system/tests/flavorlib/lib2/src/main/java/com/android/tests/flavorlib/lib/MainActivity.java
new file mode 100644
index 0000000..8a13e9b
--- /dev/null
+++ b/build-system/tests/flavorlib/lib2/src/main/java/com/android/tests/flavorlib/lib/MainActivity.java
@@ -0,0 +1,15 @@
+package com.android.tests.flavorlib.lib;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class MainActivity extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.lib_main);
+
+ Lib.handleTextView(this);
+ }
+}
diff --git a/build-system/tests/flavorlib/lib2/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/tests/flavorlib/lib2/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..8074c4c
--- /dev/null
+++ b/build-system/tests/flavorlib/lib2/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/flavorlib/lib2/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/tests/flavorlib/lib2/src/main/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..1095584
--- /dev/null
+++ b/build-system/tests/flavorlib/lib2/src/main/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/flavorlib/lib2/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/tests/flavorlib/lib2/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/flavorlib/lib2/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/flavorlib/lib2/src/main/res/layout/lib_main.xml b/build-system/tests/flavorlib/lib2/src/main/res/layout/lib_main.xml
new file mode 100644
index 0000000..47e792a
--- /dev/null
+++ b/build-system/tests/flavorlib/lib2/src/main/res/layout/lib_main.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/lib_text1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/lib_string" />
+
+ <TextView
+ android:id="@+id/lib_text2"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/build-system/tests/flavorlib/lib2/src/main/res/values/strings.xml b/build-system/tests/flavorlib/lib2/src/main/res/values/strings.xml
new file mode 100644
index 0000000..e27cb40
--- /dev/null
+++ b/build-system/tests/flavorlib/lib2/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="lib_name">flavorlib-lib2</string>
+ <string name="lib_string">SUCCESS-LIB2</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/flavorlib/lib2/src/main/resources/com/android/tests/flavorlib/lib/Lib.txt b/build-system/tests/flavorlib/lib2/src/main/resources/com/android/tests/flavorlib/lib/Lib.txt
new file mode 100644
index 0000000..94cabe4
--- /dev/null
+++ b/build-system/tests/flavorlib/lib2/src/main/resources/com/android/tests/flavorlib/lib/Lib.txt
@@ -0,0 +1 @@
+SUCCESS-LIB2
\ No newline at end of file
diff --git a/build-system/tests/flavorlib/settings.gradle b/build-system/tests/flavorlib/settings.gradle
new file mode 100644
index 0000000..c72f855
--- /dev/null
+++ b/build-system/tests/flavorlib/settings.gradle
@@ -0,0 +1,3 @@
+include 'app'
+include 'lib1'
+include 'lib2'
diff --git a/build-system/tests/flavorlibWithFailedTests/app/build.gradle b/build-system/tests/flavorlibWithFailedTests/app/build.gradle
new file mode 100644
index 0000000..87e7230
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/app/build.gradle
@@ -0,0 +1,20 @@
+apply plugin: 'android'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+ productFlavors {
+ flavor1 {
+ packageName = "com.android.tests.flavorlib.app.flavor1"
+ }
+ flavor2 {
+ packageName = "com.android.tests.flavorlib.app.flavor2"
+ }
+ }
+}
+
+dependencies {
+ flavor1Compile project(':lib1')
+ flavor2Compile project(':lib2')
+}
diff --git a/build-system/tests/flavorlibWithFailedTests/app/proguard-project.txt b/build-system/tests/flavorlibWithFailedTests/app/proguard-project.txt
new file mode 100644
index 0000000..349f80f
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/app/proguard-project.txt
@@ -0,0 +1,22 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+-adaptclassstrings
\ No newline at end of file
diff --git a/build-system/tests/flavorlibWithFailedTests/app/src/flavor1/res/values/strings.xml b/build-system/tests/flavorlibWithFailedTests/app/src/flavor1/res/values/strings.xml
new file mode 100644
index 0000000..b402efb
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/app/src/flavor1/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="app_name">flavorlib-app-f1</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/flavorlibWithFailedTests/app/src/flavor2/res/values/strings.xml b/build-system/tests/flavorlibWithFailedTests/app/src/flavor2/res/values/strings.xml
new file mode 100644
index 0000000..af1b43c
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/app/src/flavor2/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="app_name">flavorlib-app-f2</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/flavorlibWithFailedTests/app/src/instrumentTest/java/com/android/tests/flavorlib/app/MainActivityTest.java b/build-system/tests/flavorlibWithFailedTests/app/src/instrumentTest/java/com/android/tests/flavorlib/app/MainActivityTest.java
new file mode 100644
index 0000000..2788b27
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/app/src/instrumentTest/java/com/android/tests/flavorlib/app/MainActivityTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.flavorlib.app;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private TextView mAppTextView1;
+ private TextView mAppTextView2;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public MainActivityTest() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final MainActivity a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ mAppTextView1 = (TextView) a.findViewById(R.id.app_text1);
+ mAppTextView2 = (TextView) a.findViewById(R.id.app_text1);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mAppTextView1);
+ assertNotNull(mAppTextView2);
+ }
+
+ @MediumTest
+ public void testAndroidStrings() {
+ assertEquals(mAppTextView1.getText(), "SUCCESS-APP");
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals(mAppTextView2.getText(), "SUCCESS-APP");
+ }
+}
diff --git a/build-system/tests/flavorlibWithFailedTests/app/src/instrumentTestFlavor1/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java b/build-system/tests/flavorlibWithFailedTests/app/src/instrumentTestFlavor1/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
new file mode 100644
index 0000000..d4b3ded
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/app/src/instrumentTestFlavor1/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.flavorlib.app;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.widget.TextView;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class MainActivityFlavorTest extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private TextView mLibTextView1;
+ private TextView mLibTextView2;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public MainActivityFlavorTest() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final MainActivity a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ mLibTextView1 = (TextView) a.findViewById(R.id.lib_text1);
+ mLibTextView2 = (TextView) a.findViewById(R.id.lib_text2);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mLibTextView1);
+ assertNotNull(mLibTextView2);
+ }
+
+ @MediumTest
+ public void testAndroidStrings() {
+ assertEquals(mLibTextView1.getText(), "SUCCESS-LIB1");
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals(mLibTextView2.getText(), "SUCCESS-LIB1");
+ }
+
+ @SmallTest
+ public void testFailureOk() {
+ assertTrue("Testing failing test", false);
+ }
+}
diff --git a/build-system/tests/flavorlibWithFailedTests/app/src/instrumentTestFlavor2/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java b/build-system/tests/flavorlibWithFailedTests/app/src/instrumentTestFlavor2/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
new file mode 100644
index 0000000..8a4b8f7
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/app/src/instrumentTestFlavor2/java/com/android/tests/flavorlib/app/MainActivityFlavorTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.flavorlib.app;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.widget.TextView;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class MainActivityFlavorTest extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private TextView mLibTextView1;
+ private TextView mLibTextView2;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public MainActivityFlavorTest() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final MainActivity a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ mLibTextView1 = (TextView) a.findViewById(R.id.lib_text1);
+ mLibTextView2 = (TextView) a.findViewById(R.id.lib_text2);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mLibTextView1);
+ assertNotNull(mLibTextView2);
+ }
+
+ @MediumTest
+ public void testAndroidStrings() {
+ assertEquals(mLibTextView1.getText(), "SUCCESS-LIB2");
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals(mLibTextView2.getText(), "SUCCESS-LIB2");
+ }
+
+ @SmallTest
+ public void testIsApi17() {
+ assertEquals(17, android.os.Build.VERSION.SDK_INT);
+ }
+}
diff --git a/build-system/tests/flavorlibWithFailedTests/app/src/main/AndroidManifest.xml b/build-system/tests/flavorlibWithFailedTests/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..45758cc
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/app/src/main/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.flavorlib.app"
+ android:versionCode="1"
+ android:versionName="1.0" xmlns:tools="http://schemas.android.com/tools">
+
+ <uses-sdk
+ android:minSdkVersion="15"
+ tools:ignore="UsesMinSdkAttributes" />
+
+ <application
+ android:icon="@drawable/icon"
+ android:label="@string/app_name" >
+ <activity
+ android:name=".MainActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/build-system/tests/flavorlibWithFailedTests/app/src/main/java/com/android/tests/flavorlib/app/App.java b/build-system/tests/flavorlibWithFailedTests/app/src/main/java/com/android/tests/flavorlib/app/App.java
new file mode 100644
index 0000000..0312a6c
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/app/src/main/java/com/android/tests/flavorlib/app/App.java
@@ -0,0 +1,43 @@
+package com.android.tests.flavorlib.app;
+
+import android.app.Activity;
+import android.widget.TextView;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+public class App {
+
+ public static void handleTextView(Activity a) {
+ TextView tv = (TextView) a.findViewById(R.id.app_text2);
+ if (tv != null) {
+ tv.setText(getContent());
+ }
+ }
+
+ private static String getContent() {
+ InputStream input = App.class.getResourceAsStream("App.txt");
+ if (input == null) {
+ return "FAILED TO FIND App.txt";
+ }
+
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+
+ return reader.readLine();
+ } catch (IOException e) {
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ return "FAILED TO READ CONTENT";
+ }
+}
diff --git a/build-system/tests/flavorlibWithFailedTests/app/src/main/java/com/android/tests/flavorlib/app/MainActivity.java b/build-system/tests/flavorlibWithFailedTests/app/src/main/java/com/android/tests/flavorlib/app/MainActivity.java
new file mode 100644
index 0000000..0d6f8a6
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/app/src/main/java/com/android/tests/flavorlib/app/MainActivity.java
@@ -0,0 +1,18 @@
+package com.android.tests.flavorlib.app;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.tests.flavorlib.lib.Lib;
+
+public class MainActivity extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ App.handleTextView(this);
+ Lib.handleTextView(this);
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/flavorlibWithFailedTests/app/src/main/res/drawable-hdpi/icon.png b/build-system/tests/flavorlibWithFailedTests/app/src/main/res/drawable-hdpi/icon.png
new file mode 100644
index 0000000..8074c4c
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/app/src/main/res/drawable-hdpi/icon.png
Binary files differ
diff --git a/build-system/tests/flavorlibWithFailedTests/app/src/main/res/drawable-ldpi/icon.png b/build-system/tests/flavorlibWithFailedTests/app/src/main/res/drawable-ldpi/icon.png
new file mode 100644
index 0000000..1095584
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/app/src/main/res/drawable-ldpi/icon.png
Binary files differ
diff --git a/build-system/tests/flavorlibWithFailedTests/app/src/main/res/drawable-mdpi/icon.png b/build-system/tests/flavorlibWithFailedTests/app/src/main/res/drawable-mdpi/icon.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/app/src/main/res/drawable-mdpi/icon.png
Binary files differ
diff --git a/build-system/tests/flavorlibWithFailedTests/app/src/main/res/layout/main.xml b/build-system/tests/flavorlibWithFailedTests/app/src/main/res/layout/main.xml
new file mode 100644
index 0000000..6761bef
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/app/src/main/res/layout/main.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/app_text1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/app_string" />
+
+ <TextView
+ android:id="@+id/app_text2"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+ <include layout="@layout/lib_main" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/build-system/tests/flavorlibWithFailedTests/app/src/main/res/values/strings.xml b/build-system/tests/flavorlibWithFailedTests/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..190a400
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/app/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="app_name">flavorlib-app</string>
+ <string name="app_string">SUCCESS-APP</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/flavorlibWithFailedTests/app/src/main/resources/com/android/tests/flavorlib/app/App.txt b/build-system/tests/flavorlibWithFailedTests/app/src/main/resources/com/android/tests/flavorlib/app/App.txt
new file mode 100644
index 0000000..084e7d0
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/app/src/main/resources/com/android/tests/flavorlib/app/App.txt
@@ -0,0 +1 @@
+SUCCESS-APP
\ No newline at end of file
diff --git a/build-system/tests/flavorlibWithFailedTests/build.gradle b/build-system/tests/flavorlibWithFailedTests/build.gradle
new file mode 100644
index 0000000..a8fdb64
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/build.gradle
@@ -0,0 +1,10 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+
+apply plugin: 'android-reporting'
\ No newline at end of file
diff --git a/build-system/tests/flavorlibWithFailedTests/lib1/build.gradle b/build-system/tests/flavorlibWithFailedTests/lib1/build.gradle
new file mode 100644
index 0000000..4b2a733
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/lib1/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'android-library'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
\ No newline at end of file
diff --git a/build-system/tests/flavorlibWithFailedTests/lib1/proguard-project.txt b/build-system/tests/flavorlibWithFailedTests/lib1/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/lib1/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/build-system/tests/flavorlibWithFailedTests/lib1/src/instrumentTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java b/build-system/tests/flavorlibWithFailedTests/lib1/src/instrumentTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java
new file mode 100644
index 0000000..26e9518
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/lib1/src/instrumentTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.flavorlib.lib;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.widget.TextView;
+
+import com.android.tests.flavorlib.lib.R;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private TextView mTextView1;
+ private TextView mTextView2;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public MainActivityTest() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final MainActivity a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ mTextView1 = (TextView) a.findViewById(R.id.lib_text1);
+ mTextView2 = (TextView) a.findViewById(R.id.lib_text2);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView1);
+ assertNotNull(mTextView2);
+ }
+
+ @MediumTest
+ public void testAndroidStrings() {
+ assertEquals("SUCCESS-LIB1", mTextView1.getText());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB1", mTextView2.getText());
+ }
+
+ @SmallTest
+ public void testFailureOk() {
+ assertTrue("Testing failing test", false);
+ }
+
+}
diff --git a/build-system/tests/flavorlibWithFailedTests/lib1/src/main/AndroidManifest.xml b/build-system/tests/flavorlibWithFailedTests/lib1/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..44bc277
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/lib1/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.flavorlib.lib"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/lib_name" >
+ <activity
+ android:name="MainActivity"
+ android:label="@string/lib_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/build-system/tests/flavorlibWithFailedTests/lib1/src/main/java/com/android/tests/flavorlib/lib/Lib.java b/build-system/tests/flavorlibWithFailedTests/lib1/src/main/java/com/android/tests/flavorlib/lib/Lib.java
new file mode 100644
index 0000000..1e981c2
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/lib1/src/main/java/com/android/tests/flavorlib/lib/Lib.java
@@ -0,0 +1,43 @@
+package com.android.tests.flavorlib.lib;
+
+import android.app.Activity;
+import android.widget.TextView;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+public class Lib {
+
+ public static void handleTextView(Activity a) {
+ TextView tv = (TextView) a.findViewById(R.id.lib_text2);
+ if (tv != null) {
+ tv.setText(getContent());
+ }
+ }
+
+ private static String getContent() {
+ InputStream input = Lib.class.getResourceAsStream("Lib.txt");
+ if (input == null) {
+ return "FAILED TO FIND Lib.txt";
+ }
+
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+
+ return reader.readLine();
+ } catch (IOException e) {
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ return "FAILED TO READ CONTENT";
+ }
+}
diff --git a/build-system/tests/flavorlibWithFailedTests/lib1/src/main/java/com/android/tests/flavorlib/lib/MainActivity.java b/build-system/tests/flavorlibWithFailedTests/lib1/src/main/java/com/android/tests/flavorlib/lib/MainActivity.java
new file mode 100644
index 0000000..8a13e9b
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/lib1/src/main/java/com/android/tests/flavorlib/lib/MainActivity.java
@@ -0,0 +1,15 @@
+package com.android.tests.flavorlib.lib;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class MainActivity extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.lib_main);
+
+ Lib.handleTextView(this);
+ }
+}
diff --git a/build-system/tests/flavorlibWithFailedTests/lib1/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/tests/flavorlibWithFailedTests/lib1/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..8074c4c
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/lib1/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/flavorlibWithFailedTests/lib1/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/tests/flavorlibWithFailedTests/lib1/src/main/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..1095584
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/lib1/src/main/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/flavorlibWithFailedTests/lib1/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/tests/flavorlibWithFailedTests/lib1/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/lib1/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/flavorlibWithFailedTests/lib1/src/main/res/layout/lib_main.xml b/build-system/tests/flavorlibWithFailedTests/lib1/src/main/res/layout/lib_main.xml
new file mode 100644
index 0000000..47e792a
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/lib1/src/main/res/layout/lib_main.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/lib_text1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/lib_string" />
+
+ <TextView
+ android:id="@+id/lib_text2"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/build-system/tests/flavorlibWithFailedTests/lib1/src/main/res/values/strings.xml b/build-system/tests/flavorlibWithFailedTests/lib1/src/main/res/values/strings.xml
new file mode 100644
index 0000000..ca7dcdb
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/lib1/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="lib_name">flavorlib-lib1</string>
+ <string name="lib_string">SUCCESS-LIB1</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/flavorlibWithFailedTests/lib1/src/main/resources/com/android/tests/flavorlib/lib/Lib.txt b/build-system/tests/flavorlibWithFailedTests/lib1/src/main/resources/com/android/tests/flavorlib/lib/Lib.txt
new file mode 100644
index 0000000..452e397
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/lib1/src/main/resources/com/android/tests/flavorlib/lib/Lib.txt
@@ -0,0 +1 @@
+SUCCESS-LIB1
\ No newline at end of file
diff --git a/build-system/tests/flavorlibWithFailedTests/lib2/build.gradle b/build-system/tests/flavorlibWithFailedTests/lib2/build.gradle
new file mode 100644
index 0000000..4b2a733
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/lib2/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'android-library'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
\ No newline at end of file
diff --git a/build-system/tests/flavorlibWithFailedTests/lib2/proguard-project.txt b/build-system/tests/flavorlibWithFailedTests/lib2/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/lib2/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/build-system/tests/flavorlibWithFailedTests/lib2/src/instrumentTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java b/build-system/tests/flavorlibWithFailedTests/lib2/src/instrumentTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java
new file mode 100644
index 0000000..05a12e5
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/lib2/src/instrumentTest/java/com/android/tests/flavorlib/lib/MainActivityTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.flavorlib.lib;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+import com.android.tests.flavorlib.lib.R;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private TextView mTextView1;
+ private TextView mTextView2;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public MainActivityTest() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final MainActivity a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ mTextView1 = (TextView) a.findViewById(R.id.lib_text1);
+ mTextView2 = (TextView) a.findViewById(R.id.lib_text2);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView1);
+ assertNotNull(mTextView2);
+ }
+
+ @MediumTest
+ public void testAndroidStrings() {
+ assertEquals("SUCCESS-LIB2", mTextView1.getText());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB2", mTextView2.getText());
+ }
+}
diff --git a/build-system/tests/flavorlibWithFailedTests/lib2/src/main/AndroidManifest.xml b/build-system/tests/flavorlibWithFailedTests/lib2/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..44bc277
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/lib2/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.flavorlib.lib"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/lib_name" >
+ <activity
+ android:name="MainActivity"
+ android:label="@string/lib_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/build-system/tests/flavorlibWithFailedTests/lib2/src/main/java/com/android/tests/flavorlib/lib/Lib.java b/build-system/tests/flavorlibWithFailedTests/lib2/src/main/java/com/android/tests/flavorlib/lib/Lib.java
new file mode 100644
index 0000000..4d8503c
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/lib2/src/main/java/com/android/tests/flavorlib/lib/Lib.java
@@ -0,0 +1,43 @@
+package com.android.tests.flavorlib.lib;
+
+import android.app.Activity;
+import android.widget.TextView;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+public class Lib {
+
+ public static void handleTextView(Activity a) {
+ TextView tv = (TextView) a.findViewById(R.id.lib_text2);
+ if (tv != null) {
+ tv.setText(getContent());
+ }
+ }
+
+ private static String getContent() {
+ InputStream input = Lib.class.getResourceAsStream("Lib.txt");
+ if (input == null) {
+ return "FAILED TO FIND Lib2.txt";
+ }
+
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+
+ return reader.readLine();
+ } catch (IOException e) {
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ return "FAILED TO READ CONTENT";
+ }
+}
diff --git a/build-system/tests/flavorlibWithFailedTests/lib2/src/main/java/com/android/tests/flavorlib/lib/MainActivity.java b/build-system/tests/flavorlibWithFailedTests/lib2/src/main/java/com/android/tests/flavorlib/lib/MainActivity.java
new file mode 100644
index 0000000..8a13e9b
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/lib2/src/main/java/com/android/tests/flavorlib/lib/MainActivity.java
@@ -0,0 +1,15 @@
+package com.android.tests.flavorlib.lib;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class MainActivity extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.lib_main);
+
+ Lib.handleTextView(this);
+ }
+}
diff --git a/build-system/tests/flavorlibWithFailedTests/lib2/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/tests/flavorlibWithFailedTests/lib2/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..8074c4c
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/lib2/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/flavorlibWithFailedTests/lib2/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/tests/flavorlibWithFailedTests/lib2/src/main/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..1095584
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/lib2/src/main/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/flavorlibWithFailedTests/lib2/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/tests/flavorlibWithFailedTests/lib2/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/lib2/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/flavorlibWithFailedTests/lib2/src/main/res/layout/lib_main.xml b/build-system/tests/flavorlibWithFailedTests/lib2/src/main/res/layout/lib_main.xml
new file mode 100644
index 0000000..47e792a
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/lib2/src/main/res/layout/lib_main.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/lib_text1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/lib_string" />
+
+ <TextView
+ android:id="@+id/lib_text2"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/build-system/tests/flavorlibWithFailedTests/lib2/src/main/res/values/strings.xml b/build-system/tests/flavorlibWithFailedTests/lib2/src/main/res/values/strings.xml
new file mode 100644
index 0000000..e27cb40
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/lib2/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="lib_name">flavorlib-lib2</string>
+ <string name="lib_string">SUCCESS-LIB2</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/flavorlibWithFailedTests/lib2/src/main/resources/com/android/tests/flavorlib/lib/Lib.txt b/build-system/tests/flavorlibWithFailedTests/lib2/src/main/resources/com/android/tests/flavorlib/lib/Lib.txt
new file mode 100644
index 0000000..94cabe4
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/lib2/src/main/resources/com/android/tests/flavorlib/lib/Lib.txt
@@ -0,0 +1 @@
+SUCCESS-LIB2
\ No newline at end of file
diff --git a/build-system/tests/flavorlibWithFailedTests/settings.gradle b/build-system/tests/flavorlibWithFailedTests/settings.gradle
new file mode 100644
index 0000000..c72f855
--- /dev/null
+++ b/build-system/tests/flavorlibWithFailedTests/settings.gradle
@@ -0,0 +1,3 @@
+include 'app'
+include 'lib1'
+include 'lib2'
diff --git a/build-system/tests/flavors/build.gradle b/build-system/tests/flavors/build.gradle
new file mode 100644
index 0000000..8eb7407
--- /dev/null
+++ b/build-system/tests/flavors/build.gradle
@@ -0,0 +1,32 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+apply plugin: 'android'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+ flavorGroups "group1", "group2"
+
+ productFlavors {
+ f1 {
+ flavorGroup "group1"
+ }
+ f2 {
+ flavorGroup "group1"
+ }
+
+ fa {
+ flavorGroup "group2"
+ }
+ fb {
+ flavorGroup "group2"
+ }
+ }
+}
diff --git a/build-system/tests/flavors/proguard-project.txt b/build-system/tests/flavors/proguard-project.txt
new file mode 100644
index 0000000..349f80f
--- /dev/null
+++ b/build-system/tests/flavors/proguard-project.txt
@@ -0,0 +1,22 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+-adaptclassstrings
\ No newline at end of file
diff --git a/build-system/tests/flavors/src/f1/java/com/android/tests/flavors/group1/SomeClass.java b/build-system/tests/flavors/src/f1/java/com/android/tests/flavors/group1/SomeClass.java
new file mode 100644
index 0000000..ffbf5ab
--- /dev/null
+++ b/build-system/tests/flavors/src/f1/java/com/android/tests/flavors/group1/SomeClass.java
@@ -0,0 +1,7 @@
+package com.android.tests.flavors.group1;
+
+public class SomeClass {
+ public static String getString() {
+ return "f1";
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/flavors/src/f1/res/values/strings.xml b/build-system/tests/flavors/src/f1/res/values/strings.xml
new file mode 100644
index 0000000..b4db3e8
--- /dev/null
+++ b/build-system/tests/flavors/src/f1/res/values/strings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="group1_string">f1</string>
+ <string name="general_string">f1</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/flavors/src/f2/java/com/android/tests/flavors/group1/SomeClass.java b/build-system/tests/flavors/src/f2/java/com/android/tests/flavors/group1/SomeClass.java
new file mode 100644
index 0000000..09327a6
--- /dev/null
+++ b/build-system/tests/flavors/src/f2/java/com/android/tests/flavors/group1/SomeClass.java
@@ -0,0 +1,7 @@
+package com.android.tests.flavors.group1;
+
+public class SomeClass {
+ public static String getString() {
+ return "f2";
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/flavors/src/f2/res/values/strings.xml b/build-system/tests/flavors/src/f2/res/values/strings.xml
new file mode 100644
index 0000000..7a306fc
--- /dev/null
+++ b/build-system/tests/flavors/src/f2/res/values/strings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="group1_string">f2</string>
+ <string name="general_string">f2</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/flavors/src/fa/java/com/android/tests/flavors/group2/SomeClass.java b/build-system/tests/flavors/src/fa/java/com/android/tests/flavors/group2/SomeClass.java
new file mode 100644
index 0000000..ae7677b
--- /dev/null
+++ b/build-system/tests/flavors/src/fa/java/com/android/tests/flavors/group2/SomeClass.java
@@ -0,0 +1,7 @@
+package com.android.tests.flavors.group2;
+
+public class SomeClass {
+ public static String getString() {
+ return "fa";
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/flavors/src/fa/res/values/strings.xml b/build-system/tests/flavors/src/fa/res/values/strings.xml
new file mode 100644
index 0000000..f8cefe7
--- /dev/null
+++ b/build-system/tests/flavors/src/fa/res/values/strings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="group2_string">fa</string>
+ <string name="general_string">fa</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/flavors/src/fb/java/com/android/tests/flavors/group2/SomeClass.java b/build-system/tests/flavors/src/fb/java/com/android/tests/flavors/group2/SomeClass.java
new file mode 100644
index 0000000..5768d64
--- /dev/null
+++ b/build-system/tests/flavors/src/fb/java/com/android/tests/flavors/group2/SomeClass.java
@@ -0,0 +1,7 @@
+package com.android.tests.flavors.group2;
+
+public class SomeClass {
+ public static String getString() {
+ return "fb";
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/flavors/src/fb/res/values/strings.xml b/build-system/tests/flavors/src/fb/res/values/strings.xml
new file mode 100644
index 0000000..8fa24dc
--- /dev/null
+++ b/build-system/tests/flavors/src/fb/res/values/strings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="group2_string">fb</string>
+ <string name="general_string">fb</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/flavors/src/instrumentTestF1/java/com/android/tests/flavors/MainActivityGroup1Test.java b/build-system/tests/flavors/src/instrumentTestF1/java/com/android/tests/flavors/MainActivityGroup1Test.java
new file mode 100644
index 0000000..9379d96
--- /dev/null
+++ b/build-system/tests/flavors/src/instrumentTestF1/java/com/android/tests/flavors/MainActivityGroup1Test.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.flavors;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class MainActivityGroup1Test extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private TextView mResOverLay;
+ private TextView mResOverLay1;
+ private TextView mBuildConfig1;
+ private TextView mCodeOverlay1;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public MainActivityGroup1Test() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final MainActivity a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ mResOverLay = (TextView) a.findViewById(R.id.resoverlay);
+ mResOverLay1 = (TextView) a.findViewById(R.id.resoverlay1);
+ mBuildConfig1 = (TextView) a.findViewById(R.id.buildconfig1);
+ mCodeOverlay1 = (TextView) a.findViewById(R.id.codeoverlay1);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mResOverLay);
+ assertNotNull(mResOverLay1);
+ assertNotNull(mBuildConfig1);
+ assertNotNull(mCodeOverlay1);
+ }
+
+ @MediumTest
+ public void testResOverlay() {
+ assertEquals("f1", mResOverLay.getText());
+ assertEquals("f1", mResOverLay1.getText());
+ }
+
+ @MediumTest
+ public void testBuildConfig() {
+ assertEquals("f1", mBuildConfig1.getText());
+ }
+
+ @MediumTest
+ public void testCodeOverlay() {
+ assertEquals("f1", mCodeOverlay1.getText());
+ }
+}
diff --git a/build-system/tests/flavors/src/instrumentTestF2/java/com/android/tests/flavors/MainActivityGroup1Test.java b/build-system/tests/flavors/src/instrumentTestF2/java/com/android/tests/flavors/MainActivityGroup1Test.java
new file mode 100644
index 0000000..8ecd057
--- /dev/null
+++ b/build-system/tests/flavors/src/instrumentTestF2/java/com/android/tests/flavors/MainActivityGroup1Test.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.flavors;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class MainActivityGroup1Test extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private TextView mResOverLay;
+ private TextView mResOverLay1;
+ private TextView mBuildConfig1;
+ private TextView mCodeOverlay1;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public MainActivityGroup1Test() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final MainActivity a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ mResOverLay = (TextView) a.findViewById(R.id.resoverlay);
+ mResOverLay1 = (TextView) a.findViewById(R.id.resoverlay1);
+ mBuildConfig1 = (TextView) a.findViewById(R.id.buildconfig1);
+ mCodeOverlay1 = (TextView) a.findViewById(R.id.codeoverlay1);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mResOverLay);
+ assertNotNull(mResOverLay1);
+ assertNotNull(mBuildConfig1);
+ assertNotNull(mCodeOverlay1);
+ }
+
+ @MediumTest
+ public void testResOverlay() {
+ assertEquals("f2", mResOverLay.getText());
+ assertEquals("f2", mResOverLay1.getText());
+ }
+
+ @MediumTest
+ public void testBuildConfig() {
+ assertEquals("f2", mBuildConfig1.getText());
+ }
+
+ @MediumTest
+ public void testCodeOverlay() {
+ assertEquals("f2", mCodeOverlay1.getText());
+ }
+}
diff --git a/build-system/tests/flavors/src/instrumentTestFa/java/com/android/tests/flavors/MainActivityGroup2Test.java b/build-system/tests/flavors/src/instrumentTestFa/java/com/android/tests/flavors/MainActivityGroup2Test.java
new file mode 100644
index 0000000..3e51225
--- /dev/null
+++ b/build-system/tests/flavors/src/instrumentTestFa/java/com/android/tests/flavors/MainActivityGroup2Test.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.flavors;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class MainActivityGroup2Test extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private TextView mResOverLay;
+ private TextView mResOverLay2;
+ private TextView mBuildConfig2;
+ private TextView mCodeOverlay2;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public MainActivityGroup2Test() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final MainActivity a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ mResOverLay = (TextView) a.findViewById(R.id.resoverlay);
+ mResOverLay2 = (TextView) a.findViewById(R.id.resoverlay2);
+ mBuildConfig2 = (TextView) a.findViewById(R.id.buildconfig2);
+ mCodeOverlay2 = (TextView) a.findViewById(R.id.codeoverlay2);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mResOverLay);
+ assertNotNull(mResOverLay2);
+ assertNotNull(mBuildConfig2);
+ assertNotNull(mCodeOverlay2);
+ }
+
+ @MediumTest
+ public void testResOverlay() {
+ // because this group has lower priority, we check that the resource from
+ // this flavor is not used.
+ assertFalse("fa".equals(mResOverLay.getText()));
+ assertEquals("fa", mResOverLay2.getText());
+ }
+
+ @MediumTest
+ public void testBuildConfig() {
+ assertEquals("fa", mBuildConfig2.getText());
+ }
+
+ @MediumTest
+ public void testCodeOverlay() {
+ assertEquals("fa", mCodeOverlay2.getText());
+ }
+}
diff --git a/build-system/tests/flavors/src/instrumentTestFb/java/com/android/tests/flavors/MainActivityGroup2Test.java b/build-system/tests/flavors/src/instrumentTestFb/java/com/android/tests/flavors/MainActivityGroup2Test.java
new file mode 100644
index 0000000..f11b5ce
--- /dev/null
+++ b/build-system/tests/flavors/src/instrumentTestFb/java/com/android/tests/flavors/MainActivityGroup2Test.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.flavors;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class MainActivityGroup2Test extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private TextView mResOverLay;
+ private TextView mResOverLay2;
+ private TextView mBuildConfig2;
+ private TextView mCodeOverlay2;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public MainActivityGroup2Test() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final MainActivity a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ mResOverLay = (TextView) a.findViewById(R.id.resoverlay);
+ mResOverLay2 = (TextView) a.findViewById(R.id.resoverlay2);
+ mBuildConfig2 = (TextView) a.findViewById(R.id.buildconfig2);
+ mCodeOverlay2 = (TextView) a.findViewById(R.id.codeoverlay2);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mResOverLay);
+ assertNotNull(mResOverLay2);
+ assertNotNull(mBuildConfig2);
+ assertNotNull(mCodeOverlay2);
+ }
+
+ @MediumTest
+ public void testResOverlay() {
+ // because this group has lower priority, we check that the resource from
+ // this flavor is not used.
+ assertFalse("fb".equals(mResOverLay.getText()));
+ assertEquals("fb", mResOverLay2.getText());
+ }
+
+ @MediumTest
+ public void testBuildConfig() {
+ assertEquals("fb", mBuildConfig2.getText());
+ }
+
+ @MediumTest
+ public void testCodeOverlay() {
+ assertEquals("fb", mCodeOverlay2.getText());
+ }
+}
diff --git a/build-system/tests/flavors/src/main/AndroidManifest.xml b/build-system/tests/flavors/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e173c44
--- /dev/null
+++ b/build-system/tests/flavors/src/main/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.flavors">
+
+ <application
+ android:icon="@drawable/icon"
+ android:label="@string/app_name" >
+ <activity
+ android:name="com.android.tests.flavors.MainActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/build-system/tests/flavors/src/main/java/com/android/tests/flavors/MainActivity.java b/build-system/tests/flavors/src/main/java/com/android/tests/flavors/MainActivity.java
new file mode 100644
index 0000000..ff2b47c
--- /dev/null
+++ b/build-system/tests/flavors/src/main/java/com/android/tests/flavors/MainActivity.java
@@ -0,0 +1,28 @@
+package com.android.tests.flavors;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+public class MainActivity extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ TextView tv;
+
+ tv = (TextView) findViewById(R.id.buildconfig1);
+ tv.setText(BuildConfig.FLAVOR_group1);
+
+ tv = (TextView) findViewById(R.id.buildconfig2);
+ tv.setText(BuildConfig.FLAVOR_group2);
+
+ tv = (TextView) findViewById(R.id.codeoverlay1);
+ tv.setText(com.android.tests.flavors.group1.SomeClass.getString());
+
+ tv = (TextView) findViewById(R.id.codeoverlay2);
+ tv.setText(com.android.tests.flavors.group2.SomeClass.getString());
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/flavors/src/main/res/drawable-hdpi/icon.png b/build-system/tests/flavors/src/main/res/drawable-hdpi/icon.png
new file mode 100644
index 0000000..8074c4c
--- /dev/null
+++ b/build-system/tests/flavors/src/main/res/drawable-hdpi/icon.png
Binary files differ
diff --git a/build-system/tests/flavors/src/main/res/drawable-ldpi/icon.png b/build-system/tests/flavors/src/main/res/drawable-ldpi/icon.png
new file mode 100644
index 0000000..1095584
--- /dev/null
+++ b/build-system/tests/flavors/src/main/res/drawable-ldpi/icon.png
Binary files differ
diff --git a/build-system/tests/flavors/src/main/res/drawable-mdpi/icon.png b/build-system/tests/flavors/src/main/res/drawable-mdpi/icon.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/flavors/src/main/res/drawable-mdpi/icon.png
Binary files differ
diff --git a/build-system/tests/flavors/src/main/res/layout/main.xml b/build-system/tests/flavors/src/main/res/layout/main.xml
new file mode 100644
index 0000000..c9814d1
--- /dev/null
+++ b/build-system/tests/flavors/src/main/res/layout/main.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/resoverlay"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/general_string" />
+ <TextView
+ android:id="@+id/resoverlay1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/group1_string" />
+ <TextView
+ android:id="@+id/resoverlay2"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/group2_string" />
+ <TextView
+ android:id="@+id/buildconfig1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+ <TextView
+ android:id="@+id/buildconfig2"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:id="@+id/codeoverlay1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:id="@+id/codeoverlay2"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/build-system/tests/flavors/src/main/res/values/strings.xml b/build-system/tests/flavors/src/main/res/values/strings.xml
new file mode 100644
index 0000000..dd71753
--- /dev/null
+++ b/build-system/tests/flavors/src/main/res/values/strings.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="app_name">libsTest-app</string>
+ <string name="group2_string">FLAVOR OVERLAY FAILED!</string>
+ <string name="group1_string">FLAVOR OVERLAY FAILED!</string>
+ <string name="general_string">FLAVOR OVERLAY FAILED!</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/genFolderApi/build.gradle b/build-system/tests/genFolderApi/build.gradle
new file mode 100644
index 0000000..229645b
--- /dev/null
+++ b/build-system/tests/genFolderApi/build.gradle
@@ -0,0 +1,45 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+apply plugin: 'android'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
+
+
+public class GenerateCode extends DefaultTask {
+ @Input
+ String value
+
+ @OutputFile
+ File outputFile
+
+ @TaskAction
+ void taskAction() {
+ getOutputFile().text =
+ "package com.custom;\n" +
+ "public class Foo {\n" +
+ " public static String getBuildDate() { return \"${getValue()}\"; }\n" +
+ "}\n";
+ }
+}
+
+
+android.applicationVariants.all { variant ->
+
+ // create a task that generates a java class
+ File sourceFolder = file("${buildDir}/customCode/${variant.dirName}")
+ def javaGenerationTask = tasks.create(name: "generatedCodeFor${variant.name.capitalize()}", type: GenerateCode) {
+ value new Date().format("yyyy-MM-dd'T'HH:mm'Z'", TimeZone.getTimeZone("UTC"))
+ outputFile file("${sourceFolder.absolutePath}/com/custom/Foo.java")
+ }
+
+ variant.registerJavaGeneratingTask(javaGenerationTask, sourceFolder)
+}
\ No newline at end of file
diff --git a/build-system/tests/genFolderApi/src/main/AndroidManifest.xml b/build-system/tests/genFolderApi/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a34d937
--- /dev/null
+++ b/build-system/tests/genFolderApi/src/main/AndroidManifest.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.basic">
+ <application android:label="@string/app_name" android:icon="@drawable/icon">
+ <activity android:name=".Main"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/build-system/tests/genFolderApi/src/main/java/com/android/tests/basic/Main.java b/build-system/tests/genFolderApi/src/main/java/com/android/tests/basic/Main.java
new file mode 100644
index 0000000..df05828e
--- /dev/null
+++ b/build-system/tests/genFolderApi/src/main/java/com/android/tests/basic/Main.java
@@ -0,0 +1,19 @@
+package com.android.tests.basic;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+public class Main extends Activity
+{
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ TextView tv = (TextView) findViewById(R.id.text);
+ tv.setText(com.custom.Foo.getBuildDate());
+ }
+}
diff --git a/build-system/tests/genFolderApi/src/main/res/drawable/icon.png b/build-system/tests/genFolderApi/src/main/res/drawable/icon.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/genFolderApi/src/main/res/drawable/icon.png
Binary files differ
diff --git a/build-system/tests/genFolderApi/src/main/res/layout/main.xml b/build-system/tests/genFolderApi/src/main/res/layout/main.xml
new file mode 100644
index 0000000..ee817cf
--- /dev/null
+++ b/build-system/tests/genFolderApi/src/main/res/layout/main.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+<TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="Test App - GenFolderApi"
+ android:id="@+id/text"
+ />
+</LinearLayout>
+
diff --git a/build-system/tests/genFolderApi/src/main/res/values/strings.xml b/build-system/tests/genFolderApi/src/main/res/values/strings.xml
new file mode 100644
index 0000000..64ee88e
--- /dev/null
+++ b/build-system/tests/genFolderApi/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">_Test-genFolderApi</string>
+</resources>
diff --git a/build-system/tests/libProguard/build.gradle b/build-system/tests/libProguard/build.gradle
new file mode 100644
index 0000000..75904f5
--- /dev/null
+++ b/build-system/tests/libProguard/build.gradle
@@ -0,0 +1,26 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+
+apply plugin: 'android-library'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+ defaultConfig {
+ versionCode 12
+ versionName "2.0"
+ minSdkVersion 16
+ targetSdkVersion 16
+ proguardFile 'config.pro'
+ }
+ release {
+ runProguard true
+ }
+}
diff --git a/build-system/tests/libProguard/config.pro b/build-system/tests/libProguard/config.pro
new file mode 100644
index 0000000..3664f87
--- /dev/null
+++ b/build-system/tests/libProguard/config.pro
@@ -0,0 +1,3 @@
+-keep public class com.android.tests.basic.StringProvider {
+ public static java.lang.String getString(int);
+}
diff --git a/build-system/tests/libProguard/src/main/AndroidManifest.xml b/build-system/tests/libProguard/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..593a287
--- /dev/null
+++ b/build-system/tests/libProguard/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.basic">
+</manifest>
diff --git a/build-system/tests/libProguard/src/main/java/com/android/tests/basic/StringProvider.java b/build-system/tests/libProguard/src/main/java/com/android/tests/basic/StringProvider.java
new file mode 100644
index 0000000..18fe927
--- /dev/null
+++ b/build-system/tests/libProguard/src/main/java/com/android/tests/basic/StringProvider.java
@@ -0,0 +1,11 @@
+package com.android.tests.basic;
+
+import java.lang.Integer;
+
+public class StringProvider {
+ private static int proguardInt = 5;
+
+ public static String getString(int foo) {
+ return Integer.toString(foo + proguardInt);
+ }
+}
diff --git a/build-system/tests/libProguardConsumerFiles/A.txt b/build-system/tests/libProguardConsumerFiles/A.txt
new file mode 100644
index 0000000..f70f10e
--- /dev/null
+++ b/build-system/tests/libProguardConsumerFiles/A.txt
@@ -0,0 +1 @@
+A
diff --git a/build-system/tests/libProguardConsumerFiles/B.txt b/build-system/tests/libProguardConsumerFiles/B.txt
new file mode 100644
index 0000000..223b783
--- /dev/null
+++ b/build-system/tests/libProguardConsumerFiles/B.txt
@@ -0,0 +1 @@
+B
diff --git a/build-system/tests/libProguardConsumerFiles/C.txt b/build-system/tests/libProguardConsumerFiles/C.txt
new file mode 100644
index 0000000..3cc58df
--- /dev/null
+++ b/build-system/tests/libProguardConsumerFiles/C.txt
@@ -0,0 +1 @@
+C
diff --git a/build-system/tests/libProguardConsumerFiles/build.gradle b/build-system/tests/libProguardConsumerFiles/build.gradle
new file mode 100644
index 0000000..59ed5ea
--- /dev/null
+++ b/build-system/tests/libProguardConsumerFiles/build.gradle
@@ -0,0 +1,32 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+
+apply plugin: 'android-library'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+ defaultConfig {
+ versionCode 12
+ versionName "2.0"
+ minSdkVersion 16
+ targetSdkVersion 16
+ proguardFile 'config.pro'
+ consumerProguardFiles 'A.txt'
+ }
+
+ debug {
+ }
+
+ release {
+ runProguard true
+ consumerProguardFiles 'B.txt', 'C.txt'
+ }
+}
diff --git a/build-system/tests/libProguardConsumerFiles/config.pro b/build-system/tests/libProguardConsumerFiles/config.pro
new file mode 100644
index 0000000..3664f87
--- /dev/null
+++ b/build-system/tests/libProguardConsumerFiles/config.pro
@@ -0,0 +1,3 @@
+-keep public class com.android.tests.basic.StringProvider {
+ public static java.lang.String getString(int);
+}
diff --git a/build-system/tests/libProguardConsumerFiles/src/main/AndroidManifest.xml b/build-system/tests/libProguardConsumerFiles/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..593a287
--- /dev/null
+++ b/build-system/tests/libProguardConsumerFiles/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.basic">
+</manifest>
diff --git a/build-system/tests/libProguardConsumerFiles/src/main/java/com/android/tests/basic/StringProvider.java b/build-system/tests/libProguardConsumerFiles/src/main/java/com/android/tests/basic/StringProvider.java
new file mode 100644
index 0000000..18fe927
--- /dev/null
+++ b/build-system/tests/libProguardConsumerFiles/src/main/java/com/android/tests/basic/StringProvider.java
@@ -0,0 +1,11 @@
+package com.android.tests.basic;
+
+import java.lang.Integer;
+
+public class StringProvider {
+ private static int proguardInt = 5;
+
+ public static String getString(int foo) {
+ return Integer.toString(foo + proguardInt);
+ }
+}
diff --git a/build-system/tests/libProguardJarDep/app/build.gradle b/build-system/tests/libProguardJarDep/app/build.gradle
new file mode 100644
index 0000000..8166bf8
--- /dev/null
+++ b/build-system/tests/libProguardJarDep/app/build.gradle
@@ -0,0 +1,35 @@
+apply plugin: 'android'
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ compile project(':lib')
+}
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+ testBuildType "proguard"
+
+ defaultConfig {
+ versionCode 12
+ versionName "2.0"
+ minSdkVersion 16
+ targetSdkVersion 16
+ }
+
+ buildTypes {
+ proguard.initWith(buildTypes.debug)
+ proguard {
+ runProguard true
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'config.pro'
+ }
+ }
+
+ dexOptions {
+ incremental false
+ }
+}
diff --git a/build-system/tests/libProguardJarDep/app/config.pro b/build-system/tests/libProguardJarDep/app/config.pro
new file mode 100644
index 0000000..4321975
--- /dev/null
+++ b/build-system/tests/libProguardJarDep/app/config.pro
@@ -0,0 +1,2 @@
+-keep class com.google.**
+-dontwarn com.google.**
diff --git a/build-system/tests/libProguardJarDep/app/src/instrumentTest/java/com/android/tests/basic/MainTest.java b/build-system/tests/libProguardJarDep/app/src/instrumentTest/java/com/android/tests/basic/MainTest.java
new file mode 100644
index 0000000..6b3ff36
--- /dev/null
+++ b/build-system/tests/libProguardJarDep/app/src/instrumentTest/java/com/android/tests/basic/MainTest.java
@@ -0,0 +1,42 @@
+package com.android.tests.basic;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+public class MainTest extends ActivityInstrumentationTestCase2<Main> {
+
+ private TextView mTextView;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
+ */
+ public MainTest() {
+ super(Main.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final Main a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+ mTextView = (TextView) a.findViewById(R.id.dateText);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView);
+ }
+
+ public void testTextViewContent() {
+ assertEquals("FredBarney", mTextView.getText());
+ }
+}
+
diff --git a/build-system/tests/libProguardJarDep/app/src/main/AndroidManifest.xml b/build-system/tests/libProguardJarDep/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..4f8d570
--- /dev/null
+++ b/build-system/tests/libProguardJarDep/app/src/main/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.basic">
+ <application android:label="@string/app_name" android:icon="@drawable/icon">
+ <activity android:name=".Main"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <uses-permission android:name="com.blah" />
+
+ <permission-group android:name="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+ <permission android:name="foo.permission.SEND_SMS"
+ android:permissionGroup="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+ <permission android:name="foo.blah.SEND_SMS"
+ android:permissionGroup="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+</manifest>
diff --git a/build-system/tests/libProguardJarDep/app/src/main/java/com/android/tests/basic/Main.java b/build-system/tests/libProguardJarDep/app/src/main/java/com/android/tests/basic/Main.java
new file mode 100644
index 0000000..f5a6953
--- /dev/null
+++ b/build-system/tests/libProguardJarDep/app/src/main/java/com/android/tests/basic/Main.java
@@ -0,0 +1,31 @@
+package com.android.tests.basic;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+import java.lang.reflect.Method;
+
+public class Main extends Activity
+{
+
+ private int foo = 1234;
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ TextView tv = (TextView) findViewById(R.id.dateText);
+
+ try {
+ // use reflection to make sure the class wasn't obfuscated
+ Class<?> theClass = Class.forName("com.android.tests.basic.StringGetter");
+ Method method = theClass.getDeclaredMethod("getString");
+ tv.setText((String) method.invoke(null));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/build-system/tests/libProguardJarDep/app/src/main/res/drawable/icon.png b/build-system/tests/libProguardJarDep/app/src/main/res/drawable/icon.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/libProguardJarDep/app/src/main/res/drawable/icon.png
Binary files differ
diff --git a/build-system/tests/libProguardJarDep/app/src/main/res/layout/main.xml b/build-system/tests/libProguardJarDep/app/src/main/res/layout/main.xml
new file mode 100644
index 0000000..89ab091
--- /dev/null
+++ b/build-system/tests/libProguardJarDep/app/src/main/res/layout/main.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+<TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="Test App - Basic"
+ android:id="@+id/text"
+ />
+<TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text=""
+ android:id="@+id/dateText"
+ />
+</LinearLayout>
+
diff --git a/build-system/tests/libProguardJarDep/app/src/main/res/values/strings.xml b/build-system/tests/libProguardJarDep/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..60ea2d0
--- /dev/null
+++ b/build-system/tests/libProguardJarDep/app/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">_Test-Basic</string>
+</resources>
diff --git a/build-system/tests/libProguardJarDep/build.gradle b/build-system/tests/libProguardJarDep/build.gradle
new file mode 100644
index 0000000..83b3e0b
--- /dev/null
+++ b/build-system/tests/libProguardJarDep/build.gradle
@@ -0,0 +1,8 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
diff --git a/build-system/tests/libProguardJarDep/lib/build.gradle b/build-system/tests/libProguardJarDep/lib/build.gradle
new file mode 100644
index 0000000..40cb840
--- /dev/null
+++ b/build-system/tests/libProguardJarDep/lib/build.gradle
@@ -0,0 +1,30 @@
+apply plugin: 'android-library'
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ compile 'com.google.guava:guava:11.0.2'
+ compile fileTree(dir: 'libs', include: '*.jar')
+}
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+ defaultConfig {
+ versionCode 12
+ versionName "2.0"
+ minSdkVersion 16
+ targetSdkVersion 16
+ proguardFile 'config.pro'
+ consumerProguardFiles 'config.pro'
+ }
+ debug {
+ runProguard true
+ }
+ release {
+ runProguard true
+ }
+}
diff --git a/build-system/tests/libProguardJarDep/lib/config.pro b/build-system/tests/libProguardJarDep/lib/config.pro
new file mode 100644
index 0000000..c3fd7bd
--- /dev/null
+++ b/build-system/tests/libProguardJarDep/lib/config.pro
@@ -0,0 +1,3 @@
+-keep public class com.android.tests.basic.StringGetter {
+ public static java.lang.String getString();
+}
diff --git a/build-system/tests/libProguardJarDep/lib/libs/util-1.0.jar b/build-system/tests/libProguardJarDep/lib/libs/util-1.0.jar
new file mode 100644
index 0000000..55471dc
--- /dev/null
+++ b/build-system/tests/libProguardJarDep/lib/libs/util-1.0.jar
Binary files differ
diff --git a/build-system/tests/libProguardJarDep/lib/src/main/AndroidManifest.xml b/build-system/tests/libProguardJarDep/lib/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..950a35a
--- /dev/null
+++ b/build-system/tests/libProguardJarDep/lib/src/main/AndroidManifest.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.basic">
+ <application />
+</manifest>
diff --git a/build-system/tests/libProguardJarDep/lib/src/main/java/com/android/tests/basic/StringGetter.java b/build-system/tests/libProguardJarDep/lib/src/main/java/com/android/tests/basic/StringGetter.java
new file mode 100644
index 0000000..e9e5e76
--- /dev/null
+++ b/build-system/tests/libProguardJarDep/lib/src/main/java/com/android/tests/basic/StringGetter.java
@@ -0,0 +1,23 @@
+package com.android.tests.basic;
+
+import java.lang.String;
+import java.lang.StringBuffer;
+import com.example.android.multiproject.person.People;
+import com.example.android.multiproject.person.Person;
+
+public class StringGetter{
+
+ public static String getString() {
+ return getStringInternal();
+ }
+
+ private static String getStringInternal() {
+ StringBuffer sb = new StringBuffer();
+
+ Iterable<Person> people = new People();
+ for (Person person : people) {
+ sb.append(person.getName());
+ }
+ return sb.toString();
+ }
+}
diff --git a/build-system/tests/libProguardJarDep/settings.gradle b/build-system/tests/libProguardJarDep/settings.gradle
new file mode 100644
index 0000000..eedb2a1
--- /dev/null
+++ b/build-system/tests/libProguardJarDep/settings.gradle
@@ -0,0 +1,2 @@
+include 'app'
+include 'lib'
diff --git a/build-system/tests/libProguardJarDep/util/build.gradle b/build-system/tests/libProguardJarDep/util/build.gradle
new file mode 100644
index 0000000..dff7725
--- /dev/null
+++ b/build-system/tests/libProguardJarDep/util/build.gradle
@@ -0,0 +1,8 @@
+apply plugin: 'java'
+
+dependencies {
+ compile 'com.google.guava:guava:11.0.2'
+}
+
+sourceCompatibility = "1.6"
+targetCompatibility = "1.6"
diff --git a/build-system/tests/libProguardJarDep/util/src/main/java/com/example/android/multiproject/person/People.java b/build-system/tests/libProguardJarDep/util/src/main/java/com/example/android/multiproject/person/People.java
new file mode 100644
index 0000000..8b99248
--- /dev/null
+++ b/build-system/tests/libProguardJarDep/util/src/main/java/com/example/android/multiproject/person/People.java
@@ -0,0 +1,10 @@
+package com.example.android.multiproject.person;
+
+import java.util.Iterator;
+import com.google.common.collect.Lists;
+
+public class People implements Iterable<Person> {
+ public Iterator<Person> iterator() {
+ return Lists.newArrayList(new Person("Fred"), new Person("Barney")).iterator();
+ }
+}
diff --git a/build-system/tests/libProguardJarDep/util/src/main/java/com/example/android/multiproject/person/Person.java b/build-system/tests/libProguardJarDep/util/src/main/java/com/example/android/multiproject/person/Person.java
new file mode 100644
index 0000000..2f4aa9f
--- /dev/null
+++ b/build-system/tests/libProguardJarDep/util/src/main/java/com/example/android/multiproject/person/Person.java
@@ -0,0 +1,13 @@
+package com.example.android.multiproject.person;
+
+public class Person {
+ private final String name;
+
+ public Person(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/build-system/tests/libProguardLibDep/app/build.gradle b/build-system/tests/libProguardLibDep/app/build.gradle
new file mode 100644
index 0000000..c52309a
--- /dev/null
+++ b/build-system/tests/libProguardLibDep/app/build.gradle
@@ -0,0 +1,31 @@
+apply plugin: 'android'
+
+dependencies {
+ compile project(':lib')
+}
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+ testBuildType "proguard"
+
+ defaultConfig {
+ versionCode 12
+ versionName "2.0"
+ minSdkVersion 16
+ targetSdkVersion 16
+ }
+
+ buildTypes {
+ proguard.initWith(buildTypes.debug)
+ proguard {
+ runProguard true
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'config.pro'
+ }
+ }
+
+ dexOptions {
+ incremental false
+ }
+}
diff --git a/build-system/tests/libProguardLibDep/app/config.pro b/build-system/tests/libProguardLibDep/app/config.pro
new file mode 100644
index 0000000..e4aadb9
--- /dev/null
+++ b/build-system/tests/libProguardLibDep/app/config.pro
@@ -0,0 +1,3 @@
+-keep public class com.android.tests.basic.Main {
+ public void getObfuscatedMethod();
+}
diff --git a/build-system/tests/libProguardLibDep/app/src/instrumentTest/java/com/android/tests/basic/MainTest.java b/build-system/tests/libProguardLibDep/app/src/instrumentTest/java/com/android/tests/basic/MainTest.java
new file mode 100644
index 0000000..f7289d1
--- /dev/null
+++ b/build-system/tests/libProguardLibDep/app/src/instrumentTest/java/com/android/tests/basic/MainTest.java
@@ -0,0 +1,54 @@
+package com.android.tests.basic;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+import java.lang.NoSuchMethodException;
+
+public class MainTest extends ActivityInstrumentationTestCase2<Main> {
+
+ private TextView mTextView;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
+ */
+ public MainTest() {
+ super(Main.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final Main a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+ mTextView = (TextView) a.findViewById(R.id.dateText);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView);
+ }
+
+ public void testTextViewContent() {
+ assertEquals("1234", mTextView.getText());
+ }
+
+ public void testConsumerProguardRules() {
+ try {
+ final Main a = getActivity();
+ a.getObfuscatedMethod();
+ fail("Excepted NoSuchMethodError");
+ } catch (NoSuchMethodException e) {
+ // test passed
+ }
+ }
+}
+
diff --git a/build-system/tests/libProguardLibDep/app/src/main/AndroidManifest.xml b/build-system/tests/libProguardLibDep/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..4f8d570
--- /dev/null
+++ b/build-system/tests/libProguardLibDep/app/src/main/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.basic">
+ <application android:label="@string/app_name" android:icon="@drawable/icon">
+ <activity android:name=".Main"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <uses-permission android:name="com.blah" />
+
+ <permission-group android:name="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+ <permission android:name="foo.permission.SEND_SMS"
+ android:permissionGroup="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+ <permission android:name="foo.blah.SEND_SMS"
+ android:permissionGroup="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+</manifest>
diff --git a/build-system/tests/libProguardLibDep/app/src/main/java/com/android/tests/basic/Main.java b/build-system/tests/libProguardLibDep/app/src/main/java/com/android/tests/basic/Main.java
new file mode 100644
index 0000000..8faebcd
--- /dev/null
+++ b/build-system/tests/libProguardLibDep/app/src/main/java/com/android/tests/basic/Main.java
@@ -0,0 +1,46 @@
+package com.android.tests.basic;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+import java.lang.ClassNotFoundException;
+import java.lang.NoSuchMethodException;
+import java.lang.reflect.Method;
+
+public class Main extends Activity
+{
+
+ private int foo = 1234;
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ TextView tv = (TextView) findViewById(R.id.dateText);
+
+ try {
+ // use reflection to make sure the class wasn't obfuscated
+ Class<?> theClass = Class.forName("com.android.tests.basic.StringGetter");
+ Method method = theClass.getDeclaredMethod("getString", int.class);
+ tv.setText((String) method.invoke(null, foo));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * use reflection to get a method that should be obfuscated
+ */
+ public void getObfuscatedMethod() throws NoSuchMethodException{
+ try {
+ Class<?> theClass = Class.forName("com.android.tests.basic.StringGetter");
+ Method method = theClass.getDeclaredMethod("getStringInternal", int.class);
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/build-system/tests/libProguardLibDep/app/src/main/res/drawable/icon.png b/build-system/tests/libProguardLibDep/app/src/main/res/drawable/icon.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/libProguardLibDep/app/src/main/res/drawable/icon.png
Binary files differ
diff --git a/build-system/tests/libProguardLibDep/app/src/main/res/layout/main.xml b/build-system/tests/libProguardLibDep/app/src/main/res/layout/main.xml
new file mode 100644
index 0000000..89ab091
--- /dev/null
+++ b/build-system/tests/libProguardLibDep/app/src/main/res/layout/main.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+<TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="Test App - Basic"
+ android:id="@+id/text"
+ />
+<TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text=""
+ android:id="@+id/dateText"
+ />
+</LinearLayout>
+
diff --git a/build-system/tests/libProguardLibDep/app/src/main/res/values/strings.xml b/build-system/tests/libProguardLibDep/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..60ea2d0
--- /dev/null
+++ b/build-system/tests/libProguardLibDep/app/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">_Test-Basic</string>
+</resources>
diff --git a/build-system/tests/libProguardLibDep/build.gradle b/build-system/tests/libProguardLibDep/build.gradle
new file mode 100644
index 0000000..83b3e0b
--- /dev/null
+++ b/build-system/tests/libProguardLibDep/build.gradle
@@ -0,0 +1,8 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
diff --git a/build-system/tests/libProguardLibDep/lib/build.gradle b/build-system/tests/libProguardLibDep/lib/build.gradle
new file mode 100644
index 0000000..ce3ccf6
--- /dev/null
+++ b/build-system/tests/libProguardLibDep/lib/build.gradle
@@ -0,0 +1,22 @@
+apply plugin: 'android-library'
+
+dependencies {
+ compile project(':lib2')
+}
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+ defaultConfig {
+ versionCode 12
+ versionName "2.0"
+ minSdkVersion 16
+ targetSdkVersion 16
+ proguardFile 'config.pro'
+ consumerProguardFiles 'consumerRules.pro'
+ }
+ release {
+ runProguard true
+ }
+}
diff --git a/build-system/tests/libProguardLibDep/lib/config.pro b/build-system/tests/libProguardLibDep/lib/config.pro
new file mode 100644
index 0000000..4cf7cba
--- /dev/null
+++ b/build-system/tests/libProguardLibDep/lib/config.pro
@@ -0,0 +1,3 @@
+-keep public class com.android.tests.basic.StringGetter {
+ public static java.lang.String getString(int);
+}
diff --git a/build-system/tests/libProguardLibDep/lib/consumerRules.pro b/build-system/tests/libProguardLibDep/lib/consumerRules.pro
new file mode 100644
index 0000000..d09be16
--- /dev/null
+++ b/build-system/tests/libProguardLibDep/lib/consumerRules.pro
@@ -0,0 +1,6 @@
+-keep public class com.android.tests.basic.StringGetter {
+ public static java.lang.String getString(int);
+}
+-keep public class com.android.tests.basic.StringGetter {
+ public static java.lang.String getStringInternal(int);
+}
diff --git a/build-system/tests/libProguardLibDep/lib/src/main/AndroidManifest.xml b/build-system/tests/libProguardLibDep/lib/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..950a35a
--- /dev/null
+++ b/build-system/tests/libProguardLibDep/lib/src/main/AndroidManifest.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.basic">
+ <application />
+</manifest>
diff --git a/build-system/tests/libProguardLibDep/lib/src/main/java/com/android/tests/basic/StringGetter.java b/build-system/tests/libProguardLibDep/lib/src/main/java/com/android/tests/basic/StringGetter.java
new file mode 100644
index 0000000..159f359
--- /dev/null
+++ b/build-system/tests/libProguardLibDep/lib/src/main/java/com/android/tests/basic/StringGetter.java
@@ -0,0 +1,23 @@
+package com.android.tests.basic;
+
+import java.lang.RuntimeException;
+import java.lang.String;
+import java.lang.reflect.Method;
+
+public class StringGetter{
+
+ public static String getString(int foo) {
+ return getStringInternal(foo);
+ }
+
+ public static String getStringInternal(int foo) {
+ try {
+ // use reflection to make sure the class wasn't obfuscated
+ Class<?> theClass = Class.forName("com.android.tests.basic.StringProvider");
+ Method method = theClass.getDeclaredMethod("getString", int.class);
+ return (String) method.invoke(null, foo);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/build-system/tests/libProguardLibDep/lib2/build.gradle b/build-system/tests/libProguardLibDep/lib2/build.gradle
new file mode 100644
index 0000000..78da4d4
--- /dev/null
+++ b/build-system/tests/libProguardLibDep/lib2/build.gradle
@@ -0,0 +1,18 @@
+apply plugin: 'android-library'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+ defaultConfig {
+ versionCode 12
+ versionName "2.0"
+ minSdkVersion 16
+ targetSdkVersion 16
+ proguardFile 'config.pro'
+ consumerProguardFiles 'config.pro'
+ }
+ release {
+ runProguard true
+ }
+}
diff --git a/build-system/tests/libProguardLibDep/lib2/config.pro b/build-system/tests/libProguardLibDep/lib2/config.pro
new file mode 100644
index 0000000..3664f87
--- /dev/null
+++ b/build-system/tests/libProguardLibDep/lib2/config.pro
@@ -0,0 +1,3 @@
+-keep public class com.android.tests.basic.StringProvider {
+ public static java.lang.String getString(int);
+}
diff --git a/build-system/tests/libProguardLibDep/lib2/src/main/AndroidManifest.xml b/build-system/tests/libProguardLibDep/lib2/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..593a287
--- /dev/null
+++ b/build-system/tests/libProguardLibDep/lib2/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.basic">
+</manifest>
diff --git a/build-system/tests/libProguardLibDep/lib2/src/main/java/com/android/tests/basic/StringProvider.java b/build-system/tests/libProguardLibDep/lib2/src/main/java/com/android/tests/basic/StringProvider.java
new file mode 100644
index 0000000..6d81901
--- /dev/null
+++ b/build-system/tests/libProguardLibDep/lib2/src/main/java/com/android/tests/basic/StringProvider.java
@@ -0,0 +1,8 @@
+package com.android.tests.basic;
+
+public class StringProvider {
+
+ public static String getString(int foo) {
+ return Integer.toString(foo);
+ }
+}
diff --git a/build-system/tests/libProguardLibDep/settings.gradle b/build-system/tests/libProguardLibDep/settings.gradle
new file mode 100644
index 0000000..d8ac2dc
--- /dev/null
+++ b/build-system/tests/libProguardLibDep/settings.gradle
@@ -0,0 +1,3 @@
+include 'app'
+include 'lib'
+include 'lib2'
diff --git a/build-system/tests/libTestDep/build.gradle b/build-system/tests/libTestDep/build.gradle
new file mode 100644
index 0000000..6d866c5
--- /dev/null
+++ b/build-system/tests/libTestDep/build.gradle
@@ -0,0 +1,23 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+
+apply plugin: 'android-library'
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ compile 'com.google.guava:guava:11.0.2'
+}
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
\ No newline at end of file
diff --git a/build-system/tests/libTestDep/src/instrumentTest/java/com/android/tests/libdeps/MainActivityTest.java b/build-system/tests/libTestDep/src/instrumentTest/java/com/android/tests/libdeps/MainActivityTest.java
new file mode 100644
index 0000000..23c8303
--- /dev/null
+++ b/build-system/tests/libTestDep/src/instrumentTest/java/com/android/tests/libdeps/MainActivityTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.libdeps;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+import com.android.tests.libdeps.MainActivity;
+import com.google.common.base.Splitter;
+
+/**
+ *
+ */
+public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private TextView mLib1TextView1;
+
+ public MainActivityTest() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final MainActivity a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ mLib1TextView1 = (TextView) a.findViewById(R.id.lib1_text1);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mLib1TextView1);
+
+ // use some of Guava's class as they should be accessible through the
+ // classpath from the library
+ Iterable<String> segments = Splitter.on("-").split(mLib1TextView1.getText());
+ assertEquals("SUCCESS", segments.iterator().next());
+ }
+}
diff --git a/build-system/tests/libTestDep/src/main/AndroidManifest.xml b/build-system/tests/libTestDep/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..00deb5c
--- /dev/null
+++ b/build-system/tests/libTestDep/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.libdeps"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/lib1_name" >
+ <activity
+ android:name="MainActivity"
+ android:label="@string/lib1_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/build-system/tests/libTestDep/src/main/java/com/android/tests/libdeps/MainActivity.java b/build-system/tests/libTestDep/src/main/java/com/android/tests/libdeps/MainActivity.java
new file mode 100644
index 0000000..ab764d9
--- /dev/null
+++ b/build-system/tests/libTestDep/src/main/java/com/android/tests/libdeps/MainActivity.java
@@ -0,0 +1,15 @@
+package com.android.tests.libdeps;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.tests.libdeps.R;
+
+public class MainActivity extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.lib1_main);
+ }
+}
diff --git a/build-system/tests/libTestDep/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/tests/libTestDep/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..8074c4c
--- /dev/null
+++ b/build-system/tests/libTestDep/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/libTestDep/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/tests/libTestDep/src/main/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..1095584
--- /dev/null
+++ b/build-system/tests/libTestDep/src/main/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/libTestDep/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/tests/libTestDep/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/libTestDep/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/libTestDep/src/main/res/layout/lib1_main.xml b/build-system/tests/libTestDep/src/main/res/layout/lib1_main.xml
new file mode 100644
index 0000000..06ec124
--- /dev/null
+++ b/build-system/tests/libTestDep/src/main/res/layout/lib1_main.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/lib1_text1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/lib1_string" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/build-system/tests/libTestDep/src/main/res/values/strings.xml b/build-system/tests/libTestDep/src/main/res/values/strings.xml
new file mode 100644
index 0000000..8d20610
--- /dev/null
+++ b/build-system/tests/libTestDep/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="lib1_name">LibsTest-lib1</string>
+ <string name="lib1_string">SUCCESS-LIB1</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/libsTest/app/build.gradle b/build-system/tests/libsTest/app/build.gradle
new file mode 100644
index 0000000..7ca58b2
--- /dev/null
+++ b/build-system/tests/libsTest/app/build.gradle
@@ -0,0 +1,15 @@
+apply plugin: 'android'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
+
+//
+// A basic Android application split over a library and a main project.
+//
+dependencies {
+ compile project(':lib1')
+ compile project(':lib2b')
+ compile project(':libapp')
+}
diff --git a/build-system/tests/libsTest/app/proguard-project.txt b/build-system/tests/libsTest/app/proguard-project.txt
new file mode 100644
index 0000000..349f80f
--- /dev/null
+++ b/build-system/tests/libsTest/app/proguard-project.txt
@@ -0,0 +1,22 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+-adaptclassstrings
\ No newline at end of file
diff --git a/build-system/tests/libsTest/app/src/instrumentTest/java/com/android/tests/libstest/app/MainActivityTest.java b/build-system/tests/libsTest/app/src/instrumentTest/java/com/android/tests/libstest/app/MainActivityTest.java
new file mode 100644
index 0000000..61a0a31
--- /dev/null
+++ b/build-system/tests/libsTest/app/src/instrumentTest/java/com/android/tests/libstest/app/MainActivityTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.libstest.app;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private TextView mAppTextView1;
+ private TextView mAppTextView2;
+ private TextView mLib1TextView1;
+ private TextView mLib1TextView2;
+ private TextView mLib2TextView1;
+ private TextView mLib2TextView2;
+ private TextView mLib2bTextView1;
+ private TextView mLib2bTextView2;
+ private TextView mLibappTextView1;
+ private TextView mLibappTextView2;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public MainActivityTest() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final MainActivity a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ mAppTextView1 = (TextView) a.findViewById(R.id.app_text1);
+ mAppTextView2 = (TextView) a.findViewById(R.id.app_text1);
+ mLib1TextView1 = (TextView) a.findViewById(R.id.lib1_text1);
+ mLib1TextView2 = (TextView) a.findViewById(R.id.lib1_text2);
+ mLib2TextView1 = (TextView) a.findViewById(R.id.lib2_text1);
+ mLib2TextView2 = (TextView) a.findViewById(R.id.lib2_text2);
+ mLib2bTextView1 = (TextView) a.findViewById(R.id.lib2b_text1);
+ mLib2bTextView2 = (TextView) a.findViewById(R.id.lib2b_text2);
+ mLibappTextView1 = (TextView) a.findViewById(R.id.libapp_text1);
+ mLibappTextView2 = (TextView) a.findViewById(R.id.libapp_text2);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mAppTextView1);
+ assertNotNull(mAppTextView2);
+ assertNotNull(mLib1TextView1);
+ assertNotNull(mLib1TextView2);
+ assertNotNull(mLib2TextView1);
+ assertNotNull(mLib2TextView2);
+ assertNotNull(mLib2bTextView1);
+ assertNotNull(mLib2bTextView2);
+ assertNotNull(mLibappTextView1);
+ assertNotNull(mLibappTextView2);
+ }
+
+ @MediumTest
+ public void testAndroidStrings() {
+ assertEquals(mAppTextView1.getText(), "SUCCESS-APP");
+ assertEquals(mLib1TextView1.getText(), "SUCCESS-LIB1");
+ assertEquals(mLib2TextView1.getText(), "SUCCESS-LIB2");
+ assertEquals(mLib2bTextView1.getText(), "SUCCESS-LIB2b");
+ assertEquals(mLibappTextView1.getText(), "SUCCESS-LIBAPP");
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals(mAppTextView2.getText(), "SUCCESS-APP");
+ assertEquals(mLib1TextView2.getText(), "SUCCESS-LIB1");
+ assertEquals(mLib2TextView2.getText(), "SUCCESS-LIB2");
+ assertEquals(mLib2bTextView2.getText(), "SUCCESS-LIB2b");
+ assertEquals(mLibappTextView2.getText(), "SUCCESS-LIBAPP");
+ }
+}
diff --git a/build-system/tests/libsTest/app/src/main/AndroidManifest.xml b/build-system/tests/libsTest/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..74f0ff2
--- /dev/null
+++ b/build-system/tests/libsTest/app/src/main/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.libstest.app"
+ android:versionCode="1"
+ android:versionName="1.0" xmlns:tools="http://schemas.android.com/tools">
+
+ <uses-sdk
+ android:minSdkVersion="15"
+ tools:ignore="UsesMinSdkAttributes" />
+
+ <application
+ android:icon="@drawable/icon"
+ android:label="@string/app_name" >
+ <activity
+ android:name="com.android.tests.libstest.app.MainActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/build-system/tests/libsTest/app/src/main/java/com/android/tests/libstest/app/App.java b/build-system/tests/libsTest/app/src/main/java/com/android/tests/libstest/app/App.java
new file mode 100644
index 0000000..54e2a09
--- /dev/null
+++ b/build-system/tests/libsTest/app/src/main/java/com/android/tests/libstest/app/App.java
@@ -0,0 +1,43 @@
+package com.android.tests.libstest.app;
+
+import android.app.Activity;
+import android.widget.TextView;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+public class App {
+
+ public static void handleTextView(Activity a) {
+ TextView tv = (TextView) a.findViewById(R.id.app_text2);
+ if (tv != null) {
+ tv.setText(getContent());
+ }
+ }
+
+ private static String getContent() {
+ InputStream input = App.class.getResourceAsStream("App.txt");
+ if (input == null) {
+ return "FAILED TO FIND App.txt";
+ }
+
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+
+ return reader.readLine();
+ } catch (IOException e) {
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ return "FAILED TO READ CONTENT";
+ }
+}
diff --git a/build-system/tests/libsTest/app/src/main/java/com/android/tests/libstest/app/MainActivity.java b/build-system/tests/libsTest/app/src/main/java/com/android/tests/libstest/app/MainActivity.java
new file mode 100644
index 0000000..739f91e
--- /dev/null
+++ b/build-system/tests/libsTest/app/src/main/java/com/android/tests/libstest/app/MainActivity.java
@@ -0,0 +1,23 @@
+package com.android.tests.libstest.app;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.tests.libstest.lib1.Lib1;
+import com.android.tests.libstest.lib2.Lib2;
+import com.android.tests.libstest.lib2.Lib2b;
+
+public class MainActivity extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ App.handleTextView(this);
+ Lib1.handleTextView(this);
+ Lib2.handleTextView(this);
+ Lib2b.handleTextView(this);
+ LibApp.handleTextView(this);
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/libsTest/app/src/main/res/drawable-hdpi/icon.png b/build-system/tests/libsTest/app/src/main/res/drawable-hdpi/icon.png
new file mode 100644
index 0000000..8074c4c
--- /dev/null
+++ b/build-system/tests/libsTest/app/src/main/res/drawable-hdpi/icon.png
Binary files differ
diff --git a/build-system/tests/libsTest/app/src/main/res/drawable-ldpi/icon.png b/build-system/tests/libsTest/app/src/main/res/drawable-ldpi/icon.png
new file mode 100644
index 0000000..1095584
--- /dev/null
+++ b/build-system/tests/libsTest/app/src/main/res/drawable-ldpi/icon.png
Binary files differ
diff --git a/build-system/tests/libsTest/app/src/main/res/drawable-mdpi/icon.png b/build-system/tests/libsTest/app/src/main/res/drawable-mdpi/icon.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/libsTest/app/src/main/res/drawable-mdpi/icon.png
Binary files differ
diff --git a/build-system/tests/libsTest/app/src/main/res/layout/main.xml b/build-system/tests/libsTest/app/src/main/res/layout/main.xml
new file mode 100644
index 0000000..fa1aaba
--- /dev/null
+++ b/build-system/tests/libsTest/app/src/main/res/layout/main.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/app_text1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/app_string" />
+
+ <TextView
+ android:id="@+id/app_text2"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+ <include layout="@layout/lib1_main" />
+
+ <include layout="@layout/lib2b_main" />
+
+ <include layout="@layout/libapp_main" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/build-system/tests/libsTest/app/src/main/res/values/strings.xml b/build-system/tests/libsTest/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..2a2e006
--- /dev/null
+++ b/build-system/tests/libsTest/app/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="app_name">libsTest-app</string>
+ <string name="app_string">SUCCESS-APP</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/libsTest/app/src/main/resources/com/android/tests/libstest/app/App.txt b/build-system/tests/libsTest/app/src/main/resources/com/android/tests/libstest/app/App.txt
new file mode 100644
index 0000000..084e7d0
--- /dev/null
+++ b/build-system/tests/libsTest/app/src/main/resources/com/android/tests/libstest/app/App.txt
@@ -0,0 +1 @@
+SUCCESS-APP
\ No newline at end of file
diff --git a/build-system/tests/libsTest/build.gradle b/build-system/tests/libsTest/build.gradle
new file mode 100644
index 0000000..a8fdb64
--- /dev/null
+++ b/build-system/tests/libsTest/build.gradle
@@ -0,0 +1,10 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+
+apply plugin: 'android-reporting'
\ No newline at end of file
diff --git a/build-system/tests/libsTest/lib1/build.gradle b/build-system/tests/libsTest/lib1/build.gradle
new file mode 100644
index 0000000..8975f4b
--- /dev/null
+++ b/build-system/tests/libsTest/lib1/build.gradle
@@ -0,0 +1,15 @@
+apply plugin: 'android-library'
+
+dependencies {
+ compile project(':lib2')
+}
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+ defaultConfig {
+ minSdkVersion 14
+ targetSdkVersion 15
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/libsTest/lib1/proguard-project.txt b/build-system/tests/libsTest/lib1/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/build-system/tests/libsTest/lib1/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/build-system/tests/libsTest/lib1/src/instrumentTest/java/com/android/tests/libstest/lib1/MainActivityTest.java b/build-system/tests/libsTest/lib1/src/instrumentTest/java/com/android/tests/libstest/lib1/MainActivityTest.java
new file mode 100644
index 0000000..4ed7ae6
--- /dev/null
+++ b/build-system/tests/libsTest/lib1/src/instrumentTest/java/com/android/tests/libstest/lib1/MainActivityTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.libstest.lib1;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private TextView mLib1TextView1;
+ private TextView mLib1TextView2;
+ private TextView mLib2TextView1;
+ private TextView mLib2TextView2;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public MainActivityTest() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final MainActivity a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ mLib1TextView1 = (TextView) a.findViewById(R.id.lib1_text1);
+ mLib1TextView2 = (TextView) a.findViewById(R.id.lib1_text2);
+ mLib2TextView1 = (TextView) a.findViewById(R.id.lib2_text1);
+ mLib2TextView2 = (TextView) a.findViewById(R.id.lib2_text2);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mLib1TextView1);
+ assertNotNull(mLib1TextView2);
+ assertNotNull(mLib2TextView1);
+ assertNotNull(mLib2TextView2);
+ }
+
+ @MediumTest
+ public void testAndroidStrings() {
+ assertEquals("SUCCESS-LIB1", mLib1TextView1.getText());
+ assertEquals("SUCCESS-LIB2", mLib2TextView1.getText());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB1", mLib1TextView2.getText());
+ assertEquals("SUCCESS-LIB2", mLib2TextView2.getText());
+ }
+}
diff --git a/build-system/tests/libsTest/lib1/src/main/AndroidManifest.xml b/build-system/tests/libsTest/lib1/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..7739b4a
--- /dev/null
+++ b/build-system/tests/libsTest/lib1/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.libstest.lib1"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/lib1_name" >
+ <activity
+ android:name="MainActivity"
+ android:label="@string/lib1_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/build-system/tests/libsTest/lib1/src/main/java/com/android/tests/libstest/lib1/Lib1.java b/build-system/tests/libsTest/lib1/src/main/java/com/android/tests/libstest/lib1/Lib1.java
new file mode 100644
index 0000000..c62bec2
--- /dev/null
+++ b/build-system/tests/libsTest/lib1/src/main/java/com/android/tests/libstest/lib1/Lib1.java
@@ -0,0 +1,43 @@
+package com.android.tests.libstest.lib1;
+
+import android.app.Activity;
+import android.widget.TextView;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+public class Lib1 {
+
+ public static void handleTextView(Activity a) {
+ TextView tv = (TextView) a.findViewById(R.id.lib1_text2);
+ if (tv != null) {
+ tv.setText(Lib1.getContent());
+ }
+ }
+
+ public static String getContent() {
+ InputStream input = Lib1.class.getResourceAsStream("Lib1.txt");
+ if (input == null) {
+ return "FAILED TO FIND Lib1.txt";
+ }
+
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+
+ return reader.readLine();
+ } catch (IOException e) {
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ return "FAILED TO READ CONTENT";
+ }
+}
diff --git a/build-system/tests/libsTest/lib1/src/main/java/com/android/tests/libstest/lib1/MainActivity.java b/build-system/tests/libsTest/lib1/src/main/java/com/android/tests/libstest/lib1/MainActivity.java
new file mode 100644
index 0000000..078bf64
--- /dev/null
+++ b/build-system/tests/libsTest/lib1/src/main/java/com/android/tests/libstest/lib1/MainActivity.java
@@ -0,0 +1,18 @@
+package com.android.tests.libstest.lib1;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.tests.libstest.lib2.Lib2;
+
+public class MainActivity extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.lib1_main);
+
+ Lib1.handleTextView(this);
+ Lib2.handleTextView(this);
+ }
+}
diff --git a/build-system/tests/libsTest/lib1/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/tests/libsTest/lib1/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..8074c4c
--- /dev/null
+++ b/build-system/tests/libsTest/lib1/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/libsTest/lib1/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/tests/libsTest/lib1/src/main/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..1095584
--- /dev/null
+++ b/build-system/tests/libsTest/lib1/src/main/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/libsTest/lib1/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/tests/libsTest/lib1/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/libsTest/lib1/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/libsTest/lib1/src/main/res/layout/lib1_main.xml b/build-system/tests/libsTest/lib1/src/main/res/layout/lib1_main.xml
new file mode 100644
index 0000000..3666d12
--- /dev/null
+++ b/build-system/tests/libsTest/lib1/src/main/res/layout/lib1_main.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/lib1_text1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/lib1_string" />
+
+ <TextView
+ android:id="@+id/lib1_text2"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+ <include layout="@layout/lib2_main" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/build-system/tests/libsTest/lib1/src/main/res/values/strings.xml b/build-system/tests/libsTest/lib1/src/main/res/values/strings.xml
new file mode 100644
index 0000000..8d20610
--- /dev/null
+++ b/build-system/tests/libsTest/lib1/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="lib1_name">LibsTest-lib1</string>
+ <string name="lib1_string">SUCCESS-LIB1</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/libsTest/lib1/src/main/resources/com/android/tests/libstest/lib1/Lib1.txt b/build-system/tests/libsTest/lib1/src/main/resources/com/android/tests/libstest/lib1/Lib1.txt
new file mode 100644
index 0000000..452e397
--- /dev/null
+++ b/build-system/tests/libsTest/lib1/src/main/resources/com/android/tests/libstest/lib1/Lib1.txt
@@ -0,0 +1 @@
+SUCCESS-LIB1
\ No newline at end of file
diff --git a/build-system/tests/libsTest/lib2/build.gradle b/build-system/tests/libsTest/lib2/build.gradle
new file mode 100644
index 0000000..4b2a733
--- /dev/null
+++ b/build-system/tests/libsTest/lib2/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'android-library'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
\ No newline at end of file
diff --git a/build-system/tests/libsTest/lib2/proguard-project.txt b/build-system/tests/libsTest/lib2/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/build-system/tests/libsTest/lib2/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/build-system/tests/libsTest/lib2/src/instrumentTest/java/com/android/tests/libstest/lib2/MainActivityTest.java b/build-system/tests/libsTest/lib2/src/instrumentTest/java/com/android/tests/libstest/lib2/MainActivityTest.java
new file mode 100644
index 0000000..6ac4a5c
--- /dev/null
+++ b/build-system/tests/libsTest/lib2/src/instrumentTest/java/com/android/tests/libstest/lib2/MainActivityTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.libstest.lib2;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+import com.android.tests.libstest.lib2.R;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private TextView mTextView1;
+ private TextView mTextView2;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public MainActivityTest() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final MainActivity a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ mTextView1 = (TextView) a.findViewById(R.id.lib2_text1);
+ mTextView2 = (TextView) a.findViewById(R.id.lib2_text2);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView1);
+ assertNotNull(mTextView2);
+ }
+
+ @MediumTest
+ public void testAndroidStrings() {
+ assertEquals("SUCCESS-LIB2", mTextView1.getText());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB2", mTextView2.getText());
+ }
+}
diff --git a/build-system/tests/libsTest/lib2/src/main/AndroidManifest.xml b/build-system/tests/libsTest/lib2/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..9374b53
--- /dev/null
+++ b/build-system/tests/libsTest/lib2/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.libstest.lib2"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/lib2_name" >
+ <activity
+ android:name="MainActivity"
+ android:label="@string/lib2_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/build-system/tests/libsTest/lib2/src/main/java/com/android/tests/libstest/lib2/Lib2.java b/build-system/tests/libsTest/lib2/src/main/java/com/android/tests/libstest/lib2/Lib2.java
new file mode 100644
index 0000000..bb8e4db
--- /dev/null
+++ b/build-system/tests/libsTest/lib2/src/main/java/com/android/tests/libstest/lib2/Lib2.java
@@ -0,0 +1,43 @@
+package com.android.tests.libstest.lib2;
+
+import android.app.Activity;
+import android.widget.TextView;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+public class Lib2 {
+
+ public static void handleTextView(Activity a) {
+ TextView tv = (TextView) a.findViewById(R.id.lib2_text2);
+ if (tv != null) {
+ tv.setText(getContent());
+ }
+ }
+
+ private static String getContent() {
+ InputStream input = Lib2.class.getResourceAsStream("Lib2.txt");
+ if (input == null) {
+ return "FAILED TO FIND Lib2.txt";
+ }
+
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+
+ return reader.readLine();
+ } catch (IOException e) {
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ return "FAILED TO READ CONTENT";
+ }
+}
diff --git a/build-system/tests/libsTest/lib2/src/main/java/com/android/tests/libstest/lib2/MainActivity.java b/build-system/tests/libsTest/lib2/src/main/java/com/android/tests/libstest/lib2/MainActivity.java
new file mode 100644
index 0000000..012f203
--- /dev/null
+++ b/build-system/tests/libsTest/lib2/src/main/java/com/android/tests/libstest/lib2/MainActivity.java
@@ -0,0 +1,15 @@
+package com.android.tests.libstest.lib2;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class MainActivity extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.lib2_main);
+
+ Lib2.handleTextView(this);
+ }
+}
diff --git a/build-system/tests/libsTest/lib2/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/tests/libsTest/lib2/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..8074c4c
--- /dev/null
+++ b/build-system/tests/libsTest/lib2/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/libsTest/lib2/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/tests/libsTest/lib2/src/main/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..1095584
--- /dev/null
+++ b/build-system/tests/libsTest/lib2/src/main/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/libsTest/lib2/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/tests/libsTest/lib2/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/libsTest/lib2/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/libsTest/lib2/src/main/res/layout/lib2_main.xml b/build-system/tests/libsTest/lib2/src/main/res/layout/lib2_main.xml
new file mode 100644
index 0000000..bb639d1
--- /dev/null
+++ b/build-system/tests/libsTest/lib2/src/main/res/layout/lib2_main.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/lib2_text1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/lib2_string" />
+
+ <TextView
+ android:id="@+id/lib2_text2"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/build-system/tests/libsTest/lib2/src/main/res/values/strings.xml b/build-system/tests/libsTest/lib2/src/main/res/values/strings.xml
new file mode 100644
index 0000000..215b8fa
--- /dev/null
+++ b/build-system/tests/libsTest/lib2/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="lib2_name">LibsTest-lib2</string>
+ <string name="lib2_string">SUCCESS-LIB2</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/libsTest/lib2/src/main/resources/com/android/tests/libstest/lib2/Lib2.txt b/build-system/tests/libsTest/lib2/src/main/resources/com/android/tests/libstest/lib2/Lib2.txt
new file mode 100644
index 0000000..94cabe4
--- /dev/null
+++ b/build-system/tests/libsTest/lib2/src/main/resources/com/android/tests/libstest/lib2/Lib2.txt
@@ -0,0 +1 @@
+SUCCESS-LIB2
\ No newline at end of file
diff --git a/build-system/tests/libsTest/lib2b/build.gradle b/build-system/tests/libsTest/lib2b/build.gradle
new file mode 100644
index 0000000..4b2a733
--- /dev/null
+++ b/build-system/tests/libsTest/lib2b/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'android-library'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
\ No newline at end of file
diff --git a/build-system/tests/libsTest/lib2b/proguard-project.txt b/build-system/tests/libsTest/lib2b/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/build-system/tests/libsTest/lib2b/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/build-system/tests/libsTest/lib2b/src/instrumentTest/java/com/android/tests/libstest/lib2/MainActivity2bTest.java b/build-system/tests/libsTest/lib2b/src/instrumentTest/java/com/android/tests/libstest/lib2/MainActivity2bTest.java
new file mode 100644
index 0000000..35c56e3
--- /dev/null
+++ b/build-system/tests/libsTest/lib2b/src/instrumentTest/java/com/android/tests/libstest/lib2/MainActivity2bTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.libstest.lib2;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+import com.android.tests.libstest.lib2.R;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class MainActivity2bTest extends ActivityInstrumentationTestCase2<MainActivity2b> {
+
+ private TextView mTextView1;
+ private TextView mTextView2;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public MainActivity2bTest() {
+ super(MainActivity2b.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final MainActivity2b a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ mTextView1 = (TextView) a.findViewById(R.id.lib2b_text1);
+ mTextView2 = (TextView) a.findViewById(R.id.lib2b_text2);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView1);
+ assertNotNull(mTextView2);
+ }
+
+ @MediumTest
+ public void testAndroidStrings() {
+ assertEquals("SUCCESS-LIB2b", mTextView1.getText());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB2b", mTextView2.getText());
+ }
+}
diff --git a/build-system/tests/libsTest/lib2b/src/main/AndroidManifest.xml b/build-system/tests/libsTest/lib2b/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..814af46
--- /dev/null
+++ b/build-system/tests/libsTest/lib2b/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.libstest.lib2"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/lib2b_name" >
+ <activity
+ android:name="MainActivity2b"
+ android:label="@string/lib2b_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/build-system/tests/libsTest/lib2b/src/main/java/com/android/tests/libstest/lib2/Lib2b.java b/build-system/tests/libsTest/lib2b/src/main/java/com/android/tests/libstest/lib2/Lib2b.java
new file mode 100644
index 0000000..e4329e5
--- /dev/null
+++ b/build-system/tests/libsTest/lib2b/src/main/java/com/android/tests/libstest/lib2/Lib2b.java
@@ -0,0 +1,43 @@
+package com.android.tests.libstest.lib2;
+
+import android.app.Activity;
+import android.widget.TextView;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+public class Lib2b {
+
+ public static void handleTextView(Activity a) {
+ TextView tv = (TextView) a.findViewById(R.id.lib2b_text2);
+ if (tv != null) {
+ tv.setText(getContent());
+ }
+ }
+
+ private static String getContent() {
+ InputStream input = Lib2b.class.getResourceAsStream("Lib2b.txt");
+ if (input == null) {
+ return "FAILED TO FIND Lib2b.txt";
+ }
+
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+
+ return reader.readLine();
+ } catch (IOException e) {
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ return "FAILED TO READ CONTENT";
+ }
+}
diff --git a/build-system/tests/libsTest/lib2b/src/main/java/com/android/tests/libstest/lib2/MainActivity2b.java b/build-system/tests/libsTest/lib2b/src/main/java/com/android/tests/libstest/lib2/MainActivity2b.java
new file mode 100644
index 0000000..2e09018
--- /dev/null
+++ b/build-system/tests/libsTest/lib2b/src/main/java/com/android/tests/libstest/lib2/MainActivity2b.java
@@ -0,0 +1,15 @@
+package com.android.tests.libstest.lib2;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class MainActivity2b extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.lib2b_main);
+
+ Lib2b.handleTextView(this);
+ }
+}
diff --git a/build-system/tests/libsTest/lib2b/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/tests/libsTest/lib2b/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..8074c4c
--- /dev/null
+++ b/build-system/tests/libsTest/lib2b/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/libsTest/lib2b/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/tests/libsTest/lib2b/src/main/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..1095584
--- /dev/null
+++ b/build-system/tests/libsTest/lib2b/src/main/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/libsTest/lib2b/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/tests/libsTest/lib2b/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/libsTest/lib2b/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/libsTest/lib2b/src/main/res/layout/lib2b_main.xml b/build-system/tests/libsTest/lib2b/src/main/res/layout/lib2b_main.xml
new file mode 100644
index 0000000..01e6a9f
--- /dev/null
+++ b/build-system/tests/libsTest/lib2b/src/main/res/layout/lib2b_main.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/lib2b_text1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/lib2b_string" />
+
+ <TextView
+ android:id="@+id/lib2b_text2"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/build-system/tests/libsTest/lib2b/src/main/res/values/strings.xml b/build-system/tests/libsTest/lib2b/src/main/res/values/strings.xml
new file mode 100644
index 0000000..d21e21f
--- /dev/null
+++ b/build-system/tests/libsTest/lib2b/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="lib2b_name">LibsTest-lib2b</string>
+ <string name="lib2b_string">SUCCESS-LIB2b</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/libsTest/lib2b/src/main/resources/com/android/tests/libstest/lib2/Lib2b.txt b/build-system/tests/libsTest/lib2b/src/main/resources/com/android/tests/libstest/lib2/Lib2b.txt
new file mode 100644
index 0000000..59e6b48
--- /dev/null
+++ b/build-system/tests/libsTest/lib2b/src/main/resources/com/android/tests/libstest/lib2/Lib2b.txt
@@ -0,0 +1 @@
+SUCCESS-LIB2b
\ No newline at end of file
diff --git a/build-system/tests/libsTest/libapp/build.gradle b/build-system/tests/libsTest/libapp/build.gradle
new file mode 100644
index 0000000..4b2a733
--- /dev/null
+++ b/build-system/tests/libsTest/libapp/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'android-library'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
\ No newline at end of file
diff --git a/build-system/tests/libsTest/libapp/proguard-project.txt b/build-system/tests/libsTest/libapp/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/build-system/tests/libsTest/libapp/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/build-system/tests/libsTest/libapp/src/instrumentTest/java/com/android/tests/libstest/libapp/MainActivityLibAppTest.java b/build-system/tests/libsTest/libapp/src/instrumentTest/java/com/android/tests/libstest/libapp/MainActivityLibAppTest.java
new file mode 100644
index 0000000..e6087a7
--- /dev/null
+++ b/build-system/tests/libsTest/libapp/src/instrumentTest/java/com/android/tests/libstest/libapp/MainActivityLibAppTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.libstest.app;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+import com.android.tests.libstest.app.R;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class MainActivityLibAppTest extends ActivityInstrumentationTestCase2<MainActivityLibApp> {
+
+ private TextView mTextView1;
+ private TextView mTextView2;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public MainActivityLibAppTest() {
+ super(MainActivityLibApp.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final MainActivityLibApp a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ mTextView1 = (TextView) a.findViewById(R.id.libapp_text1);
+ mTextView2 = (TextView) a.findViewById(R.id.libapp_text2);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView1);
+ assertNotNull(mTextView2);
+ }
+
+ @MediumTest
+ public void testAndroidStrings() {
+ assertEquals("SUCCESS-LIBAPP", mTextView1.getText());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIBAPP", mTextView2.getText());
+ }
+}
diff --git a/build-system/tests/libsTest/libapp/src/main/AndroidManifest.xml b/build-system/tests/libsTest/libapp/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..0592d2d
--- /dev/null
+++ b/build-system/tests/libsTest/libapp/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.libstest.app"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/libapp_name" >
+ <activity
+ android:name="MainActivityLibApp"
+ android:label="@string/libapp_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/build-system/tests/libsTest/libapp/src/main/java/com/android/tests/libstest/app/LibApp.java b/build-system/tests/libsTest/libapp/src/main/java/com/android/tests/libstest/app/LibApp.java
new file mode 100644
index 0000000..9a25e9e
--- /dev/null
+++ b/build-system/tests/libsTest/libapp/src/main/java/com/android/tests/libstest/app/LibApp.java
@@ -0,0 +1,43 @@
+package com.android.tests.libstest.app;
+
+import android.app.Activity;
+import android.widget.TextView;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+public class LibApp {
+
+ public static void handleTextView(Activity a) {
+ TextView tv = (TextView) a.findViewById(R.id.libapp_text2);
+ if (tv != null) {
+ tv.setText(getContent());
+ }
+ }
+
+ private static String getContent() {
+ InputStream input = LibApp.class.getResourceAsStream("Libapp.txt");
+ if (input == null) {
+ return "FAILED TO FIND Libapp.txt";
+ }
+
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+
+ return reader.readLine();
+ } catch (IOException e) {
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ return "FAILED TO READ CONTENT";
+ }
+}
diff --git a/build-system/tests/libsTest/libapp/src/main/java/com/android/tests/libstest/app/MainActivityLibApp.java b/build-system/tests/libsTest/libapp/src/main/java/com/android/tests/libstest/app/MainActivityLibApp.java
new file mode 100644
index 0000000..65460f7
--- /dev/null
+++ b/build-system/tests/libsTest/libapp/src/main/java/com/android/tests/libstest/app/MainActivityLibApp.java
@@ -0,0 +1,15 @@
+package com.android.tests.libstest.app;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class MainActivityLibApp extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.libapp_main);
+
+ LibApp.handleTextView(this);
+ }
+}
diff --git a/build-system/tests/libsTest/libapp/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/tests/libsTest/libapp/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..8074c4c
--- /dev/null
+++ b/build-system/tests/libsTest/libapp/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/libsTest/libapp/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/tests/libsTest/libapp/src/main/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..1095584
--- /dev/null
+++ b/build-system/tests/libsTest/libapp/src/main/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/libsTest/libapp/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/tests/libsTest/libapp/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/libsTest/libapp/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/libsTest/libapp/src/main/res/layout/libapp_main.xml b/build-system/tests/libsTest/libapp/src/main/res/layout/libapp_main.xml
new file mode 100644
index 0000000..6bf607b
--- /dev/null
+++ b/build-system/tests/libsTest/libapp/src/main/res/layout/libapp_main.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/libapp_text1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/libapp_string" />
+
+ <TextView
+ android:id="@+id/libapp_text2"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/build-system/tests/libsTest/libapp/src/main/res/values/strings.xml b/build-system/tests/libsTest/libapp/src/main/res/values/strings.xml
new file mode 100644
index 0000000..0a0a597
--- /dev/null
+++ b/build-system/tests/libsTest/libapp/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="libapp_name">LibsTest-libapp</string>
+ <string name="libapp_string">SUCCESS-LIBAPP</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/libsTest/libapp/src/main/resources/com/android/tests/libstest/app/Libapp.txt b/build-system/tests/libsTest/libapp/src/main/resources/com/android/tests/libstest/app/Libapp.txt
new file mode 100644
index 0000000..9d626ac
--- /dev/null
+++ b/build-system/tests/libsTest/libapp/src/main/resources/com/android/tests/libstest/app/Libapp.txt
@@ -0,0 +1 @@
+SUCCESS-LIBAPP
\ No newline at end of file
diff --git a/build-system/tests/libsTest/settings.gradle b/build-system/tests/libsTest/settings.gradle
new file mode 100644
index 0000000..c8ed4b4
--- /dev/null
+++ b/build-system/tests/libsTest/settings.gradle
@@ -0,0 +1,5 @@
+include 'app'
+include 'lib1'
+include 'lib2'
+include 'lib2b'
+include 'libapp'
diff --git a/build-system/tests/localJars/app/build.gradle b/build-system/tests/localJars/app/build.gradle
new file mode 100644
index 0000000..286d360
--- /dev/null
+++ b/build-system/tests/localJars/app/build.gradle
@@ -0,0 +1,10 @@
+apply plugin: 'android'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
+
+dependencies {
+ compile project(':library')
+}
diff --git a/build-system/tests/localJars/app/src/main/AndroidManifest.xml b/build-system/tests/localJars/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..71e7a47
--- /dev/null
+++ b/build-system/tests/localJars/app/src/main/AndroidManifest.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.multiproject"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <application android:label="@string/app_name" android:icon="@drawable/ic_launcher">
+ <activity android:name="MainActivity"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/build-system/tests/localJars/app/src/main/java/com/example/android/multiproject/MainActivity.java b/build-system/tests/localJars/app/src/main/java/com/example/android/multiproject/MainActivity.java
new file mode 100644
index 0000000..11d7c32
--- /dev/null
+++ b/build-system/tests/localJars/app/src/main/java/com/example/android/multiproject/MainActivity.java
@@ -0,0 +1,21 @@
+package com.example.android.multiproject;
+
+import android.app.Activity;
+import android.view.View;
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.example.android.multiproject.library.ShowPeopleActivity;
+
+public class MainActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ }
+
+ public void sendMessage(View view) {
+ Intent intent = new Intent(this, ShowPeopleActivity.class);
+ startActivity(intent);
+ }
+}
diff --git a/templates/gradle-projects/NewAndroidLibrary/root/res/drawable-hdpi/ic_launcher.png b/build-system/tests/localJars/app/src/main/res/drawable-hdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
copy from templates/gradle-projects/NewAndroidLibrary/root/res/drawable-hdpi/ic_launcher.png
copy to build-system/tests/localJars/app/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/localJars/app/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/tests/localJars/app/src/main/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..9923872
--- /dev/null
+++ b/build-system/tests/localJars/app/src/main/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/templates/gradle-projects/NewAndroidLibrary/root/res/drawable-mdpi/ic_launcher.png b/build-system/tests/localJars/app/src/main/res/drawable-mdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
copy from templates/gradle-projects/NewAndroidLibrary/root/res/drawable-mdpi/ic_launcher.png
copy to build-system/tests/localJars/app/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/templates/gradle-projects/NewAndroidLibrary/root/res/drawable-xhdpi/ic_launcher.png b/build-system/tests/localJars/app/src/main/res/drawable-xhdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
copy from templates/gradle-projects/NewAndroidLibrary/root/res/drawable-xhdpi/ic_launcher.png
copy to build-system/tests/localJars/app/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/localJars/app/src/main/res/layout/main.xml b/build-system/tests/localJars/app/src/main/res/layout/main.xml
new file mode 100644
index 0000000..ccc59fb
--- /dev/null
+++ b/build-system/tests/localJars/app/src/main/res/layout/main.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/button_send"
+ android:onClick="sendMessage" />
+</LinearLayout>
diff --git a/build-system/tests/localJars/app/src/main/res/values/strings.xml b/build-system/tests/localJars/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..e1f49b6
--- /dev/null
+++ b/build-system/tests/localJars/app/src/main/res/values/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">Composite App</string>
+ <string name="button_send">Go</string>
+</resources>
diff --git a/build-system/tests/localJars/baseLibrary/build.gradle b/build-system/tests/localJars/baseLibrary/build.gradle
new file mode 100644
index 0000000..e8609b5
--- /dev/null
+++ b/build-system/tests/localJars/baseLibrary/build.gradle
@@ -0,0 +1,11 @@
+apply plugin: 'android-library'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: '*.jar')
+ compile 'com.google.guava:guava:11.0.2'
+}
diff --git a/build-system/tests/localJars/baseLibrary/libs/util-1.0.jar b/build-system/tests/localJars/baseLibrary/libs/util-1.0.jar
new file mode 100644
index 0000000..58972e7
--- /dev/null
+++ b/build-system/tests/localJars/baseLibrary/libs/util-1.0.jar
Binary files differ
diff --git a/build-system/tests/localJars/baseLibrary/src/main/AndroidManifest.xml b/build-system/tests/localJars/baseLibrary/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..54d079c
--- /dev/null
+++ b/build-system/tests/localJars/baseLibrary/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.multiproject.library.base">
+</manifest>
diff --git a/build-system/tests/localJars/baseLibrary/src/main/java/com/sample/android/multiproject/library/PersonView.java b/build-system/tests/localJars/baseLibrary/src/main/java/com/sample/android/multiproject/library/PersonView.java
new file mode 100644
index 0000000..b218532
--- /dev/null
+++ b/build-system/tests/localJars/baseLibrary/src/main/java/com/sample/android/multiproject/library/PersonView.java
@@ -0,0 +1,13 @@
+package com.example.android.multiproject.library;
+
+import android.widget.TextView;
+import android.content.Context;
+import com.example.android.multiproject.person.Person;
+
+class PersonView extends TextView {
+ public PersonView(Context context, Person person) {
+ super(context);
+ setTextSize(20);
+ setText(person.getName());
+ }
+}
diff --git a/build-system/tests/localJars/build.gradle b/build-system/tests/localJars/build.gradle
new file mode 100644
index 0000000..0916ba9
--- /dev/null
+++ b/build-system/tests/localJars/build.gradle
@@ -0,0 +1,18 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+
+allprojects {
+ version = '1.0'
+
+ repositories {
+ mavenCentral()
+ }
+}
+
+apply plugin: 'android-reporting'
\ No newline at end of file
diff --git a/build-system/tests/localJars/library/build.gradle b/build-system/tests/localJars/library/build.gradle
new file mode 100644
index 0000000..979a308
--- /dev/null
+++ b/build-system/tests/localJars/library/build.gradle
@@ -0,0 +1,10 @@
+apply plugin: 'android-library'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
+
+dependencies {
+ compile project(':baseLibrary')
+}
diff --git a/build-system/tests/localJars/library/src/main/AndroidManifest.xml b/build-system/tests/localJars/library/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..2bc9331
--- /dev/null
+++ b/build-system/tests/localJars/library/src/main/AndroidManifest.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.multiproject.library">
+ <application>
+ <activity
+ android:name="ShowPeopleActivity"
+ android:label="@string/title_activity_display_message" >
+ </activity>
+ </application>
+</manifest>
diff --git a/build-system/tests/localJars/library/src/main/java/com/example/android/multiproject/library/ShowPeopleActivity.java b/build-system/tests/localJars/library/src/main/java/com/example/android/multiproject/library/ShowPeopleActivity.java
new file mode 100644
index 0000000..a3f2195
--- /dev/null
+++ b/build-system/tests/localJars/library/src/main/java/com/example/android/multiproject/library/ShowPeopleActivity.java
@@ -0,0 +1,30 @@
+package com.example.android.multiproject.library;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.content.Intent;
+import android.widget.TextView;
+import android.widget.LinearLayout;
+
+import java.lang.String;
+import java.util.Arrays;
+
+import com.example.android.multiproject.person.Person;
+import com.example.android.multiproject.person.People;
+
+public class ShowPeopleActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ LinearLayout group = new LinearLayout(this);
+ group.setOrientation(LinearLayout.VERTICAL);
+
+ Iterable<Person> people = new People();
+ for (Person person : people) {
+ group.addView(new PersonView(this, person));
+ }
+
+ setContentView(group);
+ }
+}
diff --git a/build-system/tests/localJars/library/src/main/res/values/strings.xml b/build-system/tests/localJars/library/src/main/res/values/strings.xml
new file mode 100644
index 0000000..45e9dbb
--- /dev/null
+++ b/build-system/tests/localJars/library/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="title_activity_display_message">People</string>
+</resources>
diff --git a/build-system/tests/localJars/settings.gradle b/build-system/tests/localJars/settings.gradle
new file mode 100644
index 0000000..1b43700
--- /dev/null
+++ b/build-system/tests/localJars/settings.gradle
@@ -0,0 +1,3 @@
+include 'app'
+include 'library'
+include 'baseLibrary'
diff --git a/build-system/tests/localJars/util/build.gradle b/build-system/tests/localJars/util/build.gradle
new file mode 100644
index 0000000..c99b1a3
--- /dev/null
+++ b/build-system/tests/localJars/util/build.gradle
@@ -0,0 +1,12 @@
+apply plugin: 'java'
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ compile 'com.google.guava:guava:11.0.2'
+}
+
+sourceCompatibility = "1.6"
+targetCompatibility = "1.6"
diff --git a/build-system/tests/localJars/util/src/main/java/com/example/android/multiproject/person/People.java b/build-system/tests/localJars/util/src/main/java/com/example/android/multiproject/person/People.java
new file mode 100644
index 0000000..8b99248
--- /dev/null
+++ b/build-system/tests/localJars/util/src/main/java/com/example/android/multiproject/person/People.java
@@ -0,0 +1,10 @@
+package com.example.android.multiproject.person;
+
+import java.util.Iterator;
+import com.google.common.collect.Lists;
+
+public class People implements Iterable<Person> {
+ public Iterator<Person> iterator() {
+ return Lists.newArrayList(new Person("Fred"), new Person("Barney")).iterator();
+ }
+}
diff --git a/build-system/tests/localJars/util/src/main/java/com/example/android/multiproject/person/Person.java b/build-system/tests/localJars/util/src/main/java/com/example/android/multiproject/person/Person.java
new file mode 100644
index 0000000..2f4aa9f
--- /dev/null
+++ b/build-system/tests/localJars/util/src/main/java/com/example/android/multiproject/person/Person.java
@@ -0,0 +1,13 @@
+package com.example.android.multiproject.person;
+
+public class Person {
+ private final String name;
+
+ public Person(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/build-system/tests/localJars/util/src/main/resources/META-INF/foo.txt b/build-system/tests/localJars/util/src/main/resources/META-INF/foo.txt
new file mode 100644
index 0000000..1910281
--- /dev/null
+++ b/build-system/tests/localJars/util/src/main/resources/META-INF/foo.txt
@@ -0,0 +1 @@
+foo
\ No newline at end of file
diff --git a/build-system/tests/localJars/util/src/main/resources/META-INF/services/com.foo.MyService b/build-system/tests/localJars/util/src/main/resources/META-INF/services/com.foo.MyService
new file mode 100644
index 0000000..829550a
--- /dev/null
+++ b/build-system/tests/localJars/util/src/main/resources/META-INF/services/com.foo.MyService
@@ -0,0 +1 @@
+com.foo.impl.MyService
diff --git a/build-system/tests/migrated/AndroidManifest.xml b/build-system/tests/migrated/AndroidManifest.xml
new file mode 100644
index 0000000..4f8d570
--- /dev/null
+++ b/build-system/tests/migrated/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.basic">
+ <application android:label="@string/app_name" android:icon="@drawable/icon">
+ <activity android:name=".Main"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <uses-permission android:name="com.blah" />
+
+ <permission-group android:name="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+ <permission android:name="foo.permission.SEND_SMS"
+ android:permissionGroup="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+ <permission android:name="foo.blah.SEND_SMS"
+ android:permissionGroup="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+</manifest>
diff --git a/build-system/tests/migrated/assets/notice.txt b/build-system/tests/migrated/assets/notice.txt
new file mode 100644
index 0000000..33ff961
--- /dev/null
+++ b/build-system/tests/migrated/assets/notice.txt
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2005-2012, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/build-system/tests/migrated/build.gradle b/build-system/tests/migrated/build.gradle
new file mode 100644
index 0000000..6c2470f
--- /dev/null
+++ b/build-system/tests/migrated/build.gradle
@@ -0,0 +1,58 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+apply plugin: 'android'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+ sourceSets {
+ main {
+ manifest {
+ // there's only ever one file so srcFile replaces it.
+ srcFile 'AndroidManifest.xml'
+ }
+ java {
+ // writing:
+ // srcDir 'src'
+ // would *add* to the default folder so we use a different syntax
+ srcDirs = ['src']
+ exclude 'some/unwanted/package/**'
+ }
+ res {
+ srcDirs = ['res']
+ }
+ assets {
+ srcDirs = ['assets']
+ }
+ resources {
+ srcDirs = ['src']
+ }
+ aidl {
+ srcDirs = ['src']
+ }
+ renderscript {
+ srcDirs = ['src']
+ }
+ }
+
+ // this moves src/instrumentTest to tests so all folders follow:
+ // tests/java, tests/res, tests/assets, ...
+ // This is a *reset* so it replaces the default paths
+ instrumentTest.setRoot('tests')
+
+ // Could also be done with:
+ //main.manifest.srcFile 'AndroidManifest.xml'
+ //main.java.srcDir 'src'
+ //main.res.srcDir 'res'
+ //main.assets.srcDir 'assets'
+ //main.resources.srcDir 'src'
+ //instrumentTest.java.srcDir 'tests/src'
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/migrated/res/drawable/icon.png b/build-system/tests/migrated/res/drawable/icon.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/migrated/res/drawable/icon.png
Binary files differ
diff --git a/build-system/tests/migrated/res/layout/main.xml b/build-system/tests/migrated/res/layout/main.xml
new file mode 100644
index 0000000..b199751
--- /dev/null
+++ b/build-system/tests/migrated/res/layout/main.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+<TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="Test App - Basic"
+ android:id="@+id/text"
+ />
+</LinearLayout>
+
diff --git a/build-system/tests/migrated/res/raw/notice.txt b/build-system/tests/migrated/res/raw/notice.txt
new file mode 100644
index 0000000..33ff961
--- /dev/null
+++ b/build-system/tests/migrated/res/raw/notice.txt
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2005-2012, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/build-system/tests/migrated/res/values/strings.xml b/build-system/tests/migrated/res/values/strings.xml
new file mode 100644
index 0000000..60ea2d0
--- /dev/null
+++ b/build-system/tests/migrated/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">_Test-Basic</string>
+</resources>
diff --git a/build-system/tests/migrated/src/com/android/tests/basic/Main.java b/build-system/tests/migrated/src/com/android/tests/basic/Main.java
new file mode 100644
index 0000000..2b0e698
--- /dev/null
+++ b/build-system/tests/migrated/src/com/android/tests/basic/Main.java
@@ -0,0 +1,15 @@
+package com.android.tests.basic;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class Main extends Activity
+{
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ }
+}
diff --git a/build-system/tests/migrated/tests/java/com/android/tests/basic/MainTest.java b/build-system/tests/migrated/tests/java/com/android/tests/basic/MainTest.java
new file mode 100644
index 0000000..7cf7329
--- /dev/null
+++ b/build-system/tests/migrated/tests/java/com/android/tests/basic/MainTest.java
@@ -0,0 +1,38 @@
+package com.android.tests.basic;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+public class MainTest extends ActivityInstrumentationTestCase2<Main> {
+
+ private TextView mTextView;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
+ */
+ public MainTest() {
+ super(Main.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final Main a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+ mTextView = (TextView) a.findViewById(R.id.text);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView);
+ }
+}
+
diff --git a/build-system/tests/multiproject/app/build.gradle b/build-system/tests/multiproject/app/build.gradle
new file mode 100644
index 0000000..286d360
--- /dev/null
+++ b/build-system/tests/multiproject/app/build.gradle
@@ -0,0 +1,10 @@
+apply plugin: 'android'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
+
+dependencies {
+ compile project(':library')
+}
diff --git a/build-system/tests/multiproject/app/src/main/AndroidManifest.xml b/build-system/tests/multiproject/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..71e7a47
--- /dev/null
+++ b/build-system/tests/multiproject/app/src/main/AndroidManifest.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.multiproject"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <application android:label="@string/app_name" android:icon="@drawable/ic_launcher">
+ <activity android:name="MainActivity"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/build-system/tests/multiproject/app/src/main/java/com/example/android/multiproject/MainActivity.java b/build-system/tests/multiproject/app/src/main/java/com/example/android/multiproject/MainActivity.java
new file mode 100644
index 0000000..11d7c32
--- /dev/null
+++ b/build-system/tests/multiproject/app/src/main/java/com/example/android/multiproject/MainActivity.java
@@ -0,0 +1,21 @@
+package com.example.android.multiproject;
+
+import android.app.Activity;
+import android.view.View;
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.example.android.multiproject.library.ShowPeopleActivity;
+
+public class MainActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ }
+
+ public void sendMessage(View view) {
+ Intent intent = new Intent(this, ShowPeopleActivity.class);
+ startActivity(intent);
+ }
+}
diff --git a/templates/gradle-projects/NewAndroidLibrary/root/res/drawable-hdpi/ic_launcher.png b/build-system/tests/multiproject/app/src/main/res/drawable-hdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
copy from templates/gradle-projects/NewAndroidLibrary/root/res/drawable-hdpi/ic_launcher.png
copy to build-system/tests/multiproject/app/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/multiproject/app/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/tests/multiproject/app/src/main/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..9923872
--- /dev/null
+++ b/build-system/tests/multiproject/app/src/main/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/templates/gradle-projects/NewAndroidLibrary/root/res/drawable-mdpi/ic_launcher.png b/build-system/tests/multiproject/app/src/main/res/drawable-mdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
copy from templates/gradle-projects/NewAndroidLibrary/root/res/drawable-mdpi/ic_launcher.png
copy to build-system/tests/multiproject/app/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/templates/gradle-projects/NewAndroidLibrary/root/res/drawable-xhdpi/ic_launcher.png b/build-system/tests/multiproject/app/src/main/res/drawable-xhdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
copy from templates/gradle-projects/NewAndroidLibrary/root/res/drawable-xhdpi/ic_launcher.png
copy to build-system/tests/multiproject/app/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/multiproject/app/src/main/res/layout/main.xml b/build-system/tests/multiproject/app/src/main/res/layout/main.xml
new file mode 100644
index 0000000..ccc59fb
--- /dev/null
+++ b/build-system/tests/multiproject/app/src/main/res/layout/main.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/button_send"
+ android:onClick="sendMessage" />
+</LinearLayout>
diff --git a/build-system/tests/multiproject/app/src/main/res/values/strings.xml b/build-system/tests/multiproject/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..e1f49b6
--- /dev/null
+++ b/build-system/tests/multiproject/app/src/main/res/values/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">Composite App</string>
+ <string name="button_send">Go</string>
+</resources>
diff --git a/build-system/tests/multiproject/baseLibrary/build.gradle b/build-system/tests/multiproject/baseLibrary/build.gradle
new file mode 100644
index 0000000..b746cd3
--- /dev/null
+++ b/build-system/tests/multiproject/baseLibrary/build.gradle
@@ -0,0 +1,10 @@
+apply plugin: 'android-library'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
+
+dependencies {
+ compile project(':util')
+}
diff --git a/build-system/tests/multiproject/baseLibrary/src/main/AndroidManifest.xml b/build-system/tests/multiproject/baseLibrary/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..54d079c
--- /dev/null
+++ b/build-system/tests/multiproject/baseLibrary/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.multiproject.library.base">
+</manifest>
diff --git a/build-system/tests/multiproject/baseLibrary/src/main/java/com/sample/android/multiproject/library/PersonView.java b/build-system/tests/multiproject/baseLibrary/src/main/java/com/sample/android/multiproject/library/PersonView.java
new file mode 100644
index 0000000..9d3b996
--- /dev/null
+++ b/build-system/tests/multiproject/baseLibrary/src/main/java/com/sample/android/multiproject/library/PersonView.java
@@ -0,0 +1,13 @@
+package com.sample.android.multiproject.library;
+
+import android.widget.TextView;
+import android.content.Context;
+import com.example.android.multiproject.person.Person;
+
+public class PersonView extends TextView {
+ public PersonView(Context context, Person person) {
+ super(context);
+ setTextSize(20);
+ setText(person.getName());
+ }
+}
diff --git a/build-system/tests/multiproject/build.gradle b/build-system/tests/multiproject/build.gradle
new file mode 100644
index 0000000..0916ba9
--- /dev/null
+++ b/build-system/tests/multiproject/build.gradle
@@ -0,0 +1,18 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+
+allprojects {
+ version = '1.0'
+
+ repositories {
+ mavenCentral()
+ }
+}
+
+apply plugin: 'android-reporting'
\ No newline at end of file
diff --git a/build-system/tests/multiproject/library/build.gradle b/build-system/tests/multiproject/library/build.gradle
new file mode 100644
index 0000000..4c62fe2
--- /dev/null
+++ b/build-system/tests/multiproject/library/build.gradle
@@ -0,0 +1,11 @@
+apply plugin: 'android-library'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
+
+dependencies {
+ compile project(':baseLibrary')
+ compile project(':util')
+}
diff --git a/build-system/tests/multiproject/library/src/main/AndroidManifest.xml b/build-system/tests/multiproject/library/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..2bc9331
--- /dev/null
+++ b/build-system/tests/multiproject/library/src/main/AndroidManifest.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.multiproject.library">
+ <application>
+ <activity
+ android:name="ShowPeopleActivity"
+ android:label="@string/title_activity_display_message" >
+ </activity>
+ </application>
+</manifest>
diff --git a/build-system/tests/multiproject/library/src/main/java/com/example/android/multiproject/library/ShowPeopleActivity.java b/build-system/tests/multiproject/library/src/main/java/com/example/android/multiproject/library/ShowPeopleActivity.java
new file mode 100644
index 0000000..b0f8b12
--- /dev/null
+++ b/build-system/tests/multiproject/library/src/main/java/com/example/android/multiproject/library/ShowPeopleActivity.java
@@ -0,0 +1,31 @@
+package com.example.android.multiproject.library;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.content.Intent;
+import android.widget.TextView;
+import android.widget.LinearLayout;
+
+import java.lang.String;
+import java.util.Arrays;
+
+import com.example.android.multiproject.person.Person;
+import com.example.android.multiproject.person.People;
+import com.sample.android.multiproject.library.PersonView;
+
+public class ShowPeopleActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ LinearLayout group = new LinearLayout(this);
+ group.setOrientation(LinearLayout.VERTICAL);
+
+ Iterable<Person> people = new People();
+ for (Person person : people) {
+ group.addView(new PersonView(this, person));
+ }
+
+ setContentView(group);
+ }
+}
diff --git a/build-system/tests/multiproject/library/src/main/res/values/strings.xml b/build-system/tests/multiproject/library/src/main/res/values/strings.xml
new file mode 100644
index 0000000..45e9dbb
--- /dev/null
+++ b/build-system/tests/multiproject/library/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="title_activity_display_message">People</string>
+</resources>
diff --git a/build-system/tests/multiproject/settings.gradle b/build-system/tests/multiproject/settings.gradle
new file mode 100644
index 0000000..4ba4df5
--- /dev/null
+++ b/build-system/tests/multiproject/settings.gradle
@@ -0,0 +1,4 @@
+include 'app'
+include 'library'
+include 'baseLibrary'
+include 'util'
diff --git a/build-system/tests/multiproject/util/build.gradle b/build-system/tests/multiproject/util/build.gradle
new file mode 100644
index 0000000..dff7725
--- /dev/null
+++ b/build-system/tests/multiproject/util/build.gradle
@@ -0,0 +1,8 @@
+apply plugin: 'java'
+
+dependencies {
+ compile 'com.google.guava:guava:11.0.2'
+}
+
+sourceCompatibility = "1.6"
+targetCompatibility = "1.6"
diff --git a/build-system/tests/multiproject/util/src/main/java/com/example/android/multiproject/person/People.java b/build-system/tests/multiproject/util/src/main/java/com/example/android/multiproject/person/People.java
new file mode 100644
index 0000000..8b99248
--- /dev/null
+++ b/build-system/tests/multiproject/util/src/main/java/com/example/android/multiproject/person/People.java
@@ -0,0 +1,10 @@
+package com.example.android.multiproject.person;
+
+import java.util.Iterator;
+import com.google.common.collect.Lists;
+
+public class People implements Iterable<Person> {
+ public Iterator<Person> iterator() {
+ return Lists.newArrayList(new Person("Fred"), new Person("Barney")).iterator();
+ }
+}
diff --git a/build-system/tests/multiproject/util/src/main/java/com/example/android/multiproject/person/Person.java b/build-system/tests/multiproject/util/src/main/java/com/example/android/multiproject/person/Person.java
new file mode 100644
index 0000000..2f4aa9f
--- /dev/null
+++ b/build-system/tests/multiproject/util/src/main/java/com/example/android/multiproject/person/Person.java
@@ -0,0 +1,13 @@
+package com.example.android.multiproject.person;
+
+public class Person {
+ private final String name;
+
+ public Person(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/build-system/tests/multires/build.gradle b/build-system/tests/multires/build.gradle
new file mode 100644
index 0000000..dafdfae
--- /dev/null
+++ b/build-system/tests/multires/build.gradle
@@ -0,0 +1,22 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+apply plugin: 'android'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+ sourceSets {
+ main {
+ res {
+ srcDirs 'src/main/res1', 'src/main/res2'
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/multires/src/instrumentTest/java/com/android/tests/basic/MainTest.java b/build-system/tests/multires/src/instrumentTest/java/com/android/tests/basic/MainTest.java
new file mode 100644
index 0000000..7cf7329
--- /dev/null
+++ b/build-system/tests/multires/src/instrumentTest/java/com/android/tests/basic/MainTest.java
@@ -0,0 +1,38 @@
+package com.android.tests.basic;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+public class MainTest extends ActivityInstrumentationTestCase2<Main> {
+
+ private TextView mTextView;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
+ */
+ public MainTest() {
+ super(Main.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final Main a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+ mTextView = (TextView) a.findViewById(R.id.text);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView);
+ }
+}
+
diff --git a/build-system/tests/multires/src/main/AndroidManifest.xml b/build-system/tests/multires/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a34d937
--- /dev/null
+++ b/build-system/tests/multires/src/main/AndroidManifest.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.basic">
+ <application android:label="@string/app_name" android:icon="@drawable/icon">
+ <activity android:name=".Main"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/build-system/tests/multires/src/main/assets/notice.txt b/build-system/tests/multires/src/main/assets/notice.txt
new file mode 100644
index 0000000..e9dcfec
--- /dev/null
+++ b/build-system/tests/multires/src/main/assets/notice.txt
@@ -0,0 +1 @@
+Some notice.
\ No newline at end of file
diff --git a/build-system/tests/multires/src/main/java/com/android/tests/basic/Main.java b/build-system/tests/multires/src/main/java/com/android/tests/basic/Main.java
new file mode 100644
index 0000000..2b0e698
--- /dev/null
+++ b/build-system/tests/multires/src/main/java/com/android/tests/basic/Main.java
@@ -0,0 +1,15 @@
+package com.android.tests.basic;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class Main extends Activity
+{
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ }
+}
diff --git a/build-system/tests/multires/src/main/res1/raw/notice.txt b/build-system/tests/multires/src/main/res1/raw/notice.txt
new file mode 100644
index 0000000..02435db
--- /dev/null
+++ b/build-system/tests/multires/src/main/res1/raw/notice.txt
@@ -0,0 +1 @@
+Some raw file.
\ No newline at end of file
diff --git a/build-system/tests/multires/src/main/res1/values/strings.xml b/build-system/tests/multires/src/main/res1/values/strings.xml
new file mode 100644
index 0000000..66eeb06
--- /dev/null
+++ b/build-system/tests/multires/src/main/res1/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">_Test-Multires</string>
+</resources>
diff --git a/build-system/tests/multires/src/main/res2/drawable/icon.png b/build-system/tests/multires/src/main/res2/drawable/icon.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/multires/src/main/res2/drawable/icon.png
Binary files differ
diff --git a/build-system/tests/multires/src/main/res2/layout/main.xml b/build-system/tests/multires/src/main/res2/layout/main.xml
new file mode 100644
index 0000000..b199751
--- /dev/null
+++ b/build-system/tests/multires/src/main/res2/layout/main.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+<TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="Test App - Basic"
+ android:id="@+id/text"
+ />
+</LinearLayout>
+
diff --git a/build-system/tests/ndkJniLib/app/build.gradle b/build-system/tests/ndkJniLib/app/build.gradle
new file mode 100644
index 0000000..0c26b2a
--- /dev/null
+++ b/build-system/tests/ndkJniLib/app/build.gradle
@@ -0,0 +1,28 @@
+apply plugin: 'android'
+
+dependencies {
+ compile project(':lib')
+}
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+ productFlavors {
+ x86 {
+ ndk {
+ abiFilter "x86"
+ }
+ }
+ arm {
+ ndk {
+ abiFilter "armeabi-v7a"
+ }
+ }
+ mips {
+ ndk {
+ abiFilter "mips"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/ndkJniLib/app/src/instrumentTest/java/com/example/hellojni/lib/HelloJniTest.java b/build-system/tests/ndkJniLib/app/src/instrumentTest/java/com/example/hellojni/lib/HelloJniTest.java
new file mode 100644
index 0000000..feadc72
--- /dev/null
+++ b/build-system/tests/ndkJniLib/app/src/instrumentTest/java/com/example/hellojni/lib/HelloJniTest.java
@@ -0,0 +1,29 @@
+package com.example.hellojni.lib;
+
+import android.test.ActivityInstrumentationTestCase;
+
+/**
+ * This is a simple framework for a test of an Application. See
+ * {@link android.test.ApplicationTestCase ApplicationTestCase} for more information on
+ * how to write and extend Application tests.
+ * <p/>
+ * To run this test, you can type:
+ * adb shell am instrument -w \
+ * -e class com.example.hellojni.HelloJniTest \
+ * com.example.hellojni.tests/android.test.InstrumentationTestRunner
+ */
+public class HelloJniTest extends ActivityInstrumentationTestCase<HelloJni> {
+
+ public HelloJniTest() {
+ super("com.example.hellojni", HelloJni.class);
+ }
+
+
+ public void testJniName() {
+ final HelloJni a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ assertFalse("unknown".equals(a.jniNameFromJNI()));
+ }
+}
diff --git a/build-system/tests/ndkJniLib/app/src/main/AndroidManifest.xml b/build-system/tests/ndkJniLib/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..aae7f79
--- /dev/null
+++ b/build-system/tests/ndkJniLib/app/src/main/AndroidManifest.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.hellojni.app"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <uses-sdk android:minSdkVersion="3" />
+
+ <application android:label="@string/app_name">
+ </application>
+</manifest>
diff --git a/build-system/tests/ndkJniLib/app/src/main/res/values/strings.xml b/build-system/tests/ndkJniLib/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..c526073
--- /dev/null
+++ b/build-system/tests/ndkJniLib/app/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">HelloJni</string>
+</resources>
diff --git a/build-system/tests/ndkJniLib/build.gradle b/build-system/tests/ndkJniLib/build.gradle
new file mode 100644
index 0000000..83b3e0b
--- /dev/null
+++ b/build-system/tests/ndkJniLib/build.gradle
@@ -0,0 +1,8 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
diff --git a/build-system/tests/ndkJniLib/lib/build.gradle b/build-system/tests/ndkJniLib/lib/build.gradle
new file mode 100644
index 0000000..5d18344
--- /dev/null
+++ b/build-system/tests/ndkJniLib/lib/build.gradle
@@ -0,0 +1,12 @@
+apply plugin: 'android-library'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+ defaultConfig {
+ ndk {
+ moduleName "hello-jni"
+ }
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/ndkJniLib/lib/src/instrumentTest/java/com/example/hellojni/lib/HelloJniTest.java b/build-system/tests/ndkJniLib/lib/src/instrumentTest/java/com/example/hellojni/lib/HelloJniTest.java
new file mode 100644
index 0000000..feadc72
--- /dev/null
+++ b/build-system/tests/ndkJniLib/lib/src/instrumentTest/java/com/example/hellojni/lib/HelloJniTest.java
@@ -0,0 +1,29 @@
+package com.example.hellojni.lib;
+
+import android.test.ActivityInstrumentationTestCase;
+
+/**
+ * This is a simple framework for a test of an Application. See
+ * {@link android.test.ApplicationTestCase ApplicationTestCase} for more information on
+ * how to write and extend Application tests.
+ * <p/>
+ * To run this test, you can type:
+ * adb shell am instrument -w \
+ * -e class com.example.hellojni.HelloJniTest \
+ * com.example.hellojni.tests/android.test.InstrumentationTestRunner
+ */
+public class HelloJniTest extends ActivityInstrumentationTestCase<HelloJni> {
+
+ public HelloJniTest() {
+ super("com.example.hellojni", HelloJni.class);
+ }
+
+
+ public void testJniName() {
+ final HelloJni a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ assertFalse("unknown".equals(a.jniNameFromJNI()));
+ }
+}
diff --git a/build-system/tests/ndkJniLib/lib/src/main/AndroidManifest.xml b/build-system/tests/ndkJniLib/lib/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..ff4f566
--- /dev/null
+++ b/build-system/tests/ndkJniLib/lib/src/main/AndroidManifest.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.hellojni.lib">
+
+ <uses-sdk android:minSdkVersion="3" />
+ <application>
+ <activity android:name=".HelloJni"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/build-system/tests/ndkJniLib/lib/src/main/java/com/example/hellojni/lib/HelloJni.java b/build-system/tests/ndkJniLib/lib/src/main/java/com/example/hellojni/lib/HelloJni.java
new file mode 100644
index 0000000..c97a0eb
--- /dev/null
+++ b/build-system/tests/ndkJniLib/lib/src/main/java/com/example/hellojni/lib/HelloJni.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.hellojni.lib;
+
+import android.app.Activity;
+import android.widget.TextView;
+import android.os.Bundle;
+
+
+public class HelloJni extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ /* Create a TextView and set its content.
+ * the text is retrieved by calling a native
+ * function.
+ */
+ TextView tv = new TextView(this);
+ tv.setText( stringFromJNI() );
+ setContentView(tv);
+ }
+
+ /* A native method that is implemented by the
+ * 'hello-jni' native library, which is packaged
+ * with this application.
+ */
+ public native String stringFromJNI();
+
+ public native String jniNameFromJNI();
+
+ /* this is used to load the 'hello-jni' library on application
+ * startup. The library has already been unpacked into
+ * /data/data/com.example.hellojni/lib/libhello-jni.so at
+ * installation time by the package manager.
+ */
+ static {
+ System.loadLibrary("hello-jni");
+ }
+}
diff --git a/build-system/tests/ndkJniLib/lib/src/main/jni/hello-jni.c b/build-system/tests/ndkJniLib/lib/src/main/jni/hello-jni.c
new file mode 100644
index 0000000..4ee252f
--- /dev/null
+++ b/build-system/tests/ndkJniLib/lib/src/main/jni/hello-jni.c
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+#include <string.h>
+#include <jni.h>
+
+/* This is a trivial JNI example where we use a native method
+ * to return a new VM String. See the corresponding Java source
+ * file located at:
+ *
+ * apps/samples/hello-jni/project/src/com/example/hellojni/HelloJni.java
+ */
+jstring
+Java_com_example_hellojni_lib_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz)
+{
+#if defined(__arm__)
+ #if defined(__ARM_ARCH_7A__)
+ #if defined(__ARM_NEON__)
+ #define ABI "armeabi-v7a with NEON"
+ #else
+ #define ABI "armeabi-v7a"
+ #endif
+ #else
+ #define ABI "armeabi"
+ #endif
+#elif defined(__i386__)
+ #define ABI "x86"
+#elif defined(__mips__)
+ #define ABI "mips"
+#else
+ #define ABI "unknown"
+#endif
+
+ return (*env)->NewStringUTF(env, "Hello from JNI ! My ABI is " ABI ".");
+}
+
+jstring
+Java_com_example_hellojni_lib_HelloJni_jniNameFromJNI(JNIEnv* env, jobject thiz)
+{
+#if defined(__arm__)
+ #if defined(__ARM_ARCH_7A__)
+ #if defined(__ARM_NEON__)
+ #define ABI "armeabi-v7a with NEON"
+ #else
+ #define ABI "armeabi-v7a"
+ #endif
+ #else
+ #define ABI "armeabi"
+ #endif
+#elif defined(__i386__)
+ #define ABI "x86"
+#elif defined(__mips__)
+ #define ABI "mips"
+#else
+ #define ABI "unknown"
+#endif
+
+ return (*env)->NewStringUTF(env, ABI);
+}
diff --git a/build-system/tests/ndkJniLib/lib/src/main/res/values/strings.xml b/build-system/tests/ndkJniLib/lib/src/main/res/values/strings.xml
new file mode 100644
index 0000000..c526073
--- /dev/null
+++ b/build-system/tests/ndkJniLib/lib/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">HelloJni</string>
+</resources>
diff --git a/build-system/tests/ndkJniLib/settings.gradle b/build-system/tests/ndkJniLib/settings.gradle
new file mode 100644
index 0000000..7f37a58
--- /dev/null
+++ b/build-system/tests/ndkJniLib/settings.gradle
@@ -0,0 +1 @@
+include 'app', 'lib'
\ No newline at end of file
diff --git a/build-system/tests/ndkRsHelloCompute/Android.mk.old b/build-system/tests/ndkRsHelloCompute/Android.mk.old
new file mode 100644
index 0000000..5dbe19f
--- /dev/null
+++ b/build-system/tests/ndkRsHelloCompute/Android.mk.old
@@ -0,0 +1,31 @@
+#
+# Copyright (C) 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+ $(call all-renderscript-files-under, src)
+
+LOCAL_PACKAGE_NAME := HelloComputeNDK
+LOCAL_SDK_VERSION := 14
+
+LOCAL_JNI_SHARED_LIBRARIES := libhellocomputendk
+
+include $(BUILD_PACKAGE)
+include $(LOCAL_PATH)/libhellocomputendk/Android.mk
\ No newline at end of file
diff --git a/build-system/tests/ndkRsHelloCompute/build.gradle b/build-system/tests/ndkRsHelloCompute/build.gradle
new file mode 100644
index 0000000..4c0f284
--- /dev/null
+++ b/build-system/tests/ndkRsHelloCompute/build.gradle
@@ -0,0 +1,43 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+apply plugin: 'android'
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion "19.0.1"
+
+ defaultConfig {
+ renderscriptNdkMode true
+ ndk {
+ moduleName "libhellocomputendk"
+ stl "stlport_shared"
+ }
+
+ }
+
+ buildTypes.debug.jniDebugBuild true
+
+ productFlavors {
+ x86 {
+ ndk {
+ abiFilter "x86"
+ }
+ }
+ arm {
+ ndk {
+ abiFilter "armeabi-v7a"
+ }
+ }
+ mips {
+ ndk {
+ abiFilter "mips"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/ndkRsHelloCompute/libhellocomputendk/Android.mk.old b/build-system/tests/ndkRsHelloCompute/libhellocomputendk/Android.mk.old
new file mode 100644
index 0000000..1c0e861
--- /dev/null
+++ b/build-system/tests/ndkRsHelloCompute/libhellocomputendk/Android.mk.old
@@ -0,0 +1,33 @@
+# Copyright (C) 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# This is the shared library included by the JNI test app.
+#
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+LOCAL_CLANG := true
+
+LOCAL_MODULE := libhellocomputendk
+LOCAL_MODULE_TAGS := optional
+LOCAL_SRC_FILES := helloComputeNDK.cpp mono.rs
+
+LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
+LOCAL_C_INCLUDES += frameworks/rs/cpp
+LOCAL_C_INCLUDES += frameworks/rs
+LOCAL_C_INCLUDES += external/stlport/stlport bionic/ bionic/libstdc++/include
+
+LOCAL_SHARED_LIBRARIES := libdl liblog libjnigraphics
+LOCAL_STATIC_LIBRARIES := libRScpp_static libstlport_static libcutils
+include $(BUILD_SHARED_LIBRARY)
diff --git a/build-system/tests/ndkRsHelloCompute/src/main/AndroidManifest.xml b/build-system/tests/ndkRsHelloCompute/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..4db45e7
--- /dev/null
+++ b/build-system/tests/ndkRsHelloCompute/src/main/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.rs.hellocomputendk">
+
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-sdk android:minSdkVersion="14" />
+ <application android:label="HelloComputeNDK">
+ <activity android:name="HelloComputeNDK">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/build-system/tests/ndkRsHelloCompute/src/main/java/com/example/android/rs/hellocomputendk/HelloComputeNDK.java b/build-system/tests/ndkRsHelloCompute/src/main/java/com/example/android/rs/hellocomputendk/HelloComputeNDK.java
new file mode 100644
index 0000000..aec6497
--- /dev/null
+++ b/build-system/tests/ndkRsHelloCompute/src/main/java/com/example/android/rs/hellocomputendk/HelloComputeNDK.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.rs.hellocomputendk;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.graphics.BitmapFactory;
+import android.graphics.Bitmap;
+import android.widget.ImageView;
+
+public class HelloComputeNDK extends Activity {
+ private Bitmap mBitmapIn;
+ private Bitmap mBitmapOut;
+
+ static {
+ System.loadLibrary("hellocomputendk");
+ }
+
+ native void nativeMono(int X, int Y, Bitmap in, Bitmap out);
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ mBitmapIn = loadBitmap(R.drawable.data);
+ mBitmapOut = Bitmap.createBitmap(mBitmapIn.getWidth(), mBitmapIn.getHeight(),
+ mBitmapIn.getConfig());
+
+ ImageView in = (ImageView) findViewById(R.id.displayin);
+ in.setImageBitmap(mBitmapIn);
+
+ ImageView out = (ImageView) findViewById(R.id.displayout);
+ out.setImageBitmap(mBitmapOut);
+
+ nativeMono(mBitmapIn.getWidth(), mBitmapIn.getHeight(), mBitmapIn, mBitmapOut);
+
+ }
+
+ private Bitmap loadBitmap(int resource) {
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+ return BitmapFactory.decodeResource(getResources(), resource, options);
+ }
+}
diff --git a/build-system/tests/ndkRsHelloCompute/src/main/jni/helloComputeNDK.cpp b/build-system/tests/ndkRsHelloCompute/src/main/jni/helloComputeNDK.cpp
new file mode 100644
index 0000000..6ed5589
--- /dev/null
+++ b/build-system/tests/ndkRsHelloCompute/src/main/jni/helloComputeNDK.cpp
@@ -0,0 +1,59 @@
+#include <jni.h>
+#include <android/log.h>
+#include <android/bitmap.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include <RenderScript.h>
+
+#include "ScriptC_mono.h"
+
+#define LOG_TAG "HelloComputeNDK"
+#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
+#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
+
+using namespace android::RSC;
+
+extern "C" JNIEXPORT void JNICALL
+Java_com_example_android_rs_hellocomputendk_HelloComputeNDK_nativeMono(JNIEnv * env,
+ jclass,
+ jint X,
+ jint Y,
+ jobject jbitmapIn,
+ jobject jbitmapOut
+ )
+{
+
+ void* inputPtr = NULL;
+ void* outputPtr = NULL;
+
+ AndroidBitmap_lockPixels(env, jbitmapIn, &inputPtr);
+ AndroidBitmap_lockPixels(env, jbitmapOut, &outputPtr);
+
+ sp<RS> rs = new RS();
+ rs->init();
+
+ sp<const Element> e = Element::RGBA_8888(rs);
+
+ sp<const Type> t = Type::create(rs, e, X, Y, 0);
+
+ sp<Allocation> inputAlloc = Allocation::createTyped(rs, t, RS_ALLOCATION_MIPMAP_NONE,
+ RS_ALLOCATION_USAGE_SHARED | RS_ALLOCATION_USAGE_SCRIPT,
+ inputPtr);
+ sp<Allocation> outputAlloc = Allocation::createTyped(rs, t, RS_ALLOCATION_MIPMAP_NONE,
+ RS_ALLOCATION_USAGE_SHARED | RS_ALLOCATION_USAGE_SCRIPT,
+ outputPtr);
+
+
+ inputAlloc->copy2DRangeFrom(0, 0, X, Y, inputPtr);
+ ScriptC_mono* sc = new ScriptC_mono(rs);
+ sc->forEach_root(inputAlloc, outputAlloc);
+ outputAlloc->copy2DRangeTo(0, 0, X, Y, outputPtr);
+
+
+ AndroidBitmap_unlockPixels(env, jbitmapIn);
+ AndroidBitmap_unlockPixels(env, jbitmapOut);
+
+}
diff --git a/build-system/tests/ndkRsHelloCompute/src/main/res/drawable/data.jpg b/build-system/tests/ndkRsHelloCompute/src/main/res/drawable/data.jpg
new file mode 100644
index 0000000..81a87b1
--- /dev/null
+++ b/build-system/tests/ndkRsHelloCompute/src/main/res/drawable/data.jpg
Binary files differ
diff --git a/build-system/tests/ndkRsHelloCompute/src/main/res/layout/main.xml b/build-system/tests/ndkRsHelloCompute/src/main/res/layout/main.xml
new file mode 100644
index 0000000..7b2c76a
--- /dev/null
+++ b/build-system/tests/ndkRsHelloCompute/src/main/res/layout/main.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ImageView
+ android:id="@+id/displayin"
+ android:layout_width="320dip"
+ android:layout_height="266dip" />
+
+ <ImageView
+ android:id="@+id/displayout"
+ android:layout_width="320dip"
+ android:layout_height="266dip" />
+
+</LinearLayout>
diff --git a/build-system/tests/ndkRsHelloCompute/src/main/rs/mono.rs b/build-system/tests/ndkRsHelloCompute/src/main/rs/mono.rs
new file mode 100644
index 0000000..efa5c72
--- /dev/null
+++ b/build-system/tests/ndkRsHelloCompute/src/main/rs/mono.rs
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma version(1)
+#pragma rs java_package_name(com.example.android.rs.hellocomputendk)
+
+const static float3 gMonoMult = {0.299f, 0.587f, 0.114f};
+
+void root(const uchar4 *v_in, uchar4 *v_out) {
+ float4 f4 = rsUnpackColor8888(*v_in);
+
+ float3 mono = dot(f4.rgb, gMonoMult);
+ *v_out = rsPackColorTo8888(mono);
+}
+
diff --git a/build-system/tests/ndkSanAngeles/README.txt b/build-system/tests/ndkSanAngeles/README.txt
new file mode 100644
index 0000000..38b8a4a
--- /dev/null
+++ b/build-system/tests/ndkSanAngeles/README.txt
@@ -0,0 +1,77 @@
+------------------------------------------------------------------------
+San Angeles Observation OpenGL ES version example
+Copyright 2004-2005 Jetro Lauha
+Web: http://iki.fi/jetro/
+See file license.txt for licensing information.
+------------------------------------------------------------------------
+
+This is an OpenGL ES port of the small self-running demonstration
+called "San Angeles Observation", which was first presented in the
+Assembly'2004 event. It won the first place in the 4 KB intro
+competition category.
+
+The demonstration features a sightseeing of a futuristic city
+having many different kind of buildings and items. Everything is
+flat shaded with three different lights.
+
+The original version was made for desktop with OpenGL. It was
+naturally heavily size optimized in order to fit it in the size
+limit. For this OpenGL ES version example much of the code is
+cleaned up and the sound is removed. Also detail level is lowered,
+although it still contains over 60000 faces.
+
+The Win32 (2000/XP) binary package of original version is
+available from this address: http://jet.ro/files/angeles.zip
+
+First version of this OpenGL ES port was submitted to the Khronos
+OpenGL ES Coding Challenge held in 2004-2005.
+
+As a code example, this source shows the following:
+ * How to create a minimal and portable ad hoc framework
+ for small testing/demonstration programs. This framework
+ compiles for both desktop and PocketPC Win32 environment,
+ and a separate source is included for Linux with X11.
+ * How to dynamically find and use the OpenGL ES DLL or
+ shared object, so that the library is not needed at
+ the compile/link stage.
+ * How to use the basic features of OpenGL ES 1.0/1.1
+ Common Lite, such as vertex arrays, color arrays and
+ lighting.
+ * How to create a self contained small demonstration
+ application with objects generated using procedural
+ algorithms.
+
+As the original version was optimized for size instead of
+performance, that holds true for this OpenGL ES version as
+well. Thus the performance could be significantly increased,
+for example by changing the code to use glDrawElements
+instead of glDrawArrays. The code uses only OpenGL ES 1.0
+Common Lite -level function calls without any extensions.
+
+The reference OpenGL ES implementations used for this application:
+ * Hybrid's OpenGL ES API Implementation (Gerbera) version 2.0.4
+ Prebuilt Win32 PC executable: SanOGLES-Gerbera.exe
+ * PowerVR MBX SDK, OpenGL ES Windows PC Emulation version 1.04.14.0170
+ Prebuilt Win32 PC executable: SanOGLES-PVRSDK.exe
+
+Note that DISABLE_IMPORTGL preprocessor macro can be used
+to specify not to use dynamic runtime binding of the library.
+You also need to define preprocessor macro PVRSDK to compile
+the source with PowerVR OpenGL ES SDK.
+
+The demo application is briefly tested with a few other OpenGL ES
+implementations as well (e.g. Vincent, GLESonGL on Linux, Dell
+Axim X50v). Most of these other implementations rendered the demo
+erroneously in some aspect. This may indicate that the demo source
+could still have some work to do with compatibility and correct
+API usage, although the non-conforming implementations are most
+probably unfinished as well.
+
+Thanks and Acknowledgements:
+
+* Toni Lönnberg (!Cube) created the music for original version, which
+ is not featured in this OpenGL ES port.
+* Sara Kapli (st Rana) for additional camera work.
+* Paul Bourke for information about the supershapes.
+
+------------------------------------------------------------------------
diff --git a/build-system/tests/ndkSanAngeles/build.gradle b/build-system/tests/ndkSanAngeles/build.gradle
new file mode 100644
index 0000000..a0bd5d4
--- /dev/null
+++ b/build-system/tests/ndkSanAngeles/build.gradle
@@ -0,0 +1,43 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+apply plugin: 'android'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+ defaultConfig {
+ ndk {
+ moduleName "sanangeles"
+ cFlags "-DANDROID_NDK -DDISABLE_IMPORTGL"
+ ldLibs "GLESv1_CM", "dl", "log"
+ stl "stlport_static"
+ }
+ }
+
+ buildTypes.debug.jniDebugBuild true
+
+ productFlavors {
+ x86 {
+ ndk {
+ abiFilter "x86"
+ }
+ }
+ arm {
+ ndk {
+ abiFilter "armeabi-v7a"
+ }
+ }
+ mips {
+ ndk {
+ abiFilter "mips"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/ndkSanAngeles/license-BSD.txt b/build-system/tests/ndkSanAngeles/license-BSD.txt
new file mode 100644
index 0000000..8924e3c
--- /dev/null
+++ b/build-system/tests/ndkSanAngeles/license-BSD.txt
@@ -0,0 +1,34 @@
+This is the BSD-style license for the "San Angeles Observation"
+OpenGL ES version example source code
+---------------------------------------------------------------
+
+San Angeles Observation OpenGL ES version example
+Copyright (c) 2004-2005, Jetro Lauha
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name of the software product's copyright owner nor
+ the names of its contributors may be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/build-system/tests/ndkSanAngeles/license-LGPL.txt b/build-system/tests/ndkSanAngeles/license-LGPL.txt
new file mode 100644
index 0000000..b1e3f5a
--- /dev/null
+++ b/build-system/tests/ndkSanAngeles/license-LGPL.txt
@@ -0,0 +1,504 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/build-system/tests/ndkSanAngeles/license.txt b/build-system/tests/ndkSanAngeles/license.txt
new file mode 100644
index 0000000..620841e
--- /dev/null
+++ b/build-system/tests/ndkSanAngeles/license.txt
@@ -0,0 +1,19 @@
+San Angeles Observation OpenGL ES version example
+Copyright 2004-2005 Jetro Lauha
+All rights reserved.
+Web: http://iki.fi/jetro/
+
+This source is free software; you can redistribute it and/or
+modify it under the terms of EITHER:
+ (1) The GNU Lesser General Public License as published by the Free
+ Software Foundation; either version 2.1 of the License, or (at
+ your option) any later version. The text of the GNU Lesser
+ General Public License is included with this source in the
+ file LICENSE-LGPL.txt.
+ (2) The BSD-style license that is included with this source in
+ the file LICENSE-BSD.txt.
+
+This source is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the files
+LICENSE-LGPL.txt and LICENSE-BSD.txt for more details.
diff --git a/build-system/tests/ndkSanAngeles/misc/app-linux.c b/build-system/tests/ndkSanAngeles/misc/app-linux.c
new file mode 100644
index 0000000..6b573f2
--- /dev/null
+++ b/build-system/tests/ndkSanAngeles/misc/app-linux.c
@@ -0,0 +1,247 @@
+/* San Angeles Observation OpenGL ES version example
+ * Copyright 2004-2005 Jetro Lauha
+ * All rights reserved.
+ * Web: http://iki.fi/jetro/
+ *
+ * This source is free software; you can redistribute it and/or
+ * modify it under the terms of EITHER:
+ * (1) The GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at
+ * your option) any later version. The text of the GNU Lesser
+ * General Public License is included with this source in the
+ * file LICENSE-LGPL.txt.
+ * (2) The BSD-style license that is included with this source in
+ * the file LICENSE-BSD.txt.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the files
+ * LICENSE-LGPL.txt and LICENSE-BSD.txt for more details.
+ *
+ * $Id: app-linux.c,v 1.4 2005/02/08 18:42:48 tonic Exp $
+ * $Revision: 1.4 $
+ *
+ * Parts of this source file is based on test/example code from
+ * GLESonGL implementation by David Blythe. Here is copy of the
+ * license notice from that source:
+ *
+ * Copyright (C) 2003 David Blythe All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * DAVID BLYTHE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/time.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/keysym.h>
+
+#include "importgl.h"
+
+#include "app.h"
+
+
+int gAppAlive = 1;
+
+static const char sAppName[] =
+ "San Angeles Observation OpenGL ES version example (Linux)";
+static Display *sDisplay;
+static Window sWindow;
+static int sWindowWidth = WINDOW_DEFAULT_WIDTH;
+static int sWindowHeight = WINDOW_DEFAULT_HEIGHT;
+static EGLDisplay sEglDisplay = EGL_NO_DISPLAY;
+static EGLConfig sEglConfig;
+static EGLContext sEglContext = EGL_NO_CONTEXT;
+static EGLSurface sEglSurface = EGL_NO_SURFACE;
+
+
+static void checkGLErrors()
+{
+ GLenum error = glGetError();
+ if (error != GL_NO_ERROR)
+ fprintf(stderr, "GL Error: 0x%04x\n", (int)error);
+}
+
+
+static void checkEGLErrors()
+{
+ EGLint error = eglGetError();
+ // GLESonGL seems to be returning 0 when there is no errors?
+ if (error && error != EGL_SUCCESS)
+ fprintf(stderr, "EGL Error: 0x%04x\n", (int)error);
+}
+
+
+// Initializes and opens both X11 display and OpenGL ES.
+static int initGraphics()
+{
+ static const EGLint configAttribs[] =
+ {
+#if (WINDOW_BPP == 16)
+ EGL_RED_SIZE, 5,
+ EGL_GREEN_SIZE, 5,
+ EGL_BLUE_SIZE, 5,
+#elif (WINDOW_BPP == 32)
+ EGL_RED_SIZE, 8,
+ EGL_GREEN_SIZE, 8,
+ EGL_BLUE_SIZE, 8,
+#else
+#error WINDOW_BPP must be 16 or 32
+#endif
+ EGL_DEPTH_SIZE, 16,
+ EGL_ALPHA_SIZE, EGL_DONT_CARE,
+ EGL_STENCIL_SIZE, EGL_DONT_CARE,
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+ EGL_NONE
+ };
+ EGLBoolean success;
+ EGLint numConfigs;
+ EGLint majorVersion;
+ EGLint minorVersion;
+
+ int importGLResult;
+ importGLResult = importGLInit();
+ if (!importGLResult)
+ return 0;
+
+ sDisplay = XOpenDisplay(NULL);
+
+ sEglDisplay = eglGetDisplay(sDisplay);
+ success = eglInitialize(sEglDisplay, &majorVersion, &minorVersion);
+ if (success != EGL_FALSE)
+ success = eglGetConfigs(sEglDisplay, NULL, 0, &numConfigs);
+ if (success != EGL_FALSE)
+ success = eglChooseConfig(sEglDisplay, configAttribs,
+ &sEglConfig, 1, &numConfigs);
+ if (success != EGL_FALSE)
+ {
+ sEglContext = eglCreateContext(sEglDisplay, sEglConfig, NULL, NULL);
+ if (sEglContext == EGL_NO_CONTEXT)
+ success = EGL_FALSE;
+ }
+ if (success != EGL_FALSE)
+ {
+ XSetWindowAttributes swa;
+ XVisualInfo *vi, tmp;
+ XSizeHints sh;
+ int n;
+ EGLint vid;
+
+ eglGetConfigAttrib(sEglDisplay, sEglConfig,
+ EGL_NATIVE_VISUAL_ID, &vid);
+ tmp.visualid = vid;
+ vi = XGetVisualInfo(sDisplay, VisualIDMask, &tmp, &n);
+ swa.colormap = XCreateColormap(sDisplay,
+ RootWindow(sDisplay, vi->screen),
+ vi->visual, AllocNone);
+ sh.flags = PMinSize | PMaxSize;
+ sh.min_width = sh.max_width = sWindowWidth;
+ sh.min_height = sh.max_height = sWindowHeight;
+ swa.border_pixel = 0;
+ swa.event_mask = ExposureMask | StructureNotifyMask |
+ KeyPressMask | ButtonPressMask | ButtonReleaseMask;
+ sWindow = XCreateWindow(sDisplay, RootWindow(sDisplay, vi->screen),
+ 0, 0, sWindowWidth, sWindowHeight,
+ 0, vi->depth, InputOutput, vi->visual,
+ CWBorderPixel | CWColormap | CWEventMask,
+ &swa);
+ XMapWindow(sDisplay, sWindow);
+ XSetStandardProperties(sDisplay, sWindow, sAppName, sAppName,
+ None, (void *)0, 0, &sh);
+ }
+ if (success != EGL_FALSE)
+ {
+ sEglSurface = eglCreateWindowSurface(sEglDisplay, sEglConfig,
+ (NativeWindowType)sWindow, NULL);
+ if (sEglSurface == EGL_NO_SURFACE)
+ success = EGL_FALSE;
+ }
+ if (success != EGL_FALSE)
+ success = eglMakeCurrent(sEglDisplay, sEglSurface,
+ sEglSurface, sEglContext);
+
+ if (success == EGL_FALSE)
+ checkEGLErrors();
+
+ return success != EGL_FALSE;
+}
+
+
+static void deinitGraphics()
+{
+ eglMakeCurrent(sEglDisplay, NULL, NULL, NULL);
+ eglDestroyContext(sEglDisplay, sEglContext);
+ eglDestroySurface(sEglDisplay, sEglSurface);
+ eglTerminate(sEglDisplay);
+ importGLDeinit();
+}
+
+
+int main(int argc, char *argv[])
+{
+ // not referenced:
+ argc = argc;
+ argv = argv;
+
+ if (!initGraphics())
+ {
+ fprintf(stderr, "Graphics initialization failed.\n");
+ return EXIT_FAILURE;
+ }
+
+ appInit();
+
+ while (gAppAlive)
+ {
+ struct timeval timeNow;
+
+ while (XPending(sDisplay))
+ {
+ XEvent ev;
+ XNextEvent(sDisplay, &ev);
+ switch (ev.type)
+ {
+ case KeyPress:
+ {
+ unsigned int keycode, keysym;
+ keycode = ((XKeyEvent *)&ev)->keycode;
+ keysym = XKeycodeToKeysym(sDisplay, keycode, 0);
+ if (keysym == XK_Return || keysym == XK_Escape)
+ gAppAlive = 0;
+ }
+ break;
+ }
+ }
+
+ if (gAppAlive)
+ {
+ gettimeofday(&timeNow, NULL);
+ appRender(timeNow.tv_sec * 1000 + timeNow.tv_usec / 1000,
+ sWindowWidth, sWindowHeight);
+ checkGLErrors();
+ eglSwapBuffers(sEglDisplay, sEglSurface);
+ checkEGLErrors();
+ }
+ }
+
+ appDeinit();
+ deinitGraphics();
+
+ return EXIT_SUCCESS;
+}
diff --git a/build-system/tests/ndkSanAngeles/misc/app-win32.c b/build-system/tests/ndkSanAngeles/misc/app-win32.c
new file mode 100644
index 0000000..b47577e
--- /dev/null
+++ b/build-system/tests/ndkSanAngeles/misc/app-win32.c
@@ -0,0 +1,294 @@
+/* San Angeles Observation OpenGL ES version example
+ * Copyright 2004-2005 Jetro Lauha
+ * All rights reserved.
+ * Web: http://iki.fi/jetro/
+ *
+ * This source is free software; you can redistribute it and/or
+ * modify it under the terms of EITHER:
+ * (1) The GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at
+ * your option) any later version. The text of the GNU Lesser
+ * General Public License is included with this source in the
+ * file LICENSE-LGPL.txt.
+ * (2) The BSD-style license that is included with this source in
+ * the file LICENSE-BSD.txt.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the files
+ * LICENSE-LGPL.txt and LICENSE-BSD.txt for more details.
+ *
+ * $Id: app-win32.c,v 1.6 2005/02/24 20:29:00 tonic Exp $
+ * $Revision: 1.6 $
+ */
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <tchar.h>
+#ifdef UNDER_CE
+#include <aygshell.h>
+#endif
+
+#include <stdio.h>
+
+#include "importgl.h"
+
+#include "app.h"
+
+
+int gAppAlive = 1;
+
+static HINSTANCE sInstance;
+
+static const _TCHAR sAppName[] =
+ _T("San Angeles Observation OpenGL ES version example (Win32)");
+static HWND sWnd;
+static int sWindowWidth = WINDOW_DEFAULT_WIDTH;
+static int sWindowHeight = WINDOW_DEFAULT_HEIGHT;
+static EGLDisplay sEglDisplay = EGL_NO_DISPLAY;
+static EGLConfig sEglConfig;
+static EGLContext sEglContext = EGL_NO_CONTEXT;
+static EGLSurface sEglSurface = EGL_NO_SURFACE;
+
+
+static void checkGLErrors()
+{
+ GLenum error = glGetError();
+ if (error != GL_NO_ERROR)
+ {
+ _TCHAR errorString[32];
+ _stprintf(errorString, _T("0x%04x"), error);
+ MessageBox(NULL, errorString, _T("GL Error"), MB_OK);
+ }
+}
+
+
+static void checkEGLErrors()
+{
+ EGLint error = eglGetError();
+ if (error != EGL_SUCCESS)
+ {
+ _TCHAR errorString[32];
+ _stprintf(errorString, _T("0x%04x"), error);
+ MessageBox(NULL, errorString, _T("EGL Initialization Error"), MB_OK);
+ }
+}
+
+
+static BOOL initEGL(HWND wnd)
+{
+ static const EGLint configAttribs[] =
+ {
+#if (WINDOW_BPP == 16)
+ EGL_RED_SIZE, 5,
+ EGL_GREEN_SIZE, 5,
+ EGL_BLUE_SIZE, 5,
+#elif (WINDOW_BPP == 32)
+ EGL_RED_SIZE, 8,
+ EGL_GREEN_SIZE, 8,
+ EGL_BLUE_SIZE, 8,
+#else
+#error WINDOW_BPP must be 16 or 32
+#endif
+ EGL_DEPTH_SIZE, 16,
+ EGL_ALPHA_SIZE, EGL_DONT_CARE,
+ EGL_STENCIL_SIZE, EGL_DONT_CARE,
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+ EGL_NONE
+ };
+ EGLBoolean success;
+ EGLint numConfigs;
+ EGLint majorVersion;
+ EGLint minorVersion;
+#ifdef PVRSDK
+ HDC dc;
+#endif // PVRSDK
+
+#ifndef DISABLE_IMPORTGL
+ int importGLResult;
+ importGLResult = importGLInit();
+ if (!importGLResult)
+ return FALSE;
+#endif // !DISABLE_IMPORTGL
+
+#ifdef PVRSDK
+ dc = GetDC(sWnd);
+ sEglDisplay = eglGetDisplay(dc);
+#else
+ sEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+#endif // !PVRSDK
+ success = eglInitialize(sEglDisplay, &majorVersion, &minorVersion);
+ if (success != EGL_FALSE)
+ success = eglGetConfigs(sEglDisplay, NULL, 0, &numConfigs);
+ if (success != EGL_FALSE)
+ success = eglChooseConfig(sEglDisplay, configAttribs,
+ &sEglConfig, 1, &numConfigs);
+ if (success != EGL_FALSE)
+ {
+ sEglSurface = eglCreateWindowSurface(sEglDisplay, sEglConfig,
+ wnd, NULL);
+ if (sEglSurface == EGL_NO_SURFACE)
+ success = EGL_FALSE;
+ }
+ if (success != EGL_FALSE)
+ {
+ sEglContext = eglCreateContext(sEglDisplay, sEglConfig, NULL, NULL);
+ if (sEglContext == EGL_NO_CONTEXT)
+ success = EGL_FALSE;
+ }
+ if (success != EGL_FALSE)
+ success = eglMakeCurrent(sEglDisplay, sEglSurface,
+ sEglSurface, sEglContext);
+
+ if (success == EGL_FALSE)
+ checkEGLErrors();
+
+ return success;
+}
+
+
+static void deinitEGL()
+{
+ eglMakeCurrent(sEglDisplay, NULL, NULL, NULL);
+ eglDestroyContext(sEglDisplay, sEglContext);
+ eglDestroySurface(sEglDisplay, sEglSurface);
+ eglTerminate(sEglDisplay);
+#ifndef DISABLE_IMPORTGL
+ importGLDeinit();
+#endif // !DISABLE_IMPORTGL
+}
+
+
+static LRESULT CALLBACK wndProc(HWND wnd, UINT message,
+ WPARAM wParam, LPARAM lParam)
+{
+ RECT rc;
+ int useDefWindowProc = 0;
+
+ switch (message)
+ {
+ case WM_CLOSE:
+ DestroyWindow(wnd);
+ gAppAlive = 0;
+ break;
+
+ case WM_DESTROY:
+ PostQuitMessage(0);
+ gAppAlive = 0;
+ break;
+
+ case WM_KEYDOWN:
+ if (wParam == VK_ESCAPE || wParam == VK_RETURN)
+ gAppAlive = 0;
+ useDefWindowProc = 1;
+ break;
+
+ case WM_KEYUP:
+ useDefWindowProc = 1;
+ break;
+
+ case WM_SIZE:
+ GetClientRect(sWnd, &rc);
+ sWindowWidth = rc.right;
+ sWindowHeight = rc.bottom;
+ break;
+
+ default:
+ useDefWindowProc = 1;
+ }
+
+ if (useDefWindowProc)
+ return DefWindowProc(wnd, message, wParam, lParam);
+ return 0;
+}
+
+
+int WINAPI WinMain(HINSTANCE instance, HINSTANCE prevInstance,
+ LPTSTR cmdLine, int cmdShow)
+{
+ MSG msg;
+ WNDCLASS wc;
+ DWORD windowStyle;
+ int windowX, windowY;
+
+ // not referenced:
+ prevInstance = prevInstance;
+ cmdLine = cmdLine;
+
+
+ sInstance = instance;
+
+ // register class
+ wc.style = CS_HREDRAW | CS_VREDRAW;
+ wc.lpfnWndProc = (WNDPROC)wndProc;
+ wc.cbClsExtra = 0;
+ wc.cbWndExtra = 0;
+ wc.hInstance = sInstance;
+ wc.hIcon = NULL;
+ wc.hCursor = 0;
+ wc.hbrBackground = GetStockObject(BLACK_BRUSH);
+ wc.lpszMenuName = NULL;
+ wc.lpszClassName = sAppName;
+ if (!RegisterClass(&wc))
+ return FALSE;
+
+ // init instance
+ windowStyle = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_VISIBLE;
+#ifdef UNDER_CE
+ sWindowWidth = GetSystemMetrics(SM_CXSCREEN);
+ sWindowHeight = GetSystemMetrics(SM_CYSCREEN);
+ windowX = windowY = 0;
+#else
+ windowStyle |= WS_OVERLAPPEDWINDOW;
+ windowX = CW_USEDEFAULT;
+ windowY = 0;
+#endif
+ sWnd = CreateWindow(sAppName, sAppName, windowStyle,
+ windowX, windowY,
+ sWindowWidth, sWindowHeight,
+ NULL, NULL, instance, NULL);
+ if (!sWnd)
+ return FALSE;
+
+ ShowWindow(sWnd, cmdShow);
+
+#ifdef UNDER_CE
+ SHFullScreen(sWnd,
+ SHFS_HIDETASKBAR | SHFS_HIDESIPBUTTON | SHFS_HIDESTARTICON);
+ MoveWindow(sWnd, 0, 0, sWindowWidth, sWindowHeight, TRUE);
+#endif
+
+ UpdateWindow(sWnd);
+
+ if (!initEGL(sWnd))
+ return FALSE;
+
+ appInit(sWindowWidth, sWindowHeight);
+
+ while (gAppAlive)
+ {
+ while (PeekMessage(&msg, sWnd, 0, 0, PM_NOREMOVE))
+ {
+ if (GetMessage(&msg, sWnd, 0, 0))
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ else
+ gAppAlive = 0;
+ }
+
+ if (gAppAlive)
+ {
+ appRender(GetTickCount(), sWindowWidth, sWindowHeight);
+ checkGLErrors();
+ eglSwapBuffers(sEglDisplay, sEglSurface);
+ checkEGLErrors();
+ }
+ }
+
+ appDeinit();
+ deinitEGL();
+
+ return 0;
+}
diff --git a/build-system/tests/ndkSanAngeles/src/main/AndroidManifest.xml b/build-system/tests/ndkSanAngeles/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..5ae6a8e
--- /dev/null
+++ b/build-system/tests/ndkSanAngeles/src/main/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.SanAngeles"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <application android:label="@string/app_name">
+ <activity android:name=".DemoActivity"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+ <uses-sdk android:minSdkVersion="4" />
+</manifest>
diff --git a/build-system/tests/ndkSanAngeles/src/main/java/com/example/SanAngeles/DemoActivity.java b/build-system/tests/ndkSanAngeles/src/main/java/com/example/SanAngeles/DemoActivity.java
new file mode 100644
index 0000000..076b8a7
--- /dev/null
+++ b/build-system/tests/ndkSanAngeles/src/main/java/com/example/SanAngeles/DemoActivity.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This is a small port of the "San Angeles Observation" demo
+ * program for OpenGL ES 1.x. For more details, see:
+ *
+ * http://jet.ro/visuals/san-angeles-observation/
+ *
+ * This program demonstrates how to use a GLSurfaceView from Java
+ * along with native OpenGL calls to perform frame rendering.
+ *
+ * Touching the screen will start/stop the animation.
+ *
+ * Note that the demo runs much faster on the emulator than on
+ * real devices, this is mainly due to the following facts:
+ *
+ * - the demo sends bazillions of polygons to OpenGL without
+ * even trying to do culling. Most of them are clearly out
+ * of view.
+ *
+ * - on a real device, the GPU bus is the real bottleneck
+ * that prevent the demo from getting acceptable performance.
+ *
+ * - the software OpenGL engine used in the emulator uses
+ * the system bus instead, and its code rocks :-)
+ *
+ * Fixing the program to send less polygons to the GPU is left
+ * as an exercise to the reader. As always, patches welcomed :-)
+ */
+package com.example.SanAngeles;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+import android.app.Activity;
+import android.content.Context;
+import android.opengl.GLSurfaceView;
+import android.os.Bundle;
+import android.view.MotionEvent;
+
+public class DemoActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mGLView = new DemoGLSurfaceView(this);
+ setContentView(mGLView);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mGLView.onPause();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mGLView.onResume();
+ }
+
+ private GLSurfaceView mGLView;
+
+ static {
+ System.loadLibrary("sanangeles");
+ }
+}
+
+class DemoGLSurfaceView extends GLSurfaceView {
+ public DemoGLSurfaceView(Context context) {
+ super(context);
+ mRenderer = new DemoRenderer();
+ setRenderer(mRenderer);
+ }
+
+ public boolean onTouchEvent(final MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ nativeTogglePauseResume();
+ }
+ return true;
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ nativePause();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ nativeResume();
+ }
+
+
+ DemoRenderer mRenderer;
+
+ private static native void nativePause();
+ private static native void nativeResume();
+ private static native void nativeTogglePauseResume();
+}
+
+class DemoRenderer implements GLSurfaceView.Renderer {
+ public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+ nativeInit();
+ }
+
+ public void onSurfaceChanged(GL10 gl, int w, int h) {
+ //gl.glViewport(0, 0, w, h);
+ nativeResize(w, h);
+ }
+
+ public void onDrawFrame(GL10 gl) {
+ nativeRender();
+ }
+
+ private static native void nativeInit();
+ private static native void nativeResize(int w, int h);
+ private static native void nativeRender();
+ private static native void nativeDone();
+}
diff --git a/build-system/tests/ndkSanAngeles/src/main/jni/app-android.c b/build-system/tests/ndkSanAngeles/src/main/jni/app-android.c
new file mode 100644
index 0000000..399d896
--- /dev/null
+++ b/build-system/tests/ndkSanAngeles/src/main/jni/app-android.c
@@ -0,0 +1,137 @@
+/* San Angeles Observation OpenGL ES version example
+ * Copyright 2009 The Android Open Source Project
+ * All rights reserved.
+ *
+ * This source is free software; you can redistribute it and/or
+ * modify it under the terms of EITHER:
+ * (1) The GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at
+ * your option) any later version. The text of the GNU Lesser
+ * General Public License is included with this source in the
+ * file LICENSE-LGPL.txt.
+ * (2) The BSD-style license that is included with this source in
+ * the file LICENSE-BSD.txt.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the files
+ * LICENSE-LGPL.txt and LICENSE-BSD.txt for more details.
+ */
+#include <jni.h>
+#include <sys/time.h>
+#include <time.h>
+#include <android/log.h>
+#include <stdint.h>
+#include "importgl.h"
+#include "app.h"
+
+int gAppAlive = 1;
+
+static int sWindowWidth = 320;
+static int sWindowHeight = 480;
+static int sDemoStopped = 0;
+static long sTimeOffset = 0;
+static int sTimeOffsetInit = 0;
+static long sTimeStopped = 0;
+
+static long
+_getTime(void)
+{
+ struct timeval now;
+
+ gettimeofday(&now, NULL);
+ return (long)(now.tv_sec*1000 + now.tv_usec/1000);
+}
+
+/* Call to initialize the graphics state */
+void
+Java_com_example_SanAngeles_DemoRenderer_nativeInit( JNIEnv* env )
+{
+ importGLInit();
+ appInit();
+ gAppAlive = 1;
+}
+
+void
+Java_com_example_SanAngeles_DemoRenderer_nativeResize( JNIEnv* env, jobject thiz, jint w, jint h )
+{
+ sWindowWidth = w;
+ sWindowHeight = h;
+ __android_log_print(ANDROID_LOG_INFO, "SanAngeles", "resize w=%d h=%d", w, h);
+}
+
+/* Call to finalize the graphics state */
+void
+Java_com_example_SanAngeles_DemoRenderer_nativeDone( JNIEnv* env )
+{
+ appDeinit();
+ importGLDeinit();
+}
+
+/* This is called to indicate to the render loop that it should
+ * stop as soon as possible.
+ */
+
+void _pause()
+{
+ /* we paused the animation, so store the current
+ * time in sTimeStopped for future nativeRender calls */
+ sDemoStopped = 1;
+ sTimeStopped = _getTime();
+}
+
+void _resume()
+{
+ /* we resumed the animation, so adjust the time offset
+ * to take care of the pause interval. */
+ sDemoStopped = 0;
+ sTimeOffset -= _getTime() - sTimeStopped;
+}
+
+
+void
+Java_com_example_SanAngeles_DemoGLSurfaceView_nativeTogglePauseResume( JNIEnv* env )
+{
+ sDemoStopped = !sDemoStopped;
+ if (sDemoStopped)
+ _pause();
+ else
+ _resume();
+}
+
+void
+Java_com_example_SanAngeles_DemoGLSurfaceView_nativePause( JNIEnv* env )
+{
+ _pause();
+}
+
+void
+Java_com_example_SanAngeles_DemoGLSurfaceView_nativeResume( JNIEnv* env )
+{
+ _resume();
+}
+
+/* Call to render the next GL frame */
+void
+Java_com_example_SanAngeles_DemoRenderer_nativeRender( JNIEnv* env )
+{
+ long curTime;
+
+ /* NOTE: if sDemoStopped is TRUE, then we re-render the same frame
+ * on each iteration.
+ */
+ if (sDemoStopped) {
+ curTime = sTimeStopped + sTimeOffset;
+ } else {
+ curTime = _getTime() + sTimeOffset;
+ if (sTimeOffsetInit == 0) {
+ sTimeOffsetInit = 1;
+ sTimeOffset = -curTime;
+ curTime = 0;
+ }
+ }
+
+ //__android_log_print(ANDROID_LOG_INFO, "SanAngeles", "curTime=%ld", curTime);
+
+ appRender(curTime, sWindowWidth, sWindowHeight);
+}
diff --git a/build-system/tests/ndkSanAngeles/src/main/jni/app.h b/build-system/tests/ndkSanAngeles/src/main/jni/app.h
new file mode 100644
index 0000000..70ebd35
--- /dev/null
+++ b/build-system/tests/ndkSanAngeles/src/main/jni/app.h
@@ -0,0 +1,56 @@
+/* San Angeles Observation OpenGL ES version example
+ * Copyright 2004-2005 Jetro Lauha
+ * All rights reserved.
+ * Web: http://iki.fi/jetro/
+ *
+ * This source is free software; you can redistribute it and/or
+ * modify it under the terms of EITHER:
+ * (1) The GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at
+ * your option) any later version. The text of the GNU Lesser
+ * General Public License is included with this source in the
+ * file LICENSE-LGPL.txt.
+ * (2) The BSD-style license that is included with this source in
+ * the file LICENSE-BSD.txt.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the files
+ * LICENSE-LGPL.txt and LICENSE-BSD.txt for more details.
+ *
+ * $Id: app.h,v 1.14 2005/02/06 21:13:54 tonic Exp $
+ * $Revision: 1.14 $
+ */
+
+#ifndef APP_H_INCLUDED
+#define APP_H_INCLUDED
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#define WINDOW_DEFAULT_WIDTH 640
+#define WINDOW_DEFAULT_HEIGHT 480
+
+#define WINDOW_BPP 16
+
+
+// The simple framework expects the application code to define these functions.
+extern void appInit();
+extern void appDeinit();
+extern void appRender(long tick, int width, int height);
+
+/* Value is non-zero when application is alive, and 0 when it is closing.
+ * Defined by the application framework.
+ */
+extern int gAppAlive;
+
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif // !APP_H_INCLUDED
diff --git a/build-system/tests/ndkSanAngeles/src/main/jni/cams.h b/build-system/tests/ndkSanAngeles/src/main/jni/cams.h
new file mode 100644
index 0000000..2b1acb3
--- /dev/null
+++ b/build-system/tests/ndkSanAngeles/src/main/jni/cams.h
@@ -0,0 +1,65 @@
+/* San Angeles Observation OpenGL ES version example
+ * Copyright 2004-2005 Jetro Lauha
+ * All rights reserved.
+ * Web: http://iki.fi/jetro/
+ *
+ * This source is free software; you can redistribute it and/or
+ * modify it under the terms of EITHER:
+ * (1) The GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at
+ * your option) any later version. The text of the GNU Lesser
+ * General Public License is included with this source in the
+ * file LICENSE-LGPL.txt.
+ * (2) The BSD-style license that is included with this source in
+ * the file LICENSE-BSD.txt.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the files
+ * LICENSE-LGPL.txt and LICENSE-BSD.txt for more details.
+ *
+ * $Id: cams.h,v 1.7 2005/01/31 22:15:15 tonic Exp $
+ * $Revision: 1.7 $
+ */
+
+#ifndef CAMS_H_INCLUDED
+#define CAMS_H_INCLUDED
+
+
+/* Length in milliseconds of one camera track base unit.
+ * The value originates from the music synchronization.
+ */
+#define CAMTRACK_LEN 5442
+
+
+// Camera track definition for one camera trucking shot.
+typedef struct
+{
+ /* Five parameters of src[5] and dest[5]:
+ * eyeX, eyeY, eyeZ, viewAngle, viewHeightOffs
+ */
+ short src[5], dest[5];
+ unsigned char dist; // if >0, cam rotates around eye xy on dist * 0.1
+ unsigned char len; // length multiplier
+} CAMTRACK;
+
+static CAMTRACK sCamTracks[] =
+{
+ { { 4500, 2700, 100, 70, -30 }, { 50, 50, -90, -100, 0 }, 20, 1 },
+ { { -1448, 4294, 25, 363, 0 }, { -136, 202, 125, -98, 100 }, 0, 1 },
+ { { 1437, 4930, 200, -275, -20 }, { 1684, 0, 0, 9, 0 }, 0, 1 },
+ { { 1800, 3609, 200, 0, 675 }, { 0, 0, 0, 300, 0 }, 0, 1 },
+ { { 923, 996, 50, 2336, -80 }, { 0, -20, -50, 0, 170 }, 0, 1 },
+ { { -1663, -43, 600, 2170, 0 }, { 20, 0, -600, 0, 100 }, 0, 1 },
+ { { 1049, -1420, 175, 2111, -17 }, { 0, 0, 0, -334, 0 }, 0, 2 },
+ { { 0, 0, 50, 300, 25 }, { 0, 0, 0, 300, 0 }, 70, 2 },
+ { { -473, -953, 3500, -353, -350 }, { 0, 0, -2800, 0, 0 }, 0, 2 },
+ { { 191, 1938, 35, 1139, -17 }, { 1205, -2909, 0, 0, 0 }, 0, 2 },
+ { { -1449, -2700, 150, 0, 0 }, { 0, 2000, 0, 0, 0 }, 0, 2 },
+ { { 5273, 4992, 650, 373, -50 }, { -4598, -3072, 0, 0, 0 }, 0, 2 },
+ { { 3223, -3282, 1075, -393, -25 }, { 1649, -1649, 0, 0, 0 }, 0, 2 }
+};
+#define CAMTRACK_COUNT (sizeof(camTracks) / sizeof(camTracks[0]))
+
+
+#endif // !CAMS_H_INCLUDED
diff --git a/build-system/tests/ndkSanAngeles/src/main/jni/demo.c b/build-system/tests/ndkSanAngeles/src/main/jni/demo.c
new file mode 100644
index 0000000..9cb73d1
--- /dev/null
+++ b/build-system/tests/ndkSanAngeles/src/main/jni/demo.c
@@ -0,0 +1,792 @@
+/* San Angeles Observation OpenGL ES version example
+ * Copyright 2004-2005 Jetro Lauha
+ * All rights reserved.
+ * Web: http://iki.fi/jetro/
+ *
+ * This source is free software; you can redistribute it and/or
+ * modify it under the terms of EITHER:
+ * (1) The GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at
+ * your option) any later version. The text of the GNU Lesser
+ * General Public License is included with this source in the
+ * file LICENSE-LGPL.txt.
+ * (2) The BSD-style license that is included with this source in
+ * the file LICENSE-BSD.txt.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the files
+ * LICENSE-LGPL.txt and LICENSE-BSD.txt for more details.
+ *
+ * $Id: demo.c,v 1.10 2005/02/08 20:54:39 tonic Exp $
+ * $Revision: 1.10 $
+ */
+
+#include <stdlib.h>
+#include <math.h>
+#include <float.h>
+#include <assert.h>
+
+#include "importgl.h"
+
+#include "app.h"
+#include "shapes.h"
+#include "cams.h"
+
+
+// Total run length is 20 * camera track base unit length (see cams.h).
+#define RUN_LENGTH (20 * CAMTRACK_LEN)
+#undef PI
+#define PI 3.1415926535897932f
+#define RANDOM_UINT_MAX 65535
+
+
+static unsigned long sRandomSeed = 0;
+
+static void seedRandom(unsigned long seed)
+{
+ sRandomSeed = seed;
+}
+
+static unsigned long randomUInt()
+{
+ sRandomSeed = sRandomSeed * 0x343fd + 0x269ec3;
+ return sRandomSeed >> 16;
+}
+
+
+// Capped conversion from float to fixed.
+static long floatToFixed(float value)
+{
+ if (value < -32768) value = -32768;
+ if (value > 32767) value = 32767;
+ return (long)(value * 65536);
+}
+
+#define FIXED(value) floatToFixed(value)
+
+
+// Definition of one GL object in this demo.
+typedef struct {
+ /* Vertex array and color array are enabled for all objects, so their
+ * pointers must always be valid and non-NULL. Normal array is not
+ * used by the ground plane, so when its pointer is NULL then normal
+ * array usage is disabled.
+ *
+ * Vertex array is supposed to use GL_FIXED datatype and stride 0
+ * (i.e. tightly packed array). Color array is supposed to have 4
+ * components per color with GL_UNSIGNED_BYTE datatype and stride 0.
+ * Normal array is supposed to use GL_FIXED datatype and stride 0.
+ */
+ GLfixed *vertexArray;
+ GLubyte *colorArray;
+ GLfixed *normalArray;
+ GLint vertexComponents;
+ GLsizei count;
+} GLOBJECT;
+
+
+static long sStartTick = 0;
+static long sTick = 0;
+
+static int sCurrentCamTrack = 0;
+static long sCurrentCamTrackStartTick = 0;
+static long sNextCamTrackStartTick = 0x7fffffff;
+
+static GLOBJECT *sSuperShapeObjects[SUPERSHAPE_COUNT] = { NULL };
+static GLOBJECT *sGroundPlane = NULL;
+
+
+typedef struct {
+ float x, y, z;
+} VECTOR3;
+
+
+static void freeGLObject(GLOBJECT *object)
+{
+ if (object == NULL)
+ return;
+ free(object->normalArray);
+ free(object->colorArray);
+ free(object->vertexArray);
+ free(object);
+}
+
+
+static GLOBJECT * newGLObject(long vertices, int vertexComponents,
+ int useNormalArray)
+{
+ GLOBJECT *result;
+ result = (GLOBJECT *)malloc(sizeof(GLOBJECT));
+ if (result == NULL)
+ return NULL;
+ result->count = vertices;
+ result->vertexComponents = vertexComponents;
+ result->vertexArray = (GLfixed *)malloc(vertices * vertexComponents *
+ sizeof(GLfixed));
+ result->colorArray = (GLubyte *)malloc(vertices * 4 * sizeof(GLubyte));
+ if (useNormalArray)
+ {
+ result->normalArray = (GLfixed *)malloc(vertices * 3 *
+ sizeof(GLfixed));
+ }
+ else
+ result->normalArray = NULL;
+ if (result->vertexArray == NULL ||
+ result->colorArray == NULL ||
+ (useNormalArray && result->normalArray == NULL))
+ {
+ freeGLObject(result);
+ return NULL;
+ }
+ return result;
+}
+
+
+static void drawGLObject(GLOBJECT *object)
+{
+ assert(object != NULL);
+
+ glVertexPointer(object->vertexComponents, GL_FIXED,
+ 0, object->vertexArray);
+ glColorPointer(4, GL_UNSIGNED_BYTE, 0, object->colorArray);
+
+ // Already done in initialization:
+ //glEnableClientState(GL_VERTEX_ARRAY);
+ //glEnableClientState(GL_COLOR_ARRAY);
+
+ if (object->normalArray)
+ {
+ glNormalPointer(GL_FIXED, 0, object->normalArray);
+ glEnableClientState(GL_NORMAL_ARRAY);
+ }
+ else
+ glDisableClientState(GL_NORMAL_ARRAY);
+ glDrawArrays(GL_TRIANGLES, 0, object->count);
+}
+
+
+static void vector3Sub(VECTOR3 *dest, VECTOR3 *v1, VECTOR3 *v2)
+{
+ dest->x = v1->x - v2->x;
+ dest->y = v1->y - v2->y;
+ dest->z = v1->z - v2->z;
+}
+
+
+static void superShapeMap(VECTOR3 *point, float r1, float r2, float t, float p)
+{
+ // sphere-mapping of supershape parameters
+ point->x = (float)(cos(t) * cos(p) / r1 / r2);
+ point->y = (float)(sin(t) * cos(p) / r1 / r2);
+ point->z = (float)(sin(p) / r2);
+}
+
+
+static float ssFunc(const float t, const float *p)
+{
+ return (float)(pow(pow(fabs(cos(p[0] * t / 4)) / p[1], p[4]) +
+ pow(fabs(sin(p[0] * t / 4)) / p[2], p[5]), 1 / p[3]));
+}
+
+
+// Creates and returns a supershape object.
+// Based on Paul Bourke's POV-Ray implementation.
+// http://astronomy.swin.edu.au/~pbourke/povray/supershape/
+static GLOBJECT * createSuperShape(const float *params)
+{
+ const int resol1 = (int)params[SUPERSHAPE_PARAMS - 3];
+ const int resol2 = (int)params[SUPERSHAPE_PARAMS - 2];
+ // latitude 0 to pi/2 for no mirrored bottom
+ // (latitudeBegin==0 for -pi/2 to pi/2 originally)
+ const int latitudeBegin = resol2 / 4;
+ const int latitudeEnd = resol2 / 2; // non-inclusive
+ const int longitudeCount = resol1;
+ const int latitudeCount = latitudeEnd - latitudeBegin;
+ const long triangleCount = longitudeCount * latitudeCount * 2;
+ const long vertices = triangleCount * 3;
+ GLOBJECT *result;
+ float baseColor[3];
+ int a, longitude, latitude;
+ long currentVertex, currentQuad;
+
+ result = newGLObject(vertices, 3, 1);
+ if (result == NULL)
+ return NULL;
+
+ for (a = 0; a < 3; ++a)
+ baseColor[a] = ((randomUInt() % 155) + 100) / 255.f;
+
+ currentQuad = 0;
+ currentVertex = 0;
+
+ // longitude -pi to pi
+ for (longitude = 0; longitude < longitudeCount; ++longitude)
+ {
+
+ // latitude 0 to pi/2
+ for (latitude = latitudeBegin; latitude < latitudeEnd; ++latitude)
+ {
+ float t1 = -PI + longitude * 2 * PI / resol1;
+ float t2 = -PI + (longitude + 1) * 2 * PI / resol1;
+ float p1 = -PI / 2 + latitude * 2 * PI / resol2;
+ float p2 = -PI / 2 + (latitude + 1) * 2 * PI / resol2;
+ float r0, r1, r2, r3;
+
+ r0 = ssFunc(t1, params);
+ r1 = ssFunc(p1, ¶ms[6]);
+ r2 = ssFunc(t2, params);
+ r3 = ssFunc(p2, ¶ms[6]);
+
+ if (r0 != 0 && r1 != 0 && r2 != 0 && r3 != 0)
+ {
+ VECTOR3 pa, pb, pc, pd;
+ VECTOR3 v1, v2, n;
+ float ca;
+ int i;
+ //float lenSq, invLenSq;
+
+ superShapeMap(&pa, r0, r1, t1, p1);
+ superShapeMap(&pb, r2, r1, t2, p1);
+ superShapeMap(&pc, r2, r3, t2, p2);
+ superShapeMap(&pd, r0, r3, t1, p2);
+
+ // kludge to set lower edge of the object to fixed level
+ if (latitude == latitudeBegin + 1)
+ pa.z = pb.z = 0;
+
+ vector3Sub(&v1, &pb, &pa);
+ vector3Sub(&v2, &pd, &pa);
+
+ // Calculate normal with cross product.
+ /* i j k i j
+ * v1.x v1.y v1.z | v1.x v1.y
+ * v2.x v2.y v2.z | v2.x v2.y
+ */
+
+ n.x = v1.y * v2.z - v1.z * v2.y;
+ n.y = v1.z * v2.x - v1.x * v2.z;
+ n.z = v1.x * v2.y - v1.y * v2.x;
+
+ /* Pre-normalization of the normals is disabled here because
+ * they will be normalized anyway later due to automatic
+ * normalization (GL_NORMALIZE). It is enabled because the
+ * objects are scaled with glScale.
+ */
+ /*
+ lenSq = n.x * n.x + n.y * n.y + n.z * n.z;
+ invLenSq = (float)(1 / sqrt(lenSq));
+ n.x *= invLenSq;
+ n.y *= invLenSq;
+ n.z *= invLenSq;
+ */
+
+ ca = pa.z + 0.5f;
+
+ for (i = currentVertex * 3;
+ i < (currentVertex + 6) * 3;
+ i += 3)
+ {
+ result->normalArray[i] = FIXED(n.x);
+ result->normalArray[i + 1] = FIXED(n.y);
+ result->normalArray[i + 2] = FIXED(n.z);
+ }
+ for (i = currentVertex * 4;
+ i < (currentVertex + 6) * 4;
+ i += 4)
+ {
+ int a, color[3];
+ for (a = 0; a < 3; ++a)
+ {
+ color[a] = (int)(ca * baseColor[a] * 255);
+ if (color[a] > 255) color[a] = 255;
+ }
+ result->colorArray[i] = (GLubyte)color[0];
+ result->colorArray[i + 1] = (GLubyte)color[1];
+ result->colorArray[i + 2] = (GLubyte)color[2];
+ result->colorArray[i + 3] = 0;
+ }
+ result->vertexArray[currentVertex * 3] = FIXED(pa.x);
+ result->vertexArray[currentVertex * 3 + 1] = FIXED(pa.y);
+ result->vertexArray[currentVertex * 3 + 2] = FIXED(pa.z);
+ ++currentVertex;
+ result->vertexArray[currentVertex * 3] = FIXED(pb.x);
+ result->vertexArray[currentVertex * 3 + 1] = FIXED(pb.y);
+ result->vertexArray[currentVertex * 3 + 2] = FIXED(pb.z);
+ ++currentVertex;
+ result->vertexArray[currentVertex * 3] = FIXED(pd.x);
+ result->vertexArray[currentVertex * 3 + 1] = FIXED(pd.y);
+ result->vertexArray[currentVertex * 3 + 2] = FIXED(pd.z);
+ ++currentVertex;
+ result->vertexArray[currentVertex * 3] = FIXED(pb.x);
+ result->vertexArray[currentVertex * 3 + 1] = FIXED(pb.y);
+ result->vertexArray[currentVertex * 3 + 2] = FIXED(pb.z);
+ ++currentVertex;
+ result->vertexArray[currentVertex * 3] = FIXED(pc.x);
+ result->vertexArray[currentVertex * 3 + 1] = FIXED(pc.y);
+ result->vertexArray[currentVertex * 3 + 2] = FIXED(pc.z);
+ ++currentVertex;
+ result->vertexArray[currentVertex * 3] = FIXED(pd.x);
+ result->vertexArray[currentVertex * 3 + 1] = FIXED(pd.y);
+ result->vertexArray[currentVertex * 3 + 2] = FIXED(pd.z);
+ ++currentVertex;
+ } // r0 && r1 && r2 && r3
+ ++currentQuad;
+ } // latitude
+ } // longitude
+
+ // Set number of vertices in object to the actual amount created.
+ result->count = currentVertex;
+
+ return result;
+}
+
+
+static GLOBJECT * createGroundPlane()
+{
+ const int scale = 4;
+ const int yBegin = -15, yEnd = 15; // ends are non-inclusive
+ const int xBegin = -15, xEnd = 15;
+ const long triangleCount = (yEnd - yBegin) * (xEnd - xBegin) * 2;
+ const long vertices = triangleCount * 3;
+ GLOBJECT *result;
+ int x, y;
+ long currentVertex, currentQuad;
+
+ result = newGLObject(vertices, 2, 0);
+ if (result == NULL)
+ return NULL;
+
+ currentQuad = 0;
+ currentVertex = 0;
+
+ for (y = yBegin; y < yEnd; ++y)
+ {
+ for (x = xBegin; x < xEnd; ++x)
+ {
+ GLubyte color;
+ int i, a;
+ color = (GLubyte)((randomUInt() & 0x5f) + 81); // 101 1111
+ for (i = currentVertex * 4; i < (currentVertex + 6) * 4; i += 4)
+ {
+ result->colorArray[i] = color;
+ result->colorArray[i + 1] = color;
+ result->colorArray[i + 2] = color;
+ result->colorArray[i + 3] = 0;
+ }
+
+ // Axis bits for quad triangles:
+ // x: 011100 (0x1c), y: 110001 (0x31) (clockwise)
+ // x: 001110 (0x0e), y: 100011 (0x23) (counter-clockwise)
+ for (a = 0; a < 6; ++a)
+ {
+ const int xm = x + ((0x1c >> a) & 1);
+ const int ym = y + ((0x31 >> a) & 1);
+ const float m = (float)(cos(xm * 2) * sin(ym * 4) * 0.75f);
+ result->vertexArray[currentVertex * 2] =
+ FIXED(xm * scale + m);
+ result->vertexArray[currentVertex * 2 + 1] =
+ FIXED(ym * scale + m);
+ ++currentVertex;
+ }
+ ++currentQuad;
+ }
+ }
+ return result;
+}
+
+
+static void drawGroundPlane()
+{
+ glDisable(GL_CULL_FACE);
+ glDisable(GL_DEPTH_TEST);
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_ZERO, GL_SRC_COLOR);
+ glDisable(GL_LIGHTING);
+
+ drawGLObject(sGroundPlane);
+
+ glEnable(GL_LIGHTING);
+ glDisable(GL_BLEND);
+ glEnable(GL_DEPTH_TEST);
+}
+
+
+static void drawFadeQuad()
+{
+ static const GLfixed quadVertices[] = {
+ -0x10000, -0x10000,
+ 0x10000, -0x10000,
+ -0x10000, 0x10000,
+ 0x10000, -0x10000,
+ 0x10000, 0x10000,
+ -0x10000, 0x10000
+ };
+
+ const int beginFade = sTick - sCurrentCamTrackStartTick;
+ const int endFade = sNextCamTrackStartTick - sTick;
+ const int minFade = beginFade < endFade ? beginFade : endFade;
+
+ if (minFade < 1024)
+ {
+ const GLfixed fadeColor = minFade << 6;
+ glColor4x(fadeColor, fadeColor, fadeColor, 0);
+
+ glDisable(GL_DEPTH_TEST);
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_ZERO, GL_SRC_COLOR);
+ glDisable(GL_LIGHTING);
+
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+
+ glDisableClientState(GL_COLOR_ARRAY);
+ glDisableClientState(GL_NORMAL_ARRAY);
+ glVertexPointer(2, GL_FIXED, 0, quadVertices);
+ glDrawArrays(GL_TRIANGLES, 0, 6);
+
+ glEnableClientState(GL_COLOR_ARRAY);
+
+ glMatrixMode(GL_MODELVIEW);
+
+ glEnable(GL_LIGHTING);
+ glDisable(GL_BLEND);
+ glEnable(GL_DEPTH_TEST);
+ }
+}
+
+
+// Called from the app framework.
+void appInit()
+{
+ int a;
+
+ glEnable(GL_NORMALIZE);
+ glEnable(GL_DEPTH_TEST);
+ glDisable(GL_CULL_FACE);
+ glShadeModel(GL_FLAT);
+
+ glEnable(GL_LIGHTING);
+ glEnable(GL_LIGHT0);
+ glEnable(GL_LIGHT1);
+ glEnable(GL_LIGHT2);
+
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glEnableClientState(GL_COLOR_ARRAY);
+
+ seedRandom(15);
+
+ for (a = 0; a < SUPERSHAPE_COUNT; ++a)
+ {
+ sSuperShapeObjects[a] = createSuperShape(sSuperShapeParams[a]);
+ assert(sSuperShapeObjects[a] != NULL);
+ }
+ sGroundPlane = createGroundPlane();
+ assert(sGroundPlane != NULL);
+}
+
+
+// Called from the app framework.
+void appDeinit()
+{
+ int a;
+ for (a = 0; a < SUPERSHAPE_COUNT; ++a)
+ freeGLObject(sSuperShapeObjects[a]);
+ freeGLObject(sGroundPlane);
+}
+
+
+static void gluPerspective(GLfloat fovy, GLfloat aspect,
+ GLfloat zNear, GLfloat zFar)
+{
+ GLfloat xmin, xmax, ymin, ymax;
+
+ ymax = zNear * (GLfloat)tan(fovy * PI / 360);
+ ymin = -ymax;
+ xmin = ymin * aspect;
+ xmax = ymax * aspect;
+
+ glFrustumx((GLfixed)(xmin * 65536), (GLfixed)(xmax * 65536),
+ (GLfixed)(ymin * 65536), (GLfixed)(ymax * 65536),
+ (GLfixed)(zNear * 65536), (GLfixed)(zFar * 65536));
+}
+
+
+static void prepareFrame(int width, int height)
+{
+ glViewport(0, 0, width, height);
+
+ glClearColorx((GLfixed)(0.1f * 65536),
+ (GLfixed)(0.2f * 65536),
+ (GLfixed)(0.3f * 65536), 0x10000);
+ glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ gluPerspective(45, (float)width / height, 0.5f, 150);
+
+ glMatrixMode(GL_MODELVIEW);
+
+ glLoadIdentity();
+}
+
+
+static void configureLightAndMaterial()
+{
+ static GLfixed light0Position[] = { -0x40000, 0x10000, 0x10000, 0 };
+ static GLfixed light0Diffuse[] = { 0x10000, 0x6666, 0, 0x10000 };
+ static GLfixed light1Position[] = { 0x10000, -0x20000, -0x10000, 0 };
+ static GLfixed light1Diffuse[] = { 0x11eb, 0x23d7, 0x5999, 0x10000 };
+ static GLfixed light2Position[] = { -0x10000, 0, -0x40000, 0 };
+ static GLfixed light2Diffuse[] = { 0x11eb, 0x2b85, 0x23d7, 0x10000 };
+ static GLfixed materialSpecular[] = { 0x10000, 0x10000, 0x10000, 0x10000 };
+
+ glLightxv(GL_LIGHT0, GL_POSITION, light0Position);
+ glLightxv(GL_LIGHT0, GL_DIFFUSE, light0Diffuse);
+ glLightxv(GL_LIGHT1, GL_POSITION, light1Position);
+ glLightxv(GL_LIGHT1, GL_DIFFUSE, light1Diffuse);
+ glLightxv(GL_LIGHT2, GL_POSITION, light2Position);
+ glLightxv(GL_LIGHT2, GL_DIFFUSE, light2Diffuse);
+ glMaterialxv(GL_FRONT_AND_BACK, GL_SPECULAR, materialSpecular);
+
+ glMaterialx(GL_FRONT_AND_BACK, GL_SHININESS, 60 << 16);
+ glEnable(GL_COLOR_MATERIAL);
+}
+
+
+static void drawModels(float zScale)
+{
+ const int translationScale = 9;
+ int x, y;
+
+ seedRandom(9);
+
+ glScalex(1 << 16, 1 << 16, (GLfixed)(zScale * 65536));
+
+ for (y = -5; y <= 5; ++y)
+ {
+ for (x = -5; x <= 5; ++x)
+ {
+ float buildingScale;
+ GLfixed fixedScale;
+
+ int curShape = randomUInt() % SUPERSHAPE_COUNT;
+ buildingScale = sSuperShapeParams[curShape][SUPERSHAPE_PARAMS - 1];
+ fixedScale = (GLfixed)(buildingScale * 65536);
+
+ glPushMatrix();
+ glTranslatex((x * translationScale) * 65536,
+ (y * translationScale) * 65536,
+ 0);
+ glRotatex((GLfixed)((randomUInt() % 360) << 16), 0, 0, 1 << 16);
+ glScalex(fixedScale, fixedScale, fixedScale);
+
+ drawGLObject(sSuperShapeObjects[curShape]);
+ glPopMatrix();
+ }
+ }
+
+ for (x = -2; x <= 2; ++x)
+ {
+ const int shipScale100 = translationScale * 500;
+ const int offs100 = x * shipScale100 + (sTick % shipScale100);
+ float offs = offs100 * 0.01f;
+ GLfixed fixedOffs = (GLfixed)(offs * 65536);
+ glPushMatrix();
+ glTranslatex(fixedOffs, -4 * 65536, 2 << 16);
+ drawGLObject(sSuperShapeObjects[SUPERSHAPE_COUNT - 1]);
+ glPopMatrix();
+ glPushMatrix();
+ glTranslatex(-4 * 65536, fixedOffs, 4 << 16);
+ glRotatex(90 << 16, 0, 0, 1 << 16);
+ drawGLObject(sSuperShapeObjects[SUPERSHAPE_COUNT - 1]);
+ glPopMatrix();
+ }
+}
+
+
+/* Following gluLookAt implementation is adapted from the
+ * Mesa 3D Graphics library. http://www.mesa3d.org
+ */
+static void gluLookAt(GLfloat eyex, GLfloat eyey, GLfloat eyez,
+ GLfloat centerx, GLfloat centery, GLfloat centerz,
+ GLfloat upx, GLfloat upy, GLfloat upz)
+{
+ GLfloat m[16];
+ GLfloat x[3], y[3], z[3];
+ GLfloat mag;
+
+ /* Make rotation matrix */
+
+ /* Z vector */
+ z[0] = eyex - centerx;
+ z[1] = eyey - centery;
+ z[2] = eyez - centerz;
+ mag = (float)sqrt(z[0] * z[0] + z[1] * z[1] + z[2] * z[2]);
+ if (mag) { /* mpichler, 19950515 */
+ z[0] /= mag;
+ z[1] /= mag;
+ z[2] /= mag;
+ }
+
+ /* Y vector */
+ y[0] = upx;
+ y[1] = upy;
+ y[2] = upz;
+
+ /* X vector = Y cross Z */
+ x[0] = y[1] * z[2] - y[2] * z[1];
+ x[1] = -y[0] * z[2] + y[2] * z[0];
+ x[2] = y[0] * z[1] - y[1] * z[0];
+
+ /* Recompute Y = Z cross X */
+ y[0] = z[1] * x[2] - z[2] * x[1];
+ y[1] = -z[0] * x[2] + z[2] * x[0];
+ y[2] = z[0] * x[1] - z[1] * x[0];
+
+ /* mpichler, 19950515 */
+ /* cross product gives area of parallelogram, which is < 1.0 for
+ * non-perpendicular unit-length vectors; so normalize x, y here
+ */
+
+ mag = (float)sqrt(x[0] * x[0] + x[1] * x[1] + x[2] * x[2]);
+ if (mag) {
+ x[0] /= mag;
+ x[1] /= mag;
+ x[2] /= mag;
+ }
+
+ mag = (float)sqrt(y[0] * y[0] + y[1] * y[1] + y[2] * y[2]);
+ if (mag) {
+ y[0] /= mag;
+ y[1] /= mag;
+ y[2] /= mag;
+ }
+
+#define M(row,col) m[col*4+row]
+ M(0, 0) = x[0];
+ M(0, 1) = x[1];
+ M(0, 2) = x[2];
+ M(0, 3) = 0.0;
+ M(1, 0) = y[0];
+ M(1, 1) = y[1];
+ M(1, 2) = y[2];
+ M(1, 3) = 0.0;
+ M(2, 0) = z[0];
+ M(2, 1) = z[1];
+ M(2, 2) = z[2];
+ M(2, 3) = 0.0;
+ M(3, 0) = 0.0;
+ M(3, 1) = 0.0;
+ M(3, 2) = 0.0;
+ M(3, 3) = 1.0;
+#undef M
+ {
+ int a;
+ GLfixed fixedM[16];
+ for (a = 0; a < 16; ++a)
+ fixedM[a] = (GLfixed)(m[a] * 65536);
+ glMultMatrixx(fixedM);
+ }
+
+ /* Translate Eye to Origin */
+ glTranslatex((GLfixed)(-eyex * 65536),
+ (GLfixed)(-eyey * 65536),
+ (GLfixed)(-eyez * 65536));
+}
+
+
+static void camTrack()
+{
+ float lerp[5];
+ float eX, eY, eZ, cX, cY, cZ;
+ float trackPos;
+ CAMTRACK *cam;
+ long currentCamTick;
+ int a;
+
+ if (sNextCamTrackStartTick <= sTick)
+ {
+ ++sCurrentCamTrack;
+ sCurrentCamTrackStartTick = sNextCamTrackStartTick;
+ }
+ sNextCamTrackStartTick = sCurrentCamTrackStartTick +
+ sCamTracks[sCurrentCamTrack].len * CAMTRACK_LEN;
+
+ cam = &sCamTracks[sCurrentCamTrack];
+ currentCamTick = sTick - sCurrentCamTrackStartTick;
+ trackPos = (float)currentCamTick / (CAMTRACK_LEN * cam->len);
+
+ for (a = 0; a < 5; ++a)
+ lerp[a] = (cam->src[a] + cam->dest[a] * trackPos) * 0.01f;
+
+ if (cam->dist)
+ {
+ float dist = cam->dist * 0.1f;
+ cX = lerp[0];
+ cY = lerp[1];
+ cZ = lerp[2];
+ eX = cX - (float)cos(lerp[3]) * dist;
+ eY = cY - (float)sin(lerp[3]) * dist;
+ eZ = cZ - lerp[4];
+ }
+ else
+ {
+ eX = lerp[0];
+ eY = lerp[1];
+ eZ = lerp[2];
+ cX = eX + (float)cos(lerp[3]);
+ cY = eY + (float)sin(lerp[3]);
+ cZ = eZ + lerp[4];
+ }
+ gluLookAt(eX, eY, eZ, cX, cY, cZ, 0, 0, 1);
+}
+
+
+// Called from the app framework.
+/* The tick is current time in milliseconds, width and height
+ * are the image dimensions to be rendered.
+ */
+void appRender(long tick, int width, int height)
+{
+ if (sStartTick == 0)
+ sStartTick = tick;
+ if (!gAppAlive)
+ return;
+
+ // Actual tick value is "blurred" a little bit.
+ sTick = (sTick + tick - sStartTick) >> 1;
+
+ // Terminate application after running through the demonstration once.
+ if (sTick >= RUN_LENGTH)
+ {
+ gAppAlive = 0;
+ return;
+ }
+
+ // Prepare OpenGL ES for rendering of the frame.
+ prepareFrame(width, height);
+
+ // Update the camera position and set the lookat.
+ camTrack();
+
+ // Configure environment.
+ configureLightAndMaterial();
+
+ // Draw the reflection by drawing models with negated Z-axis.
+ glPushMatrix();
+ drawModels(-1);
+ glPopMatrix();
+
+ // Blend the ground plane to the window.
+ drawGroundPlane();
+
+ // Draw all the models normally.
+ drawModels(1);
+
+ // Draw fade quad over whole window (when changing cameras).
+ drawFadeQuad();
+}
diff --git a/build-system/tests/ndkSanAngeles/src/main/jni/importgl.c b/build-system/tests/ndkSanAngeles/src/main/jni/importgl.c
new file mode 100644
index 0000000..f501636
--- /dev/null
+++ b/build-system/tests/ndkSanAngeles/src/main/jni/importgl.c
@@ -0,0 +1,168 @@
+/* San Angeles Observation OpenGL ES version example
+ * Copyright 2004-2005 Jetro Lauha
+ * All rights reserved.
+ * Web: http://iki.fi/jetro/
+ *
+ * This source is free software; you can redistribute it and/or
+ * modify it under the terms of EITHER:
+ * (1) The GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at
+ * your option) any later version. The text of the GNU Lesser
+ * General Public License is included with this source in the
+ * file LICENSE-LGPL.txt.
+ * (2) The BSD-style license that is included with this source in
+ * the file LICENSE-BSD.txt.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the files
+ * LICENSE-LGPL.txt and LICENSE-BSD.txt for more details.
+ *
+ * $Id: importgl.c,v 1.4 2005/02/08 18:42:55 tonic Exp $
+ * $Revision: 1.4 $
+ */
+
+#undef WIN32
+#undef LINUX
+#ifdef _MSC_VER
+// Desktop or mobile Win32 environment:
+#define WIN32
+#else
+// Linux environment:
+#define LINUX
+#endif
+
+#ifndef DISABLE_IMPORTGL
+
+#if defined(WIN32)
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <tchar.h>
+static HMODULE sGLESDLL = NULL;
+#endif // WIN32
+
+#ifdef LINUX
+#include <stdlib.h>
+#include <dlfcn.h>
+static void *sGLESSO = NULL;
+#endif // LINUX
+
+#endif /* DISABLE_IMPORTGL */
+
+#define IMPORTGL_NO_FNPTR_DEFS
+#define IMPORTGL_API
+#define IMPORTGL_FNPTRINIT = NULL
+#include "importgl.h"
+
+
+/* Imports function pointers to selected function calls in OpenGL ES Common
+ * or Common Lite profile DLL or shared object. The function pointers are
+ * stored as global symbols with equivalent function name but prefixed with
+ * "funcPtr_". Standard gl/egl calls are redirected to the function pointers
+ * with preprocessor macros (see importgl.h).
+ */
+int importGLInit()
+{
+ int result = 1;
+
+#ifndef DISABLE_IMPORTGL
+
+#undef IMPORT_FUNC
+
+#ifdef WIN32
+ sGLESDLL = LoadLibrary(_T("libGLES_CM.dll"));
+ if (sGLESDLL == NULL)
+ sGLESDLL = LoadLibrary(_T("libGLES_CL.dll"));
+ if (sGLESDLL == NULL)
+ return 0; // Cannot find OpenGL ES Common or Common Lite DLL.
+
+ /* The following fetches address to each egl & gl function call
+ * and stores it to the related function pointer. Casting through
+ * void * results in warnings with VC warning level 4, which
+ * could be fixed by casting to the true type for each fetch.
+ */
+#define IMPORT_FUNC(funcName) do { \
+ void *procAddress = (void *)GetProcAddress(sGLESDLL, _T(#funcName)); \
+ if (procAddress == NULL) result = 0; \
+ *((void **)&FNPTR(funcName)) = procAddress; } while (0)
+#endif // WIN32
+
+#ifdef LINUX
+#ifdef ANDROID_NDK
+ sGLESSO = dlopen("libGLESv1_CM.so", RTLD_NOW);
+#else /* !ANDROID_NDK */
+ sGLESSO = dlopen("libGLES_CM.so", RTLD_NOW);
+ if (sGLESSO == NULL)
+ sGLESSO = dlopen("libGLES_CL.so", RTLD_NOW);
+#endif /* !ANDROID_NDK */
+ if (sGLESSO == NULL)
+ return 0; // Cannot find OpenGL ES Common or Common Lite SO.
+
+#define IMPORT_FUNC(funcName) do { \
+ void *procAddress = (void *)dlsym(sGLESSO, #funcName); \
+ if (procAddress == NULL) result = 0; \
+ *((void **)&FNPTR(funcName)) = procAddress; } while (0)
+#endif // LINUX
+
+#ifndef ANDROID_NDK
+ IMPORT_FUNC(eglChooseConfig);
+ IMPORT_FUNC(eglCreateContext);
+ IMPORT_FUNC(eglCreateWindowSurface);
+ IMPORT_FUNC(eglDestroyContext);
+ IMPORT_FUNC(eglDestroySurface);
+ IMPORT_FUNC(eglGetConfigAttrib);
+ IMPORT_FUNC(eglGetConfigs);
+ IMPORT_FUNC(eglGetDisplay);
+ IMPORT_FUNC(eglGetError);
+ IMPORT_FUNC(eglInitialize);
+ IMPORT_FUNC(eglMakeCurrent);
+ IMPORT_FUNC(eglSwapBuffers);
+ IMPORT_FUNC(eglTerminate);
+#endif /* !ANDROID_NDK */
+
+ IMPORT_FUNC(glBlendFunc);
+ IMPORT_FUNC(glClear);
+ IMPORT_FUNC(glClearColorx);
+ IMPORT_FUNC(glColor4x);
+ IMPORT_FUNC(glColorPointer);
+ IMPORT_FUNC(glDisable);
+ IMPORT_FUNC(glDisableClientState);
+ IMPORT_FUNC(glDrawArrays);
+ IMPORT_FUNC(glEnable);
+ IMPORT_FUNC(glEnableClientState);
+ IMPORT_FUNC(glFrustumx);
+ IMPORT_FUNC(glGetError);
+ IMPORT_FUNC(glLightxv);
+ IMPORT_FUNC(glLoadIdentity);
+ IMPORT_FUNC(glMaterialx);
+ IMPORT_FUNC(glMaterialxv);
+ IMPORT_FUNC(glMatrixMode);
+ IMPORT_FUNC(glMultMatrixx);
+ IMPORT_FUNC(glNormalPointer);
+ IMPORT_FUNC(glPopMatrix);
+ IMPORT_FUNC(glPushMatrix);
+ IMPORT_FUNC(glRotatex);
+ IMPORT_FUNC(glScalex);
+ IMPORT_FUNC(glShadeModel);
+ IMPORT_FUNC(glTranslatex);
+ IMPORT_FUNC(glVertexPointer);
+ IMPORT_FUNC(glViewport);
+
+#endif /* DISABLE_IMPORTGL */
+
+ return result;
+}
+
+
+void importGLDeinit()
+{
+#ifndef DISABLE_IMPORTGL
+#ifdef WIN32
+ FreeLibrary(sGLESDLL);
+#endif
+
+#ifdef LINUX
+ dlclose(sGLESSO);
+#endif
+#endif /* DISABLE_IMPORTGL */
+}
diff --git a/build-system/tests/ndkSanAngeles/src/main/jni/importgl.h b/build-system/tests/ndkSanAngeles/src/main/jni/importgl.h
new file mode 100644
index 0000000..a19a3a7
--- /dev/null
+++ b/build-system/tests/ndkSanAngeles/src/main/jni/importgl.h
@@ -0,0 +1,171 @@
+/* San Angeles Observation OpenGL ES version example
+ * Copyright 2004-2005 Jetro Lauha
+ * All rights reserved.
+ * Web: http://iki.fi/jetro/
+ *
+ * This source is free software; you can redistribute it and/or
+ * modify it under the terms of EITHER:
+ * (1) The GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at
+ * your option) any later version. The text of the GNU Lesser
+ * General Public License is included with this source in the
+ * file LICENSE-LGPL.txt.
+ * (2) The BSD-style license that is included with this source in
+ * the file LICENSE-BSD.txt.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the files
+ * LICENSE-LGPL.txt and LICENSE-BSD.txt for more details.
+ *
+ * $Id: importgl.h,v 1.4 2005/02/24 20:29:33 tonic Exp $
+ * $Revision: 1.4 $
+ */
+
+#ifndef IMPORTGL_H_INCLUDED
+#define IMPORTGL_H_INCLUDED
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#include <GLES/gl.h>
+#ifndef ANDROID_NDK
+#include <GLES/egl.h>
+#endif /* !ANDROID_NDK */
+
+/* Dynamically fetches pointers to the egl & gl functions.
+ * Should be called once on application initialization.
+ * Returns non-zero on success and 0 on failure.
+ */
+extern int importGLInit();
+
+/* Frees the handle to egl & gl functions library.
+ */
+extern void importGLDeinit();
+
+/* Use DISABLE_IMPORTGL if you want to link the OpenGL ES at
+ * compile/link time and not import it dynamically runtime.
+ */
+#ifndef DISABLE_IMPORTGL
+
+
+#ifndef IMPORTGL_API
+#define IMPORTGL_API extern
+#endif
+#ifndef IMPORTGL_FNPTRINIT
+#define IMPORTGL_FNPTRINIT
+#endif
+
+#define FNDEF(retType, funcName, args) IMPORTGL_API retType (*funcPtr_##funcName) args IMPORTGL_FNPTRINIT
+
+#ifndef ANDROID_NDK
+FNDEF(EGLBoolean, eglChooseConfig, (EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config));
+FNDEF(EGLContext, eglCreateContext, (EGLDisplay dpy, EGLConfig config, EGLContext share_list, const EGLint *attrib_list));
+FNDEF(EGLSurface, eglCreateWindowSurface, (EGLDisplay dpy, EGLConfig config, NativeWindowType window, const EGLint *attrib_list));
+FNDEF(EGLBoolean, eglDestroyContext, (EGLDisplay dpy, EGLContext ctx));
+FNDEF(EGLBoolean, eglDestroySurface, (EGLDisplay dpy, EGLSurface surface));
+FNDEF(EGLBoolean, eglGetConfigAttrib, (EGLDisplay dpy, EGLConfig config, EGLint attribute, EGLint *value));
+FNDEF(EGLBoolean, eglGetConfigs, (EGLDisplay dpy, EGLConfig *configs, EGLint config_size, EGLint *num_config));
+FNDEF(EGLDisplay, eglGetDisplay, (NativeDisplayType display));
+FNDEF(EGLint, eglGetError, (void));
+FNDEF(EGLBoolean, eglInitialize, (EGLDisplay dpy, EGLint *major, EGLint *minor));
+FNDEF(EGLBoolean, eglMakeCurrent, (EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx));
+FNDEF(EGLBoolean, eglSwapBuffers, (EGLDisplay dpy, EGLSurface draw));
+FNDEF(EGLBoolean, eglTerminate, (EGLDisplay dpy));
+#endif /* !ANDROID_NDK */
+
+FNDEF(void, glBlendFunc, (GLenum sfactor, GLenum dfactor));
+FNDEF(void, glClear, (GLbitfield mask));
+FNDEF(void, glClearColorx, (GLclampx red, GLclampx green, GLclampx blue, GLclampx alpha));
+FNDEF(void, glColor4x, (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha));
+FNDEF(void, glColorPointer, (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer));
+FNDEF(void, glDisable, (GLenum cap));
+FNDEF(void, glDisableClientState, (GLenum array));
+FNDEF(void, glDrawArrays, (GLenum mode, GLint first, GLsizei count));
+FNDEF(void, glEnable, (GLenum cap));
+FNDEF(void, glEnableClientState, (GLenum array));
+FNDEF(void, glFrustumx, (GLfixed left, GLfixed right, GLfixed bottom, GLfixed top, GLfixed zNear, GLfixed zFar));
+FNDEF(GLenum, glGetError, (void));
+FNDEF(void, glLightxv, (GLenum light, GLenum pname, const GLfixed *params));
+FNDEF(void, glLoadIdentity, (void));
+FNDEF(void, glMaterialx, (GLenum face, GLenum pname, GLfixed param));
+FNDEF(void, glMaterialxv, (GLenum face, GLenum pname, const GLfixed *params));
+FNDEF(void, glMatrixMode, (GLenum mode));
+FNDEF(void, glMultMatrixx, (const GLfixed *m));
+FNDEF(void, glNormalPointer, (GLenum type, GLsizei stride, const GLvoid *pointer));
+FNDEF(void, glPopMatrix, (void));
+FNDEF(void, glPushMatrix, (void));
+FNDEF(void, glRotatex, (GLfixed angle, GLfixed x, GLfixed y, GLfixed z));
+FNDEF(void, glScalex, (GLfixed x, GLfixed y, GLfixed z));
+FNDEF(void, glShadeModel, (GLenum mode));
+FNDEF(void, glTranslatex, (GLfixed x, GLfixed y, GLfixed z));
+FNDEF(void, glVertexPointer, (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer));
+FNDEF(void, glViewport, (GLint x, GLint y, GLsizei width, GLsizei height));
+
+
+#undef FN
+#define FNPTR(name) funcPtr_##name
+
+#ifndef IMPORTGL_NO_FNPTR_DEFS
+
+// Redirect egl* and gl* function calls to funcPtr_egl* and funcPtr_gl*.
+
+#ifndef ANDROID_NDK
+#define eglChooseConfig FNPTR(eglChooseConfig)
+#define eglCreateContext FNPTR(eglCreateContext)
+#define eglCreateWindowSurface FNPTR(eglCreateWindowSurface)
+#define eglDestroyContext FNPTR(eglDestroyContext)
+#define eglDestroySurface FNPTR(eglDestroySurface)
+#define eglGetConfigAttrib FNPTR(eglGetConfigAttrib)
+#define eglGetConfigs FNPTR(eglGetConfigs)
+#define eglGetDisplay FNPTR(eglGetDisplay)
+#define eglGetError FNPTR(eglGetError)
+#define eglInitialize FNPTR(eglInitialize)
+#define eglMakeCurrent FNPTR(eglMakeCurrent)
+#define eglSwapBuffers FNPTR(eglSwapBuffers)
+#define eglTerminate FNPTR(eglTerminate)
+#endif /* !ANDROID_NDK */
+
+#define glBlendFunc FNPTR(glBlendFunc)
+#define glClear FNPTR(glClear)
+#define glClearColorx FNPTR(glClearColorx)
+#define glColor4x FNPTR(glColor4x)
+#define glColorPointer FNPTR(glColorPointer)
+#define glDisable FNPTR(glDisable)
+#define glDisableClientState FNPTR(glDisableClientState)
+#define glDrawArrays FNPTR(glDrawArrays)
+#define glEnable FNPTR(glEnable)
+#define glEnableClientState FNPTR(glEnableClientState)
+#define glFrustumx FNPTR(glFrustumx)
+#define glGetError FNPTR(glGetError)
+#define glLightxv FNPTR(glLightxv)
+#define glLoadIdentity FNPTR(glLoadIdentity)
+#define glMaterialx FNPTR(glMaterialx)
+#define glMaterialxv FNPTR(glMaterialxv)
+#define glMatrixMode FNPTR(glMatrixMode)
+#define glMultMatrixx FNPTR(glMultMatrixx)
+#define glNormalPointer FNPTR(glNormalPointer)
+#define glPopMatrix FNPTR(glPopMatrix)
+#define glPushMatrix FNPTR(glPushMatrix)
+#define glRotatex FNPTR(glRotatex)
+#define glScalex FNPTR(glScalex)
+#define glShadeModel FNPTR(glShadeModel)
+#define glTranslatex FNPTR(glTranslatex)
+#define glVertexPointer FNPTR(glVertexPointer)
+#define glViewport FNPTR(glViewport)
+
+#endif // !IMPORTGL_NO_FNPTR_DEFS
+
+
+#endif // !DISABLE_IMPORTGL
+
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif // !IMPORTGL_H_INCLUDED
diff --git a/build-system/tests/ndkSanAngeles/src/main/jni/shapes.h b/build-system/tests/ndkSanAngeles/src/main/jni/shapes.h
new file mode 100644
index 0000000..25ffae8
--- /dev/null
+++ b/build-system/tests/ndkSanAngeles/src/main/jni/shapes.h
@@ -0,0 +1,59 @@
+/* San Angeles Observation OpenGL ES version example
+ * Copyright 2004-2005 Jetro Lauha
+ * All rights reserved.
+ * Web: http://iki.fi/jetro/
+ *
+ * This source is free software; you can redistribute it and/or
+ * modify it under the terms of EITHER:
+ * (1) The GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at
+ * your option) any later version. The text of the GNU Lesser
+ * General Public License is included with this source in the
+ * file LICENSE-LGPL.txt.
+ * (2) The BSD-style license that is included with this source in
+ * the file LICENSE-BSD.txt.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the files
+ * LICENSE-LGPL.txt and LICENSE-BSD.txt for more details.
+ *
+ * $Id: shapes.h,v 1.6 2005/01/31 22:15:30 tonic Exp $
+ * $Revision: 1.6 $
+ */
+
+#ifndef SHAPES_H_INCLUDED
+#define SHAPES_H_INCLUDED
+
+
+#define SUPERSHAPE_PARAMS 15
+
+static const float sSuperShapeParams[][SUPERSHAPE_PARAMS] =
+{
+ // m a b n1 n2 n3 m a b n1 n2 n3 res1 res2 scale (org.res1,res2)
+ { 10, 1, 2, 90, 1, -45, 8, 1, 1, -1, 1, -0.4f, 20, 30, 2 }, // 40, 60
+ { 10, 1, 2, 90, 1, -45, 4, 1, 1, 10, 1, -0.4f, 20, 20, 4 }, // 40, 40
+ { 10, 1, 2, 60, 1, -10, 4, 1, 1, -1, -2, -0.4f, 41, 41, 1 }, // 82, 82
+ { 6, 1, 1, 60, 1, -70, 8, 1, 1, 0.4f, 3, 0.25f, 20, 20, 1 }, // 40, 40
+ { 4, 1, 1, 30, 1, 20, 12, 1, 1, 0.4f, 3, 0.25f, 10, 30, 1 }, // 20, 60
+ { 8, 1, 1, 30, 1, -4, 8, 2, 1, -1, 5, 0.5f, 25, 26, 1 }, // 60, 60
+ { 13, 1, 1, 30, 1, -4, 13, 1, 1, 1, 5, 1, 30, 30, 6 }, // 60, 60
+ { 10, 1, 1.1f, -0.5f, 0.1f, 70, 60, 1, 1, -90, 0, -0.25f, 20, 60, 8 }, // 60, 180
+ { 7, 1, 1, 20, -0.3f, -3.5f, 6, 1, 1, -1, 4.5f, 0.5f, 10, 20, 4 }, // 60, 80
+ { 4, 1, 1, 10, 10, 10, 4, 1, 1, 10, 10, 10, 10, 20, 1 }, // 20, 40
+ { 4, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 10, 10, 2 }, // 10, 10
+ { 1, 1, 1, 38, -0.25f, 19, 4, 1, 1, 10, 10, 10, 10, 15, 2 }, // 20, 40
+ { 2, 1, 1, 0.7f, 0.3f, 0.2f, 3, 1, 1, 100, 100, 100, 10, 25, 2 }, // 20, 50
+ { 6, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 30, 30, 2 }, // 60, 60
+ { 3, 1, 1, 1, 1, 1, 6, 1, 1, 2, 1, 1, 10, 20, 2 }, // 20, 40
+ { 6, 1, 1, 6, 5.5f, 100, 6, 1, 1, 25, 10, 10, 30, 20, 2 }, // 60, 40
+ { 3, 1, 1, 0.5f, 1.7f, 1.7f, 2, 1, 1, 10, 10, 10, 20, 20, 2 }, // 40, 40
+ { 5, 1, 1, 0.1f, 1.7f, 1.7f, 1, 1, 1, 0.3f, 0.5f, 0.5f, 20, 20, 4 }, // 40, 40
+ { 2, 1, 1, 6, 5.5f, 100, 6, 1, 1, 4, 10, 10, 10, 22, 1 }, // 40, 40
+ { 6, 1, 1, -1, 70, 0.1f, 9, 1, 0.5f, -98, 0.05f, -45, 20, 30, 4 }, // 60, 91
+ { 6, 1, 1, -1, 90, -0.1f, 7, 1, 1, 90, 1.3f, 34, 13, 16, 1 }, // 32, 60
+};
+#define SUPERSHAPE_COUNT (sizeof(sSuperShapeParams) / sizeof(sSuperShapeParams[0]))
+
+
+#endif // !SHAPES_H_INCLUDED
diff --git a/build-system/tests/ndkSanAngeles/src/main/res/layout/main.xml b/build-system/tests/ndkSanAngeles/src/main/res/layout/main.xml
new file mode 100644
index 0000000..3e76662
--- /dev/null
+++ b/build-system/tests/ndkSanAngeles/src/main/res/layout/main.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+<TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="Hello World, DemoActivity"
+ />
+</LinearLayout>
+
diff --git a/build-system/tests/ndkSanAngeles/src/main/res/values/strings.xml b/build-system/tests/ndkSanAngeles/src/main/res/values/strings.xml
new file mode 100644
index 0000000..574c333
--- /dev/null
+++ b/build-system/tests/ndkSanAngeles/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">SanAngeles</string>
+</resources>
diff --git a/build-system/tests/overlay1/build.gradle b/build-system/tests/overlay1/build.gradle
new file mode 100644
index 0000000..026e777
--- /dev/null
+++ b/build-system/tests/overlay1/build.gradle
@@ -0,0 +1,15 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+
+apply plugin: 'android'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
\ No newline at end of file
diff --git a/build-system/tests/overlay1/src/debug/res/drawable/type_overlay.png b/build-system/tests/overlay1/src/debug/res/drawable/type_overlay.png
new file mode 100644
index 0000000..47e1adf
--- /dev/null
+++ b/build-system/tests/overlay1/src/debug/res/drawable/type_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay1/src/instrumentTest/java/com/android/tests/overlay1/MainTest.java b/build-system/tests/overlay1/src/instrumentTest/java/com/android/tests/overlay1/MainTest.java
new file mode 100644
index 0000000..4797e72
--- /dev/null
+++ b/build-system/tests/overlay1/src/instrumentTest/java/com/android/tests/overlay1/MainTest.java
@@ -0,0 +1,60 @@
+package com.android.tests.overlay1;
+
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.ImageView;
+
+
+public class MainTest extends ActivityInstrumentationTestCase2<Main> {
+
+ private final static int GREEN = 0xFF00FF00;
+
+ private ImageView mNoOverlayIV;
+ private ImageView mTypeOverlayIV;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
+ */
+ public MainTest() {
+ super(Main.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final Main a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+ mNoOverlayIV = (ImageView) a.findViewById(R.id.no_overlay);
+ mTypeOverlayIV = (ImageView) a.findViewById(R.id.type_overlay);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mNoOverlayIV);
+ assertNotNull(mTypeOverlayIV);
+ }
+
+ public void testNoOverlay() {
+ pixelLooker(mNoOverlayIV, GREEN);
+ }
+
+ public void testTypeOverlay() {
+ pixelLooker(mTypeOverlayIV, GREEN);
+ }
+
+ private void pixelLooker(ImageView iv, int expectedColor) {
+ BitmapDrawable d = (BitmapDrawable) iv.getDrawable();
+ Bitmap bitmap = d.getBitmap();
+ assertEquals(expectedColor, bitmap.getPixel(0, 0));
+ }
+}
+
diff --git a/build-system/tests/overlay1/src/main/AndroidManifest.xml b/build-system/tests/overlay1/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..63aee8e
--- /dev/null
+++ b/build-system/tests/overlay1/src/main/AndroidManifest.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.overlay1">
+ <application android:label="@string/app_name" android:icon="@drawable/icon">
+ <activity android:name=".Main"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/build-system/tests/overlay1/src/main/java/com/android/tests/overlay1/Main.java b/build-system/tests/overlay1/src/main/java/com/android/tests/overlay1/Main.java
new file mode 100644
index 0000000..de7b5d4
--- /dev/null
+++ b/build-system/tests/overlay1/src/main/java/com/android/tests/overlay1/Main.java
@@ -0,0 +1,15 @@
+package com.android.tests.overlay1;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class Main extends Activity
+{
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ }
+}
diff --git a/build-system/tests/overlay1/src/main/res/drawable/icon.png b/build-system/tests/overlay1/src/main/res/drawable/icon.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/overlay1/src/main/res/drawable/icon.png
Binary files differ
diff --git a/build-system/tests/overlay1/src/main/res/drawable/no_overlay.png b/build-system/tests/overlay1/src/main/res/drawable/no_overlay.png
new file mode 100644
index 0000000..47e1adf
--- /dev/null
+++ b/build-system/tests/overlay1/src/main/res/drawable/no_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay1/src/main/res/drawable/type_overlay.png b/build-system/tests/overlay1/src/main/res/drawable/type_overlay.png
new file mode 100644
index 0000000..b55e544
--- /dev/null
+++ b/build-system/tests/overlay1/src/main/res/drawable/type_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay1/src/main/res/layout/main.xml b/build-system/tests/overlay1/src/main/res/layout/main.xml
new file mode 100644
index 0000000..ab46a13
--- /dev/null
+++ b/build-system/tests/overlay1/src/main/res/layout/main.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+ <ImageView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:src="@drawable/no_overlay"
+ android:id="@+id/no_overlay" />
+ <ImageView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:src="@drawable/type_overlay"
+ android:id="@+id/type_overlay" />
+</LinearLayout>
+
diff --git a/build-system/tests/overlay1/src/main/res/values/strings.xml b/build-system/tests/overlay1/src/main/res/values/strings.xml
new file mode 100644
index 0000000..acc034c
--- /dev/null
+++ b/build-system/tests/overlay1/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">_Test-Overlay1</string>
+</resources>
diff --git a/build-system/tests/overlay2/build.gradle b/build-system/tests/overlay2/build.gradle
new file mode 100644
index 0000000..e785ff5
--- /dev/null
+++ b/build-system/tests/overlay2/build.gradle
@@ -0,0 +1,19 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+
+apply plugin: 'android'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+ productFlavors {
+ one {}
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/overlay2/src/debug/res/drawable/type_flavor_overlay.png b/build-system/tests/overlay2/src/debug/res/drawable/type_flavor_overlay.png
new file mode 100644
index 0000000..47e1adf
--- /dev/null
+++ b/build-system/tests/overlay2/src/debug/res/drawable/type_flavor_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay2/src/debug/res/drawable/type_overlay.png b/build-system/tests/overlay2/src/debug/res/drawable/type_overlay.png
new file mode 100644
index 0000000..47e1adf
--- /dev/null
+++ b/build-system/tests/overlay2/src/debug/res/drawable/type_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay2/src/debug/res/drawable/variant_type_flavor_overlay.png b/build-system/tests/overlay2/src/debug/res/drawable/variant_type_flavor_overlay.png
new file mode 100644
index 0000000..0e89381
--- /dev/null
+++ b/build-system/tests/overlay2/src/debug/res/drawable/variant_type_flavor_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay2/src/instrumentTest/java/com/android/tests/overlay2/MainTest.java b/build-system/tests/overlay2/src/instrumentTest/java/com/android/tests/overlay2/MainTest.java
new file mode 100644
index 0000000..364640d
--- /dev/null
+++ b/build-system/tests/overlay2/src/instrumentTest/java/com/android/tests/overlay2/MainTest.java
@@ -0,0 +1,81 @@
+package com.android.tests.overlay2;
+
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.ImageView;
+
+
+public class MainTest extends ActivityInstrumentationTestCase2<Main> {
+
+ private final static int GREEN = 0xFF00FF00;
+
+ private ImageView mNoOverlayIV;
+ private ImageView mTypeOverlayIV;
+ private ImageView mFlavorOverlayIV;
+ private ImageView mTypeFlavorOverlayIV;
+ private ImageView mVariantTypeFlavorOverlayIV;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
+ */
+ public MainTest() {
+ super(Main.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final Main a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+ mNoOverlayIV = (ImageView) a.findViewById(R.id.no_overlay);
+ mTypeOverlayIV = (ImageView) a.findViewById(R.id.type_overlay);
+ mFlavorOverlayIV = (ImageView) a.findViewById(R.id.flavor_overlay);
+ mTypeFlavorOverlayIV = (ImageView) a.findViewById(R.id.type_flavor_overlay);
+ mVariantTypeFlavorOverlayIV = (ImageView) a.findViewById(R.id.variant_type_flavor_overlay);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mNoOverlayIV);
+ assertNotNull(mTypeOverlayIV);
+ assertNotNull(mFlavorOverlayIV);
+ assertNotNull(mTypeFlavorOverlayIV);
+ assertNotNull(mVariantTypeFlavorOverlayIV);
+ }
+
+ public void testNoOverlay() {
+ pixelLooker(mNoOverlayIV, GREEN);
+ }
+
+ public void testTypeOverlay() {
+ pixelLooker(mTypeOverlayIV, GREEN);
+ }
+
+ public void testFlavorOverlay() {
+ pixelLooker(mFlavorOverlayIV, GREEN);
+ }
+
+ public void testTypeFlavorOverlay() {
+ pixelLooker(mTypeFlavorOverlayIV, GREEN);
+ }
+
+ public void testVariantTypeFlavorOverlay() {
+ pixelLooker(mVariantTypeFlavorOverlayIV, GREEN);
+ }
+
+ private void pixelLooker(ImageView iv, int expectedColor) {
+ BitmapDrawable d = (BitmapDrawable) iv.getDrawable();
+ Bitmap bitmap = d.getBitmap();
+ assertEquals(expectedColor, bitmap.getPixel(0, 0));
+ }
+}
+
diff --git a/build-system/tests/overlay2/src/main/AndroidManifest.xml b/build-system/tests/overlay2/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..1c35a5b
--- /dev/null
+++ b/build-system/tests/overlay2/src/main/AndroidManifest.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.overlay2">
+ <application android:label="@string/app_name" android:icon="@drawable/icon">
+ <activity android:name=".Main"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/build-system/tests/overlay2/src/main/java/com/android/tests/overlay2/Main.java b/build-system/tests/overlay2/src/main/java/com/android/tests/overlay2/Main.java
new file mode 100644
index 0000000..e8a0a83
--- /dev/null
+++ b/build-system/tests/overlay2/src/main/java/com/android/tests/overlay2/Main.java
@@ -0,0 +1,15 @@
+package com.android.tests.overlay2;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class Main extends Activity
+{
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ }
+}
diff --git a/build-system/tests/overlay2/src/main/res/drawable/flavor_overlay.png b/build-system/tests/overlay2/src/main/res/drawable/flavor_overlay.png
new file mode 100644
index 0000000..b55e544
--- /dev/null
+++ b/build-system/tests/overlay2/src/main/res/drawable/flavor_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay2/src/main/res/drawable/icon.png b/build-system/tests/overlay2/src/main/res/drawable/icon.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/overlay2/src/main/res/drawable/icon.png
Binary files differ
diff --git a/build-system/tests/overlay2/src/main/res/drawable/no_overlay.png b/build-system/tests/overlay2/src/main/res/drawable/no_overlay.png
new file mode 100644
index 0000000..47e1adf
--- /dev/null
+++ b/build-system/tests/overlay2/src/main/res/drawable/no_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay2/src/main/res/drawable/type_flavor_overlay.png b/build-system/tests/overlay2/src/main/res/drawable/type_flavor_overlay.png
new file mode 100644
index 0000000..b55e544
--- /dev/null
+++ b/build-system/tests/overlay2/src/main/res/drawable/type_flavor_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay2/src/main/res/drawable/type_overlay.png b/build-system/tests/overlay2/src/main/res/drawable/type_overlay.png
new file mode 100644
index 0000000..b55e544
--- /dev/null
+++ b/build-system/tests/overlay2/src/main/res/drawable/type_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay2/src/main/res/drawable/variant_type_flavor_overlay.png b/build-system/tests/overlay2/src/main/res/drawable/variant_type_flavor_overlay.png
new file mode 100644
index 0000000..b55e544
--- /dev/null
+++ b/build-system/tests/overlay2/src/main/res/drawable/variant_type_flavor_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay2/src/main/res/layout/main.xml b/build-system/tests/overlay2/src/main/res/layout/main.xml
new file mode 100644
index 0000000..408c1f4
--- /dev/null
+++ b/build-system/tests/overlay2/src/main/res/layout/main.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+ <ImageView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:src="@drawable/no_overlay"
+ android:id="@+id/no_overlay" />
+ <ImageView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:src="@drawable/type_overlay"
+ android:id="@+id/type_overlay" />
+ <ImageView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:src="@drawable/flavor_overlay"
+ android:id="@+id/flavor_overlay" />
+ <ImageView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:src="@drawable/type_flavor_overlay"
+ android:id="@+id/type_flavor_overlay" />
+ <ImageView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:src="@drawable/variant_type_flavor_overlay"
+ android:id="@+id/variant_type_flavor_overlay" />
+</LinearLayout>
+
diff --git a/build-system/tests/overlay2/src/main/res/values/strings.xml b/build-system/tests/overlay2/src/main/res/values/strings.xml
new file mode 100644
index 0000000..d1420e7
--- /dev/null
+++ b/build-system/tests/overlay2/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">_Test-Overlay2</string>
+</resources>
diff --git a/build-system/tests/overlay2/src/one/res/drawable/flavor_overlay.png b/build-system/tests/overlay2/src/one/res/drawable/flavor_overlay.png
new file mode 100644
index 0000000..47e1adf
--- /dev/null
+++ b/build-system/tests/overlay2/src/one/res/drawable/flavor_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay2/src/one/res/drawable/type_flavor_overlay.png b/build-system/tests/overlay2/src/one/res/drawable/type_flavor_overlay.png
new file mode 100644
index 0000000..0e89381
--- /dev/null
+++ b/build-system/tests/overlay2/src/one/res/drawable/type_flavor_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay2/src/one/res/drawable/variant_type_flavor_overlay.png b/build-system/tests/overlay2/src/one/res/drawable/variant_type_flavor_overlay.png
new file mode 100644
index 0000000..0e89381
--- /dev/null
+++ b/build-system/tests/overlay2/src/one/res/drawable/variant_type_flavor_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay2/src/oneDebug/res/drawable/variant_type_flavor_overlay.png b/build-system/tests/overlay2/src/oneDebug/res/drawable/variant_type_flavor_overlay.png
new file mode 100644
index 0000000..47e1adf
--- /dev/null
+++ b/build-system/tests/overlay2/src/oneDebug/res/drawable/variant_type_flavor_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay3/build.gradle b/build-system/tests/overlay3/build.gradle
new file mode 100644
index 0000000..5132796
--- /dev/null
+++ b/build-system/tests/overlay3/build.gradle
@@ -0,0 +1,45 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+
+apply plugin: 'android'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+ flavorGroups "pricing", "releaseType"
+
+ sourceSets {
+ beta.setRoot('movedSrc/beta')
+ free.setRoot('movedSrc/free')
+ debug.setRoot('movedSrc/debug')
+ freeBeta.setRoot('movedSrc/freeBeta')
+ freeBetaDebug.setRoot('movedSrc/freeBetaDebug')
+ freeNormal.setRoot('movedSrc/freeNormal')
+ }
+
+ productFlavors {
+
+ beta {
+ flavorGroup "releaseType"
+ }
+
+ normal {
+ flavorGroup "releaseType"
+ }
+
+ free {
+ flavorGroup "pricing"
+ }
+
+ paid {
+ flavorGroup "pricing"
+ }
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/overlay3/movedSrc/beta/res/drawable/beta_overlay.png b/build-system/tests/overlay3/movedSrc/beta/res/drawable/beta_overlay.png
new file mode 100644
index 0000000..47e1adf
--- /dev/null
+++ b/build-system/tests/overlay3/movedSrc/beta/res/drawable/beta_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay3/movedSrc/beta/res/drawable/debug_overlay.png b/build-system/tests/overlay3/movedSrc/beta/res/drawable/debug_overlay.png
new file mode 100644
index 0000000..b55e544
--- /dev/null
+++ b/build-system/tests/overlay3/movedSrc/beta/res/drawable/debug_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay3/movedSrc/beta/res/drawable/free_beta_debug_overlay.png b/build-system/tests/overlay3/movedSrc/beta/res/drawable/free_beta_debug_overlay.png
new file mode 100644
index 0000000..b55e544
--- /dev/null
+++ b/build-system/tests/overlay3/movedSrc/beta/res/drawable/free_beta_debug_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay3/movedSrc/beta/res/drawable/free_beta_overlay.png b/build-system/tests/overlay3/movedSrc/beta/res/drawable/free_beta_overlay.png
new file mode 100644
index 0000000..b55e544
--- /dev/null
+++ b/build-system/tests/overlay3/movedSrc/beta/res/drawable/free_beta_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay3/movedSrc/beta/res/drawable/free_overlay.png b/build-system/tests/overlay3/movedSrc/beta/res/drawable/free_overlay.png
new file mode 100644
index 0000000..b55e544
--- /dev/null
+++ b/build-system/tests/overlay3/movedSrc/beta/res/drawable/free_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay3/movedSrc/debug/res/drawable/debug_overlay.png b/build-system/tests/overlay3/movedSrc/debug/res/drawable/debug_overlay.png
new file mode 100644
index 0000000..47e1adf
--- /dev/null
+++ b/build-system/tests/overlay3/movedSrc/debug/res/drawable/debug_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay3/movedSrc/debug/res/drawable/free_beta_debug_overlay.png b/build-system/tests/overlay3/movedSrc/debug/res/drawable/free_beta_debug_overlay.png
new file mode 100644
index 0000000..b55e544
--- /dev/null
+++ b/build-system/tests/overlay3/movedSrc/debug/res/drawable/free_beta_debug_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay3/movedSrc/free/res/drawable/debug_overlay.png b/build-system/tests/overlay3/movedSrc/free/res/drawable/debug_overlay.png
new file mode 100644
index 0000000..b55e544
--- /dev/null
+++ b/build-system/tests/overlay3/movedSrc/free/res/drawable/debug_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay3/movedSrc/free/res/drawable/free_beta_debug_overlay.png b/build-system/tests/overlay3/movedSrc/free/res/drawable/free_beta_debug_overlay.png
new file mode 100644
index 0000000..b55e544
--- /dev/null
+++ b/build-system/tests/overlay3/movedSrc/free/res/drawable/free_beta_debug_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay3/movedSrc/free/res/drawable/free_beta_overlay.png b/build-system/tests/overlay3/movedSrc/free/res/drawable/free_beta_overlay.png
new file mode 100644
index 0000000..b55e544
--- /dev/null
+++ b/build-system/tests/overlay3/movedSrc/free/res/drawable/free_beta_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay3/movedSrc/free/res/drawable/free_overlay.png b/build-system/tests/overlay3/movedSrc/free/res/drawable/free_overlay.png
new file mode 100644
index 0000000..47e1adf
--- /dev/null
+++ b/build-system/tests/overlay3/movedSrc/free/res/drawable/free_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay3/movedSrc/freeBeta/res/drawable/debug_overlay.png b/build-system/tests/overlay3/movedSrc/freeBeta/res/drawable/debug_overlay.png
new file mode 100644
index 0000000..b55e544
--- /dev/null
+++ b/build-system/tests/overlay3/movedSrc/freeBeta/res/drawable/debug_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay3/movedSrc/freeBeta/res/drawable/free_beta_debug_overlay.png b/build-system/tests/overlay3/movedSrc/freeBeta/res/drawable/free_beta_debug_overlay.png
new file mode 100644
index 0000000..b55e544
--- /dev/null
+++ b/build-system/tests/overlay3/movedSrc/freeBeta/res/drawable/free_beta_debug_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay3/movedSrc/freeBeta/res/drawable/free_beta_overlay.png b/build-system/tests/overlay3/movedSrc/freeBeta/res/drawable/free_beta_overlay.png
new file mode 100644
index 0000000..47e1adf
--- /dev/null
+++ b/build-system/tests/overlay3/movedSrc/freeBeta/res/drawable/free_beta_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay3/movedSrc/freeBetaDebug/res/drawable/free_beta_debug_overlay.png b/build-system/tests/overlay3/movedSrc/freeBetaDebug/res/drawable/free_beta_debug_overlay.png
new file mode 100644
index 0000000..47e1adf
--- /dev/null
+++ b/build-system/tests/overlay3/movedSrc/freeBetaDebug/res/drawable/free_beta_debug_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay3/movedSrc/freeNormal/res/drawable/debug_overlay.png b/build-system/tests/overlay3/movedSrc/freeNormal/res/drawable/debug_overlay.png
new file mode 100644
index 0000000..b55e544
--- /dev/null
+++ b/build-system/tests/overlay3/movedSrc/freeNormal/res/drawable/debug_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay3/movedSrc/freeNormal/res/drawable/free_normal_overlay.png b/build-system/tests/overlay3/movedSrc/freeNormal/res/drawable/free_normal_overlay.png
new file mode 100644
index 0000000..47e1adf
--- /dev/null
+++ b/build-system/tests/overlay3/movedSrc/freeNormal/res/drawable/free_normal_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay3/src/instrumentTest/java/com/android/tests/overlay2/MainTest.java b/build-system/tests/overlay3/src/instrumentTest/java/com/android/tests/overlay2/MainTest.java
new file mode 100644
index 0000000..a4d6da1
--- /dev/null
+++ b/build-system/tests/overlay3/src/instrumentTest/java/com/android/tests/overlay2/MainTest.java
@@ -0,0 +1,98 @@
+package com.android.tests.overlay2;
+
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.ImageView;
+
+
+public class MainTest extends ActivityInstrumentationTestCase2<Main> {
+
+ private final static int RED = 0xFFFF0000;
+ private final static int GREEN = 0xFF00FF00;
+
+ private ImageView mNoOverlayIV;
+ private ImageView mDebugOverlayIV;
+ private ImageView mBetaOverlayIV;
+ private ImageView mFreeNormalOverlayIV;
+ private ImageView mFreeBetaDebugOverlayIV;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
+ */
+ public MainTest() {
+ super(Main.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final Main a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+ mNoOverlayIV = (ImageView) a.findViewById(R.id.no_overlay);
+ mDebugOverlayIV = (ImageView) a.findViewById(R.id.debug_overlay);
+ mBetaOverlayIV = (ImageView) a.findViewById(R.id.beta_overlay);
+ mFreeNormalOverlayIV = (ImageView) a.findViewById(R.id.free_normal_overlay);
+ mFreeBetaDebugOverlayIV = (ImageView) a.findViewById(R.id.free_beta_debug_overlay);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mNoOverlayIV);
+ assertNotNull(mDebugOverlayIV);
+ assertNotNull(mFreeBetaDebugOverlayIV);
+ assertNotNull(mFreeNormalOverlayIV);
+ assertNotNull(mFreeBetaDebugOverlayIV);
+ }
+
+ public void testNoOverlay() {
+ pixelLooker(mNoOverlayIV, GREEN);
+ }
+
+ public void testDebugOverlay() {
+ if ("debug".equals(BuildConfig.BUILD_TYPE)) {
+ pixelLooker(mDebugOverlayIV, GREEN);
+ } else {
+ pixelLooker(mDebugOverlayIV, RED);
+ }
+ }
+
+ public void testBetaOverlay() {
+ if ("beta".equals(BuildConfig.FLAVOR2)) {
+ pixelLooker(mBetaOverlayIV, GREEN);
+ } else {
+ pixelLooker(mBetaOverlayIV, RED);
+ }
+ }
+
+ public void testFreeNormalOverlay() {
+ if ("freeNormal".equals(BuildConfig.FLAVOR)) {
+ pixelLooker(mFreeNormalOverlayIV, GREEN);
+ } else {
+ pixelLooker(mFreeNormalOverlayIV, RED);
+ }
+ }
+
+ public void testFreeBetaDebugOverlay() {
+ if ("freeBeta".equals(BuildConfig.FLAVOR) && "debug".equals(BuildConfig.BUILD_TYPE)) {
+ pixelLooker(mFreeBetaDebugOverlayIV, GREEN);
+ } else {
+ pixelLooker(mFreeBetaDebugOverlayIV, RED);
+ }
+ }
+
+ private void pixelLooker(ImageView iv, int expectedColor) {
+ BitmapDrawable d = (BitmapDrawable) iv.getDrawable();
+ Bitmap bitmap = d.getBitmap();
+ assertEquals(expectedColor, bitmap.getPixel(0, 0));
+ }
+}
+
diff --git a/build-system/tests/overlay3/src/main/AndroidManifest.xml b/build-system/tests/overlay3/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..1c35a5b
--- /dev/null
+++ b/build-system/tests/overlay3/src/main/AndroidManifest.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.overlay2">
+ <application android:label="@string/app_name" android:icon="@drawable/icon">
+ <activity android:name=".Main"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/build-system/tests/overlay3/src/main/java/com/android/tests/overlay2/Main.java b/build-system/tests/overlay3/src/main/java/com/android/tests/overlay2/Main.java
new file mode 100644
index 0000000..e8a0a83
--- /dev/null
+++ b/build-system/tests/overlay3/src/main/java/com/android/tests/overlay2/Main.java
@@ -0,0 +1,15 @@
+package com.android.tests.overlay2;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class Main extends Activity
+{
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ }
+}
diff --git a/build-system/tests/overlay3/src/main/res/drawable/beta_overlay.png b/build-system/tests/overlay3/src/main/res/drawable/beta_overlay.png
new file mode 100644
index 0000000..b55e544
--- /dev/null
+++ b/build-system/tests/overlay3/src/main/res/drawable/beta_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay3/src/main/res/drawable/debug_overlay.png b/build-system/tests/overlay3/src/main/res/drawable/debug_overlay.png
new file mode 100644
index 0000000..b55e544
--- /dev/null
+++ b/build-system/tests/overlay3/src/main/res/drawable/debug_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay3/src/main/res/drawable/free_beta_debug_overlay.png b/build-system/tests/overlay3/src/main/res/drawable/free_beta_debug_overlay.png
new file mode 100644
index 0000000..b55e544
--- /dev/null
+++ b/build-system/tests/overlay3/src/main/res/drawable/free_beta_debug_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay3/src/main/res/drawable/free_beta_overlay.png b/build-system/tests/overlay3/src/main/res/drawable/free_beta_overlay.png
new file mode 100644
index 0000000..b55e544
--- /dev/null
+++ b/build-system/tests/overlay3/src/main/res/drawable/free_beta_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay3/src/main/res/drawable/free_normal_overlay.png b/build-system/tests/overlay3/src/main/res/drawable/free_normal_overlay.png
new file mode 100644
index 0000000..b55e544
--- /dev/null
+++ b/build-system/tests/overlay3/src/main/res/drawable/free_normal_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay3/src/main/res/drawable/free_overlay.png b/build-system/tests/overlay3/src/main/res/drawable/free_overlay.png
new file mode 100644
index 0000000..b55e544
--- /dev/null
+++ b/build-system/tests/overlay3/src/main/res/drawable/free_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay3/src/main/res/drawable/icon.png b/build-system/tests/overlay3/src/main/res/drawable/icon.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/overlay3/src/main/res/drawable/icon.png
Binary files differ
diff --git a/build-system/tests/overlay3/src/main/res/drawable/no_overlay.png b/build-system/tests/overlay3/src/main/res/drawable/no_overlay.png
new file mode 100644
index 0000000..47e1adf
--- /dev/null
+++ b/build-system/tests/overlay3/src/main/res/drawable/no_overlay.png
Binary files differ
diff --git a/build-system/tests/overlay3/src/main/res/layout/main.xml b/build-system/tests/overlay3/src/main/res/layout/main.xml
new file mode 100644
index 0000000..1046827
--- /dev/null
+++ b/build-system/tests/overlay3/src/main/res/layout/main.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+ <ImageView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:src="@drawable/no_overlay"
+ android:id="@+id/no_overlay" />
+ <ImageView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:src="@drawable/debug_overlay"
+ android:id="@+id/debug_overlay" />
+ <ImageView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:src="@drawable/beta_overlay"
+ android:id="@+id/beta_overlay" />
+ <ImageView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:src="@drawable/free_normal_overlay"
+ android:id="@+id/free_normal_overlay" />
+ <ImageView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:src="@drawable/free_beta_debug_overlay"
+ android:id="@+id/free_beta_debug_overlay" />
+</LinearLayout>
+
diff --git a/build-system/tests/overlay3/src/main/res/values/strings.xml b/build-system/tests/overlay3/src/main/res/values/strings.xml
new file mode 100644
index 0000000..d1420e7
--- /dev/null
+++ b/build-system/tests/overlay3/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">_Test-Overlay2</string>
+</resources>
diff --git a/build-system/tests/pkgOverride/build.gradle b/build-system/tests/pkgOverride/build.gradle
new file mode 100644
index 0000000..96774ee
--- /dev/null
+++ b/build-system/tests/pkgOverride/build.gradle
@@ -0,0 +1,18 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+apply plugin: 'android'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+ defaultConfig {
+ packageName "com.android.tests.basic.foo"
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/pkgOverride/src/instrumentTest/java/com/android/tests/basic/MainTest.java b/build-system/tests/pkgOverride/src/instrumentTest/java/com/android/tests/basic/MainTest.java
new file mode 100644
index 0000000..40e4749
--- /dev/null
+++ b/build-system/tests/pkgOverride/src/instrumentTest/java/com/android/tests/basic/MainTest.java
@@ -0,0 +1,44 @@
+package com.android.tests.basic;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+public class MainTest extends ActivityInstrumentationTestCase2<Main> {
+
+ private TextView mTextView;
+ private int mId;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
+ */
+ public MainTest() {
+ super(Main.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final Main a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+ mTextView = (TextView) a.findViewById(R.id.text);
+ mId = a.mId;
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView);
+ }
+
+ public void testResourceQuery() {
+ assertTrue(mId != 0);
+ }
+}
+
diff --git a/build-system/tests/pkgOverride/src/main/AndroidManifest.xml b/build-system/tests/pkgOverride/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a34d937
--- /dev/null
+++ b/build-system/tests/pkgOverride/src/main/AndroidManifest.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.basic">
+ <application android:label="@string/app_name" android:icon="@drawable/icon">
+ <activity android:name=".Main"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/build-system/tests/pkgOverride/src/main/java/com/android/tests/basic/Main.java b/build-system/tests/pkgOverride/src/main/java/com/android/tests/basic/Main.java
new file mode 100644
index 0000000..0eae98e
--- /dev/null
+++ b/build-system/tests/pkgOverride/src/main/java/com/android/tests/basic/Main.java
@@ -0,0 +1,19 @@
+package com.android.tests.basic;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class Main extends Activity
+{
+ int mId;
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ mId = getResources().getIdentifier("icon", "drawable", getPackageName());
+ }
+}
diff --git a/build-system/tests/pkgOverride/src/main/res/drawable/icon.png b/build-system/tests/pkgOverride/src/main/res/drawable/icon.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/pkgOverride/src/main/res/drawable/icon.png
Binary files differ
diff --git a/build-system/tests/pkgOverride/src/main/res/layout/main.xml b/build-system/tests/pkgOverride/src/main/res/layout/main.xml
new file mode 100644
index 0000000..b199751
--- /dev/null
+++ b/build-system/tests/pkgOverride/src/main/res/layout/main.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+<TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="Test App - Basic"
+ android:id="@+id/text"
+ />
+</LinearLayout>
+
diff --git a/build-system/tests/pkgOverride/src/main/res/values/strings.xml b/build-system/tests/pkgOverride/src/main/res/values/strings.xml
new file mode 100644
index 0000000..60ea2d0
--- /dev/null
+++ b/build-system/tests/pkgOverride/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">_Test-Basic</string>
+</resources>
diff --git a/build-system/tests/proguard/build.gradle b/build-system/tests/proguard/build.gradle
new file mode 100644
index 0000000..3a9d3e7
--- /dev/null
+++ b/build-system/tests/proguard/build.gradle
@@ -0,0 +1,35 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+apply plugin: 'android'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+ testBuildType "proguard"
+
+ defaultConfig {
+ versionCode 12
+ versionName "2.0"
+ minSdkVersion 16
+ targetSdkVersion 16
+ }
+
+ buildTypes {
+ proguard.initWith(buildTypes.debug)
+ proguard {
+ runProguard true
+ proguardFile getDefaultProguardFile('proguard-android.txt')
+ }
+ }
+
+ dexOptions {
+ incremental false
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/proguard/src/instrumentTest/java/com/android/tests/basic/MainTest.java b/build-system/tests/proguard/src/instrumentTest/java/com/android/tests/basic/MainTest.java
new file mode 100644
index 0000000..44bc9f4
--- /dev/null
+++ b/build-system/tests/proguard/src/instrumentTest/java/com/android/tests/basic/MainTest.java
@@ -0,0 +1,49 @@
+package com.android.tests.basic;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+public class MainTest extends ActivityInstrumentationTestCase2<Main> {
+
+ private TextView mTextView;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
+ */
+ public MainTest() {
+ super(Main.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final Main a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+ mTextView = (TextView) a.findViewById(R.id.dateText);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView);
+ }
+
+ public void testTextViewContent() {
+ assertEquals("1234", mTextView.getText());
+ }
+
+ /** Test using a obfuscated class */
+ public void testObfuscatedCode() {
+ final Main a = getActivity();
+ StringProvider sp = a.getStringProvider();
+ assertEquals("42", sp.getString(42));
+ }
+}
+
diff --git a/build-system/tests/proguard/src/main/AndroidManifest.xml b/build-system/tests/proguard/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..4f8d570
--- /dev/null
+++ b/build-system/tests/proguard/src/main/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.basic">
+ <application android:label="@string/app_name" android:icon="@drawable/icon">
+ <activity android:name=".Main"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <uses-permission android:name="com.blah" />
+
+ <permission-group android:name="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+ <permission android:name="foo.permission.SEND_SMS"
+ android:permissionGroup="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+ <permission android:name="foo.blah.SEND_SMS"
+ android:permissionGroup="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+</manifest>
diff --git a/build-system/tests/proguard/src/main/java/com/android/tests/basic/Main.java b/build-system/tests/proguard/src/main/java/com/android/tests/basic/Main.java
new file mode 100644
index 0000000..e32404a
--- /dev/null
+++ b/build-system/tests/proguard/src/main/java/com/android/tests/basic/Main.java
@@ -0,0 +1,26 @@
+package com.android.tests.basic;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+public class Main extends Activity {
+
+ private int foo = 1234;
+
+ private final StringProvider mStringProvider = new StringProvider();
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ TextView tv = (TextView) findViewById(R.id.dateText);
+ tv.setText(getStringProvider().getString(foo));
+ }
+
+ public StringProvider getStringProvider() {
+ return mStringProvider;
+ }
+}
diff --git a/build-system/tests/proguard/src/main/java/com/android/tests/basic/StringProvider.java b/build-system/tests/proguard/src/main/java/com/android/tests/basic/StringProvider.java
new file mode 100644
index 0000000..6659418
--- /dev/null
+++ b/build-system/tests/proguard/src/main/java/com/android/tests/basic/StringProvider.java
@@ -0,0 +1,8 @@
+package com.android.tests.basic;
+
+public class StringProvider {
+
+ public String getString(int foo) {
+ return Integer.toString(foo);
+ }
+}
diff --git a/build-system/tests/proguard/src/main/res/drawable/icon.png b/build-system/tests/proguard/src/main/res/drawable/icon.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/proguard/src/main/res/drawable/icon.png
Binary files differ
diff --git a/build-system/tests/proguard/src/main/res/layout/main.xml b/build-system/tests/proguard/src/main/res/layout/main.xml
new file mode 100644
index 0000000..89ab091
--- /dev/null
+++ b/build-system/tests/proguard/src/main/res/layout/main.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+<TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="Test App - Basic"
+ android:id="@+id/text"
+ />
+<TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text=""
+ android:id="@+id/dateText"
+ />
+</LinearLayout>
+
diff --git a/build-system/tests/proguard/src/main/res/values/strings.xml b/build-system/tests/proguard/src/main/res/values/strings.xml
new file mode 100644
index 0000000..60ea2d0
--- /dev/null
+++ b/build-system/tests/proguard/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">_Test-Basic</string>
+</resources>
diff --git a/build-system/tests/proguardLib/app/build.gradle b/build-system/tests/proguardLib/app/build.gradle
new file mode 100644
index 0000000..2653d36
--- /dev/null
+++ b/build-system/tests/proguardLib/app/build.gradle
@@ -0,0 +1,31 @@
+apply plugin: 'android'
+
+dependencies {
+ compile project(':lib')
+}
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+ testBuildType "proguard"
+
+ defaultConfig {
+ versionCode 12
+ versionName "2.0"
+ minSdkVersion 16
+ targetSdkVersion 16
+ }
+
+ buildTypes {
+ proguard.initWith(buildTypes.debug)
+ proguard {
+ runProguard true
+ proguardFile getDefaultProguardFile('proguard-android.txt')
+ }
+ }
+
+ dexOptions {
+ incremental false
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/proguardLib/app/src/instrumentTest/java/com/android/tests/basic/MainTest.java b/build-system/tests/proguardLib/app/src/instrumentTest/java/com/android/tests/basic/MainTest.java
new file mode 100644
index 0000000..cbbc52b
--- /dev/null
+++ b/build-system/tests/proguardLib/app/src/instrumentTest/java/com/android/tests/basic/MainTest.java
@@ -0,0 +1,42 @@
+package com.android.tests.basic;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+public class MainTest extends ActivityInstrumentationTestCase2<Main> {
+
+ private TextView mTextView;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
+ */
+ public MainTest() {
+ super(Main.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final Main a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+ mTextView = (TextView) a.findViewById(R.id.dateText);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView);
+ }
+
+ public void testTextViewContent() {
+ assertEquals("1234", mTextView.getText());
+ }
+}
+
diff --git a/build-system/tests/proguardLib/app/src/main/AndroidManifest.xml b/build-system/tests/proguardLib/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..4f8d570
--- /dev/null
+++ b/build-system/tests/proguardLib/app/src/main/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.basic">
+ <application android:label="@string/app_name" android:icon="@drawable/icon">
+ <activity android:name=".Main"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <uses-permission android:name="com.blah" />
+
+ <permission-group android:name="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+ <permission android:name="foo.permission.SEND_SMS"
+ android:permissionGroup="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+ <permission android:name="foo.blah.SEND_SMS"
+ android:permissionGroup="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+</manifest>
diff --git a/build-system/tests/proguardLib/app/src/main/java/com/android/tests/basic/Main.java b/build-system/tests/proguardLib/app/src/main/java/com/android/tests/basic/Main.java
new file mode 100644
index 0000000..77edc4c
--- /dev/null
+++ b/build-system/tests/proguardLib/app/src/main/java/com/android/tests/basic/Main.java
@@ -0,0 +1,31 @@
+package com.android.tests.basic;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+import java.lang.reflect.Method;
+
+public class Main extends Activity
+{
+
+ private int foo = 1234;
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ TextView tv = (TextView) findViewById(R.id.dateText);
+
+ try {
+ // use reflection to make sure the class wasn't obfuscated
+ Class<?> theClass = Class.forName("com.android.tests.basic.StringProvider");
+ Method method = theClass.getDeclaredMethod("getString", int.class);
+ tv.setText((String) method.invoke(null, foo));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/build-system/tests/proguardLib/app/src/main/res/drawable/icon.png b/build-system/tests/proguardLib/app/src/main/res/drawable/icon.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/proguardLib/app/src/main/res/drawable/icon.png
Binary files differ
diff --git a/build-system/tests/proguardLib/app/src/main/res/layout/main.xml b/build-system/tests/proguardLib/app/src/main/res/layout/main.xml
new file mode 100644
index 0000000..89ab091
--- /dev/null
+++ b/build-system/tests/proguardLib/app/src/main/res/layout/main.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+<TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="Test App - Basic"
+ android:id="@+id/text"
+ />
+<TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text=""
+ android:id="@+id/dateText"
+ />
+</LinearLayout>
+
diff --git a/build-system/tests/proguardLib/app/src/main/res/values/strings.xml b/build-system/tests/proguardLib/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..60ea2d0
--- /dev/null
+++ b/build-system/tests/proguardLib/app/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">_Test-Basic</string>
+</resources>
diff --git a/build-system/tests/proguardLib/build.gradle b/build-system/tests/proguardLib/build.gradle
new file mode 100644
index 0000000..83b3e0b
--- /dev/null
+++ b/build-system/tests/proguardLib/build.gradle
@@ -0,0 +1,8 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
diff --git a/build-system/tests/proguardLib/lib/build.gradle b/build-system/tests/proguardLib/lib/build.gradle
new file mode 100644
index 0000000..efbffe6
--- /dev/null
+++ b/build-system/tests/proguardLib/lib/build.gradle
@@ -0,0 +1,14 @@
+apply plugin: 'android-library'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+ defaultConfig {
+ versionCode 12
+ versionName "2.0"
+ minSdkVersion 16
+ targetSdkVersion 16
+ consumerProguardFiles 'config.pro'
+ }
+}
diff --git a/build-system/tests/proguardLib/lib/config.pro b/build-system/tests/proguardLib/lib/config.pro
new file mode 100644
index 0000000..3416f5d
--- /dev/null
+++ b/build-system/tests/proguardLib/lib/config.pro
@@ -0,0 +1,4 @@
+-keep public class com.android.tests.basic.StringProvider {
+ public static java.lang.String getString(int);
+}
+
diff --git a/build-system/tests/proguardLib/lib/src/main/AndroidManifest.xml b/build-system/tests/proguardLib/lib/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..593a287
--- /dev/null
+++ b/build-system/tests/proguardLib/lib/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.basic">
+</manifest>
diff --git a/build-system/tests/proguardLib/lib/src/main/java/com/android/tests/basic/StringProvider.java b/build-system/tests/proguardLib/lib/src/main/java/com/android/tests/basic/StringProvider.java
new file mode 100644
index 0000000..6d81901
--- /dev/null
+++ b/build-system/tests/proguardLib/lib/src/main/java/com/android/tests/basic/StringProvider.java
@@ -0,0 +1,8 @@
+package com.android.tests.basic;
+
+public class StringProvider {
+
+ public static String getString(int foo) {
+ return Integer.toString(foo);
+ }
+}
diff --git a/build-system/tests/proguardLib/settings.gradle b/build-system/tests/proguardLib/settings.gradle
new file mode 100644
index 0000000..eedb2a1
--- /dev/null
+++ b/build-system/tests/proguardLib/settings.gradle
@@ -0,0 +1,2 @@
+include 'app'
+include 'lib'
diff --git a/build-system/tests/renamedApk/build.gradle b/build-system/tests/renamedApk/build.gradle
new file mode 100644
index 0000000..9600a82
--- /dev/null
+++ b/build-system/tests/renamedApk/build.gradle
@@ -0,0 +1,26 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+apply plugin: 'android'
+
+repositories {
+ mavenCentral()
+}
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+ buildTypes.debug {
+ zipAlign true
+ }
+}
+
+android.applicationVariants.all { variant ->
+ variant.outputFile = file("$project.buildDir/${variant.name}.apk")
+}
\ No newline at end of file
diff --git a/build-system/tests/renamedApk/src/instrumentTest/java/com/android/tests/basic/MainTest.java b/build-system/tests/renamedApk/src/instrumentTest/java/com/android/tests/basic/MainTest.java
new file mode 100644
index 0000000..7cf7329
--- /dev/null
+++ b/build-system/tests/renamedApk/src/instrumentTest/java/com/android/tests/basic/MainTest.java
@@ -0,0 +1,38 @@
+package com.android.tests.basic;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+public class MainTest extends ActivityInstrumentationTestCase2<Main> {
+
+ private TextView mTextView;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
+ */
+ public MainTest() {
+ super(Main.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final Main a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+ mTextView = (TextView) a.findViewById(R.id.text);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView);
+ }
+}
+
diff --git a/build-system/tests/renamedApk/src/main/AndroidManifest.xml b/build-system/tests/renamedApk/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..4f8d570
--- /dev/null
+++ b/build-system/tests/renamedApk/src/main/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.basic">
+ <application android:label="@string/app_name" android:icon="@drawable/icon">
+ <activity android:name=".Main"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <uses-permission android:name="com.blah" />
+
+ <permission-group android:name="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+ <permission android:name="foo.permission.SEND_SMS"
+ android:permissionGroup="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+ <permission android:name="foo.blah.SEND_SMS"
+ android:permissionGroup="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+</manifest>
diff --git a/build-system/tests/renamedApk/src/main/java/com/android/tests/basic/Main.java b/build-system/tests/renamedApk/src/main/java/com/android/tests/basic/Main.java
new file mode 100644
index 0000000..2b0e698
--- /dev/null
+++ b/build-system/tests/renamedApk/src/main/java/com/android/tests/basic/Main.java
@@ -0,0 +1,15 @@
+package com.android.tests.basic;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class Main extends Activity
+{
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ }
+}
diff --git a/build-system/tests/renamedApk/src/main/res/drawable/icon.png b/build-system/tests/renamedApk/src/main/res/drawable/icon.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/renamedApk/src/main/res/drawable/icon.png
Binary files differ
diff --git a/build-system/tests/renamedApk/src/main/res/layout/main.xml b/build-system/tests/renamedApk/src/main/res/layout/main.xml
new file mode 100644
index 0000000..b199751
--- /dev/null
+++ b/build-system/tests/renamedApk/src/main/res/layout/main.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+<TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="Test App - Basic"
+ android:id="@+id/text"
+ />
+</LinearLayout>
+
diff --git a/build-system/tests/renamedApk/src/main/res/values/strings.xml b/build-system/tests/renamedApk/src/main/res/values/strings.xml
new file mode 100644
index 0000000..60ea2d0
--- /dev/null
+++ b/build-system/tests/renamedApk/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">_Test-Basic</string>
+</resources>
diff --git a/build-system/tests/renamedApk/src/release/res/values/strings.xml b/build-system/tests/renamedApk/src/release/res/values/strings.xml
new file mode 100644
index 0000000..532909c
--- /dev/null
+++ b/build-system/tests/renamedApk/src/release/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">_Test-Basic-Release</string>
+</resources>
diff --git a/build-system/tests/renderscript/build.gradle b/build-system/tests/renderscript/build.gradle
new file mode 100644
index 0000000..8a15397
--- /dev/null
+++ b/build-system/tests/renderscript/build.gradle
@@ -0,0 +1,18 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+apply plugin: 'android'
+
+android {
+ compileSdkVersion 17
+ buildToolsVersion "18.0.1"
+
+ defaultConfig {
+ renderscriptTargetApi = 17
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/renderscript/src/main/AndroidManifest.xml b/build-system/tests/renderscript/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..73e1110
--- /dev/null
+++ b/build-system/tests/renderscript/src/main/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.rs.hellocompute">
+
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-sdk android:minSdkVersion="14" />
+ <application android:label="RsHelloCompute">
+ <activity android:name="HelloCompute">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/build-system/tests/renderscript/src/main/java/com/example/android/rs/hellocompute/HelloCompute.java b/build-system/tests/renderscript/src/main/java/com/example/android/rs/hellocompute/HelloCompute.java
new file mode 100644
index 0000000..0d6c47b
--- /dev/null
+++ b/build-system/tests/renderscript/src/main/java/com/example/android/rs/hellocompute/HelloCompute.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.rs.hellocompute;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.graphics.BitmapFactory;
+import android.graphics.Bitmap;
+import android.renderscript.RenderScript;
+import android.renderscript.Allocation;
+import android.widget.ImageView;
+
+public class HelloCompute extends Activity {
+ private Bitmap mBitmapIn;
+ private Bitmap mBitmapOut;
+
+ private RenderScript mRS;
+ private Allocation mInAllocation;
+ private Allocation mOutAllocation;
+ private ScriptC_mono mScript;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ mBitmapIn = loadBitmap(R.drawable.data);
+ mBitmapOut = Bitmap.createBitmap(mBitmapIn.getWidth(), mBitmapIn.getHeight(),
+ mBitmapIn.getConfig());
+
+ ImageView in = (ImageView) findViewById(R.id.displayin);
+ in.setImageBitmap(mBitmapIn);
+
+ ImageView out = (ImageView) findViewById(R.id.displayout);
+ out.setImageBitmap(mBitmapOut);
+
+ createScript();
+ }
+
+
+ private void createScript() {
+ mRS = RenderScript.create(this);
+
+ mInAllocation = Allocation.createFromBitmap(mRS, mBitmapIn,
+ Allocation.MipmapControl.MIPMAP_NONE,
+ Allocation.USAGE_SCRIPT);
+ mOutAllocation = Allocation.createTyped(mRS, mInAllocation.getType());
+
+ mScript = new ScriptC_mono(mRS, getResources(), R.raw.mono);
+
+ mScript.forEach_root(mInAllocation, mOutAllocation);
+ mOutAllocation.copyTo(mBitmapOut);
+ }
+
+ private Bitmap loadBitmap(int resource) {
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+ return BitmapFactory.decodeResource(getResources(), resource, options);
+ }
+}
diff --git a/build-system/tests/renderscript/src/main/res/drawable/data.jpg b/build-system/tests/renderscript/src/main/res/drawable/data.jpg
new file mode 100644
index 0000000..81a87b1
--- /dev/null
+++ b/build-system/tests/renderscript/src/main/res/drawable/data.jpg
Binary files differ
diff --git a/build-system/tests/renderscript/src/main/res/layout/main.xml b/build-system/tests/renderscript/src/main/res/layout/main.xml
new file mode 100644
index 0000000..3f7de43
--- /dev/null
+++ b/build-system/tests/renderscript/src/main/res/layout/main.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ImageView
+ android:id="@+id/displayin"
+ android:layout_width="320dip"
+ android:layout_height="266dip" />
+
+ <ImageView
+ android:id="@+id/displayout"
+ android:layout_width="320dip"
+ android:layout_height="266dip" />
+
+</LinearLayout>
diff --git a/build-system/tests/renderscript/src/main/rs/com/example/android/rs/hellocompute/mono.rs b/build-system/tests/renderscript/src/main/rs/com/example/android/rs/hellocompute/mono.rs
new file mode 100644
index 0000000..08592f5
--- /dev/null
+++ b/build-system/tests/renderscript/src/main/rs/com/example/android/rs/hellocompute/mono.rs
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma version(1)
+#pragma rs java_package_name(com.example.android.rs.hellocompute)
+
+const static float3 gMonoMult = {0.299f, 0.587f, 0.114f};
+
+void root(const uchar4 *v_in, uchar4 *v_out) {
+ float4 f4 = rsUnpackColor8888(*v_in);
+
+ float3 mono = dot(f4.rgb, gMonoMult);
+ *v_out = rsPackColorTo8888(mono);
+}
+
diff --git a/build-system/tests/renderscriptInLib/app/build.gradle b/build-system/tests/renderscriptInLib/app/build.gradle
new file mode 100644
index 0000000..c028ad5
--- /dev/null
+++ b/build-system/tests/renderscriptInLib/app/build.gradle
@@ -0,0 +1,14 @@
+apply plugin: 'android'
+
+android {
+ compileSdkVersion 17
+ buildToolsVersion "18.0.1"
+
+ defaultConfig {
+ renderscriptTargetApi = 11
+ }
+}
+
+dependencies {
+ compile project(':lib')
+}
diff --git a/build-system/tests/renderscriptInLib/app/src/main/AndroidManifest.xml b/build-system/tests/renderscriptInLib/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..c0ae2ff
--- /dev/null
+++ b/build-system/tests/renderscriptInLib/app/src/main/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.rs.balls">
+ <uses-sdk android:minSdkVersion="11" />
+ <application
+ android:label="RsBalls"
+ android:icon="@drawable/test_pattern">
+ <activity android:name="Balls"
+ android:screenOrientation="landscape">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/build-system/tests/renderscriptInLib/app/src/main/java/com/example/android/rs/balls/Balls.java b/build-system/tests/renderscriptInLib/app/src/main/java/com/example/android/rs/balls/Balls.java
new file mode 100644
index 0000000..d3b900a
--- /dev/null
+++ b/build-system/tests/renderscriptInLib/app/src/main/java/com/example/android/rs/balls/Balls.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.rs.balls;
+
+import android.renderscript.RSSurfaceView;
+import android.renderscript.RenderScript;
+
+import android.app.Activity;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.provider.Settings.System;
+import android.util.Config;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.Window;
+import android.widget.Button;
+import android.widget.ListView;
+
+import java.lang.Runtime;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+
+public class Balls extends Activity implements SensorEventListener {
+ //EventListener mListener = new EventListener();
+
+ private static final String LOG_TAG = "libRS_jni";
+ private static final boolean DEBUG = false;
+ private static final boolean LOG_ENABLED = DEBUG ? Config.LOGD : Config.LOGV;
+
+ private BallsView mView;
+ private SensorManager mSensorManager;
+
+ // get the current looper (from your Activity UI thread for instance
+
+
+ public void onSensorChanged(SensorEvent event) {
+ //android.util.Log.d("rs", "sensor: " + event.sensor + ", x: " + event.values[0] + ", y: " + event.values[1] + ", z: " + event.values[2]);
+ synchronized (this) {
+ if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
+ if(mView != null) {
+ mView.setAccel(event.values[0], event.values[1], event.values[2]);
+ }
+ }
+ }
+ }
+
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
+
+ // Create our Preview view and set it as the content of our
+ // Activity
+ mView = new BallsView(this);
+ setContentView(mView);
+ }
+
+ @Override
+ protected void onResume() {
+ mSensorManager.registerListener(this,
+ mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
+ SensorManager.SENSOR_DELAY_FASTEST);
+
+ // Ideally a game should implement onResume() and onPause()
+ // to take appropriate action when the activity looses focus
+ super.onResume();
+ mView.resume();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mView.pause();
+ Runtime.getRuntime().exit(0);
+ }
+
+ @Override
+ protected void onStop() {
+ mSensorManager.unregisterListener(this);
+ super.onStop();
+ }
+
+ static void log(String message) {
+ if (LOG_ENABLED) {
+ Log.v(LOG_TAG, message);
+ }
+ }
+
+
+}
+
diff --git a/build-system/tests/renderscriptInLib/app/src/main/java/com/example/android/rs/balls/BallsRS.java b/build-system/tests/renderscriptInLib/app/src/main/java/com/example/android/rs/balls/BallsRS.java
new file mode 100644
index 0000000..8cab9b8
--- /dev/null
+++ b/build-system/tests/renderscriptInLib/app/src/main/java/com/example/android/rs/balls/BallsRS.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.rs.balls;
+
+import android.content.res.Resources;
+import android.renderscript.*;
+import android.util.Log;
+
+public class BallsRS {
+ public static final int PART_COUNT = 900;
+
+ public BallsRS() {
+ }
+
+ private Resources mRes;
+ private RenderScriptGL mRS;
+ private ScriptC_balls mScript;
+ private ScriptC_ball_physics mPhysicsScript;
+ private ProgramFragment mPFLines;
+ private ProgramFragment mPFPoints;
+ private ProgramVertex mPV;
+ private ScriptField_Point mPoints;
+ private ScriptField_VpConsts mVpConsts;
+
+ void updateProjectionMatrices() {
+ mVpConsts = new ScriptField_VpConsts(mRS, 1,
+ Allocation.USAGE_SCRIPT |
+ Allocation.USAGE_GRAPHICS_CONSTANTS);
+ ScriptField_VpConsts.Item i = new ScriptField_VpConsts.Item();
+ Matrix4f mvp = new Matrix4f();
+ mvp.loadOrtho(0, mRS.getWidth(), mRS.getHeight(), 0, -1, 1);
+ i.MVP = mvp;
+ mVpConsts.set(i, 0, true);
+ }
+
+ private void createProgramVertex() {
+ updateProjectionMatrices();
+
+ ProgramVertex.Builder sb = new ProgramVertex.Builder(mRS);
+ String t = "varying vec4 varColor;\n" +
+ "void main() {\n" +
+ " vec4 pos = vec4(0.0, 0.0, 0.0, 1.0);\n" +
+ " pos.xy = ATTRIB_position;\n" +
+ " gl_Position = UNI_MVP * pos;\n" +
+ " varColor = vec4(1.0, 1.0, 1.0, 1.0);\n" +
+ " gl_PointSize = ATTRIB_size;\n" +
+ "}\n";
+ sb.setShader(t);
+ sb.addConstant(mVpConsts.getType());
+ sb.addInput(mPoints.getElement());
+ ProgramVertex pvs = sb.create();
+ pvs.bindConstants(mVpConsts.getAllocation(), 0);
+ mRS.bindProgramVertex(pvs);
+ }
+
+ private Allocation loadTexture(int id) {
+ final Allocation allocation =
+ Allocation.createFromBitmapResource(mRS, mRes,
+ id, Allocation.MipmapControl.MIPMAP_NONE,
+ Allocation.USAGE_GRAPHICS_TEXTURE);
+ return allocation;
+ }
+
+ ProgramStore BLEND_ADD_DEPTH_NONE(RenderScript rs) {
+ ProgramStore.Builder builder = new ProgramStore.Builder(rs);
+ builder.setDepthFunc(ProgramStore.DepthFunc.ALWAYS);
+ builder.setBlendFunc(ProgramStore.BlendSrcFunc.ONE, ProgramStore.BlendDstFunc.ONE);
+ builder.setDitherEnabled(false);
+ builder.setDepthMaskEnabled(false);
+ return builder.create();
+ }
+
+ public void init(RenderScriptGL rs, Resources res, int width, int height) {
+ mRS = rs;
+ mRes = res;
+
+ ProgramFragmentFixedFunction.Builder pfb = new ProgramFragmentFixedFunction.Builder(rs);
+ pfb.setPointSpriteTexCoordinateReplacement(true);
+ pfb.setTexture(ProgramFragmentFixedFunction.Builder.EnvMode.MODULATE,
+ ProgramFragmentFixedFunction.Builder.Format.RGBA, 0);
+ pfb.setVaryingColor(true);
+ mPFPoints = pfb.create();
+
+ pfb = new ProgramFragmentFixedFunction.Builder(rs);
+ pfb.setVaryingColor(true);
+ mPFLines = pfb.create();
+
+ android.util.Log.e("rs", "Load texture");
+ mPFPoints.bindTexture(loadTexture(R.drawable.flares), 0);
+
+ mPoints = new ScriptField_Point(mRS, PART_COUNT, Allocation.USAGE_SCRIPT);
+
+ Mesh.AllocationBuilder smb = new Mesh.AllocationBuilder(mRS);
+ smb.addVertexAllocation(mPoints.getAllocation());
+ smb.addIndexSetType(Mesh.Primitive.POINT);
+ Mesh smP = smb.create();
+
+ mPhysicsScript = new ScriptC_ball_physics(mRS, mRes, R.raw.ball_physics);
+
+ mScript = new ScriptC_balls(mRS, mRes, R.raw.balls);
+ mScript.set_partMesh(smP);
+ mScript.set_physics_script(mPhysicsScript);
+ mScript.bind_point(mPoints);
+ mScript.bind_balls1(new ScriptField_Ball(mRS, PART_COUNT, Allocation.USAGE_SCRIPT));
+ mScript.bind_balls2(new ScriptField_Ball(mRS, PART_COUNT, Allocation.USAGE_SCRIPT));
+
+ mScript.set_gPFLines(mPFLines);
+ mScript.set_gPFPoints(mPFPoints);
+ createProgramVertex();
+
+ mRS.bindProgramStore(BLEND_ADD_DEPTH_NONE(mRS));
+
+ mPhysicsScript.set_gMinPos(new Float2(5, 5));
+ mPhysicsScript.set_gMaxPos(new Float2(width - 5, height - 5));
+
+ mScript.invoke_initParts(width, height);
+
+ mRS.bindRootScript(mScript);
+ }
+
+ public void newTouchPosition(float x, float y, float pressure, int id) {
+ mPhysicsScript.invoke_touch(x, y, pressure, id);
+ }
+
+ public void setAccel(float x, float y) {
+ mPhysicsScript.set_gGravityVector(new Float2(x, y));
+ }
+
+}
diff --git a/build-system/tests/renderscriptInLib/app/src/main/java/com/example/android/rs/balls/BallsView.java b/build-system/tests/renderscriptInLib/app/src/main/java/com/example/android/rs/balls/BallsView.java
new file mode 100644
index 0000000..b3b3756
--- /dev/null
+++ b/build-system/tests/renderscriptInLib/app/src/main/java/com/example/android/rs/balls/BallsView.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.rs.balls;
+
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.concurrent.Semaphore;
+
+import android.renderscript.RSSurfaceView;
+import android.renderscript.RenderScript;
+import android.renderscript.RenderScriptGL;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+public class BallsView extends RSSurfaceView {
+
+ public BallsView(Context context) {
+ super(context);
+ //setFocusable(true);
+ }
+
+ private RenderScriptGL mRS;
+ private BallsRS mRender;
+
+ public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
+ super.surfaceChanged(holder, format, w, h);
+ if (mRS == null) {
+ RenderScriptGL.SurfaceConfig sc = new RenderScriptGL.SurfaceConfig();
+ mRS = createRenderScriptGL(sc);
+ mRS.setSurface(holder, w, h);
+ mRender = new BallsRS();
+ mRender.init(mRS, getResources(), w, h);
+ }
+ mRender.updateProjectionMatrices();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ if(mRS != null) {
+ mRS = null;
+ destroyRenderScriptGL();
+ }
+ }
+
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev)
+ {
+ int act = ev.getActionMasked();
+ if (act == ev.ACTION_UP) {
+ mRender.newTouchPosition(0, 0, 0, ev.getPointerId(0));
+ return false;
+ } else if (act == MotionEvent.ACTION_POINTER_UP) {
+ // only one pointer going up, we can get the index like this
+ int pointerIndex = ev.getActionIndex();
+ int pointerId = ev.getPointerId(pointerIndex);
+ mRender.newTouchPosition(0, 0, 0, pointerId);
+ return false;
+ }
+ int count = ev.getHistorySize();
+ int pcount = ev.getPointerCount();
+
+ for (int p=0; p < pcount; p++) {
+ int id = ev.getPointerId(p);
+ mRender.newTouchPosition(ev.getX(p),
+ ev.getY(p),
+ ev.getPressure(p),
+ id);
+
+ for (int i=0; i < count; i++) {
+ mRender.newTouchPosition(ev.getHistoricalX(p, i),
+ ev.getHistoricalY(p, i),
+ ev.getHistoricalPressure(p, i),
+ id);
+ }
+ }
+ return true;
+ }
+
+ void setAccel(float x, float y, float z) {
+ if (mRender == null) {
+ return;
+ }
+ mRender.setAccel(x, -y);
+ }
+
+}
+
+
diff --git a/build-system/tests/renderscriptInLib/app/src/main/res/drawable/flares.png b/build-system/tests/renderscriptInLib/app/src/main/res/drawable/flares.png
new file mode 100644
index 0000000..3a5c970
--- /dev/null
+++ b/build-system/tests/renderscriptInLib/app/src/main/res/drawable/flares.png
Binary files differ
diff --git a/build-system/tests/renderscriptInLib/app/src/main/res/drawable/test_pattern.png b/build-system/tests/renderscriptInLib/app/src/main/res/drawable/test_pattern.png
new file mode 100644
index 0000000..e7d1455
--- /dev/null
+++ b/build-system/tests/renderscriptInLib/app/src/main/res/drawable/test_pattern.png
Binary files differ
diff --git a/build-system/tests/renderscriptInLib/app/src/main/rs/com/example/android/rs/balls/ball_physics.rs b/build-system/tests/renderscriptInLib/app/src/main/rs/com/example/android/rs/balls/ball_physics.rs
new file mode 100644
index 0000000..ee6ab1d
--- /dev/null
+++ b/build-system/tests/renderscriptInLib/app/src/main/rs/com/example/android/rs/balls/ball_physics.rs
@@ -0,0 +1,146 @@
+#pragma version(1)
+#pragma rs java_package_name(com.example.android.rs.balls)
+
+#include "balls.rsh"
+
+float2 gGravityVector = {0.f, 9.8f};
+
+float2 gMinPos = {0.f, 0.f};
+float2 gMaxPos = {1280.f, 700.f};
+
+static float2 touchPos[10];
+static float touchPressure[10];
+
+void touch(float x, float y, float pressure, int id) {
+ if (id >= 10) {
+ return;
+ }
+
+ touchPos[id].x = x;
+ touchPos[id].y = y;
+ touchPressure[id] = pressure;
+}
+
+void root(const Ball_t *ballIn, Ball_t *ballOut, const BallControl_t *ctl, uint32_t x) {
+ float2 fv = {0, 0};
+ float2 pos = ballIn->position;
+
+ int arcID = -1;
+ float arcInvStr = 100000;
+
+ const Ball_t * bPtr = rsGetElementAt(ctl->ain, 0);
+ for (uint32_t xin = 0; xin < ctl->dimX; xin++) {
+ float2 vec = bPtr[xin].position - pos;
+ float2 vec2 = vec * vec;
+ float len2 = vec2.x + vec2.y;
+
+ if (len2 < 10000) {
+ //float minDist = ballIn->size + bPtr[xin].size;
+ float forceScale = ballIn->size * bPtr[xin].size;
+ forceScale *= forceScale;
+
+ if (len2 > 16 /* (minDist*minDist)*/) {
+ // Repulsion
+ float len = sqrt(len2);
+ fv -= (vec / (len * len * len)) * 20000.f * forceScale;
+ } else {
+ if (len2 < 1) {
+ if (xin == x) {
+ continue;
+ }
+ ballOut->delta = 0.f;
+ ballOut->position = ballIn->position;
+ if (xin > x) {
+ ballOut->position.x += 1.f;
+ } else {
+ ballOut->position.x -= 1.f;
+ }
+ //ballOut->color.rgb = 1.f;
+ //ballOut->arcID = -1;
+ //ballOut->arcStr = 0;
+ return;
+ }
+ // Collision
+ float2 axis = normalize(vec);
+ float e1 = dot(axis, ballIn->delta);
+ float e2 = dot(axis, bPtr[xin].delta);
+ float e = (e1 - e2) * 0.45f;
+ if (e1 > 0) {
+ fv -= axis * e;
+ } else {
+ fv += axis * e;
+ }
+ }
+ }
+ }
+
+ fv /= ballIn->size * ballIn->size * ballIn->size;
+ fv -= gGravityVector * 4.f;
+ fv *= ctl->dt;
+
+ for (int i=0; i < 10; i++) {
+ if (touchPressure[i] > 0.1f) {
+ float2 vec = touchPos[i] - ballIn->position;
+ float2 vec2 = vec * vec;
+ float len2 = max(2.f, vec2.x + vec2.y);
+ fv -= (vec / len2) * touchPressure[i] * 300.f;
+ }
+ }
+
+ ballOut->delta = (ballIn->delta * (1.f - 0.004f)) + fv;
+ ballOut->position = ballIn->position + (ballOut->delta * ctl->dt);
+
+ const float wallForce = 400.f;
+ if (ballOut->position.x > (gMaxPos.x - 20.f)) {
+ float d = gMaxPos.x - ballOut->position.x;
+ if (d < 0.f) {
+ if (ballOut->delta.x > 0) {
+ ballOut->delta.x *= -0.7;
+ }
+ ballOut->position.x = gMaxPos.x;
+ } else {
+ ballOut->delta.x -= min(wallForce / (d * d), 10.f);
+ }
+ }
+
+ if (ballOut->position.x < (gMinPos.x + 20.f)) {
+ float d = ballOut->position.x - gMinPos.x;
+ if (d < 0.f) {
+ if (ballOut->delta.x < 0) {
+ ballOut->delta.x *= -0.7;
+ }
+ ballOut->position.x = gMinPos.x + 1.f;
+ } else {
+ ballOut->delta.x += min(wallForce / (d * d), 10.f);
+ }
+ }
+
+ if (ballOut->position.y > (gMaxPos.y - 20.f)) {
+ float d = gMaxPos.y - ballOut->position.y;
+ if (d < 0.f) {
+ if (ballOut->delta.y > 0) {
+ ballOut->delta.y *= -0.7;
+ }
+ ballOut->position.y = gMaxPos.y;
+ } else {
+ ballOut->delta.y -= min(wallForce / (d * d), 10.f);
+ }
+ }
+
+ if (ballOut->position.y < (gMinPos.y + 20.f)) {
+ float d = ballOut->position.y - gMinPos.y;
+ if (d < 0.f) {
+ if (ballOut->delta.y < 0) {
+ ballOut->delta.y *= -0.7;
+ }
+ ballOut->position.y = gMinPos.y + 1.f;
+ } else {
+ ballOut->delta.y += min(wallForce / (d * d * d), 10.f);
+ }
+ }
+
+ ballOut->size = ballIn->size;
+
+ //rsDebug("physics pos out", ballOut->position);
+}
+
diff --git a/build-system/tests/renderscriptInLib/app/src/main/rs/com/example/android/rs/balls/balls.rs b/build-system/tests/renderscriptInLib/app/src/main/rs/com/example/android/rs/balls/balls.rs
new file mode 100644
index 0000000..d86b804
--- /dev/null
+++ b/build-system/tests/renderscriptInLib/app/src/main/rs/com/example/android/rs/balls/balls.rs
@@ -0,0 +1,85 @@
+#pragma version(1)
+#pragma rs java_package_name(com.example.android.rs.balls)
+#include "rs_graphics.rsh"
+
+#include "balls.rsh"
+
+#pragma stateVertex(parent)
+#pragma stateStore(parent)
+
+rs_program_fragment gPFPoints;
+rs_program_fragment gPFLines;
+rs_mesh partMesh;
+
+typedef struct __attribute__((packed, aligned(4))) Point {
+ float2 position;
+ float size;
+} Point_t;
+Point_t *point;
+
+typedef struct VpConsts {
+ rs_matrix4x4 MVP;
+} VpConsts_t;
+VpConsts_t *vpConstants;
+
+rs_script physics_script;
+
+Ball_t *balls1;
+Ball_t *balls2;
+
+static int frame = 0;
+
+void initParts(int w, int h)
+{
+ uint32_t dimX = rsAllocationGetDimX(rsGetAllocation(balls1));
+
+ for (uint32_t ct=0; ct < dimX; ct++) {
+ balls1[ct].position.x = rsRand(0.f, (float)w);
+ balls1[ct].position.y = rsRand(0.f, (float)h);
+ balls1[ct].delta.x = 0.f;
+ balls1[ct].delta.y = 0.f;
+ balls1[ct].size = 1.f;
+
+ float r = rsRand(100.f);
+ if (r > 90.f) {
+ balls1[ct].size += pow(10.f, rsRand(0.f, 2.f)) * 0.07;
+ }
+ }
+}
+
+
+
+int root() {
+ rsgClearColor(0.f, 0.f, 0.f, 1.f);
+
+ BallControl_t bc;
+ Ball_t *bout;
+
+ if (frame & 1) {
+ rsSetObject(&bc.ain, rsGetAllocation(balls2));
+ rsSetObject(&bc.aout, rsGetAllocation(balls1));
+ bout = balls2;
+ } else {
+ rsSetObject(&bc.ain, rsGetAllocation(balls1));
+ rsSetObject(&bc.aout, rsGetAllocation(balls2));
+ bout = balls1;
+ }
+
+ bc.dimX = rsAllocationGetDimX(bc.ain);
+ bc.dt = 1.f / 30.f;
+
+ rsForEach(physics_script, bc.ain, bc.aout, &bc);
+
+ for (uint32_t ct=0; ct < bc.dimX; ct++) {
+ point[ct].position = bout[ct].position;
+ point[ct].size = 6.f /*+ bout[ct].color.g * 6.f*/ * bout[ct].size;
+ }
+
+ frame++;
+ rsgBindProgramFragment(gPFPoints);
+ rsgDrawMesh(partMesh);
+ rsClearObject(&bc.ain);
+ rsClearObject(&bc.aout);
+ return 1;
+}
+
diff --git a/build-system/tests/renderscriptInLib/build.gradle b/build-system/tests/renderscriptInLib/build.gradle
new file mode 100644
index 0000000..83b3e0b
--- /dev/null
+++ b/build-system/tests/renderscriptInLib/build.gradle
@@ -0,0 +1,8 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
diff --git a/build-system/tests/renderscriptInLib/lib/build.gradle b/build-system/tests/renderscriptInLib/lib/build.gradle
new file mode 100644
index 0000000..1c875e4
--- /dev/null
+++ b/build-system/tests/renderscriptInLib/lib/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'android-library'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
diff --git a/build-system/tests/renderscriptInLib/lib/src/main/AndroidManifest.xml b/build-system/tests/renderscriptInLib/lib/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..469aac1
--- /dev/null
+++ b/build-system/tests/renderscriptInLib/lib/src/main/AndroidManifest.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.rs.balls.lib">
+ <uses-sdk android:minSdkVersion="11" />
+</manifest>
diff --git a/build-system/tests/renderscriptInLib/lib/src/main/rs/com/example/android/rs/balls/balls.rsh b/build-system/tests/renderscriptInLib/lib/src/main/rs/com/example/android/rs/balls/balls.rsh
new file mode 100644
index 0000000..fc886f9
--- /dev/null
+++ b/build-system/tests/renderscriptInLib/lib/src/main/rs/com/example/android/rs/balls/balls.rsh
@@ -0,0 +1,18 @@
+
+typedef struct __attribute__((packed, aligned(4))) Ball {
+ float2 delta;
+ float2 position;
+ //float3 color;
+ float size;
+ //int arcID;
+ //float arcStr;
+} Ball_t;
+Ball_t *balls;
+
+
+typedef struct BallControl {
+ uint32_t dimX;
+ rs_allocation ain;
+ rs_allocation aout;
+ float dt;
+} BallControl_t;
diff --git a/build-system/tests/renderscriptInLib/settings.gradle b/build-system/tests/renderscriptInLib/settings.gradle
new file mode 100644
index 0000000..5ed7972
--- /dev/null
+++ b/build-system/tests/renderscriptInLib/settings.gradle
@@ -0,0 +1,2 @@
+include 'app'
+include 'lib'
\ No newline at end of file
diff --git a/build-system/tests/renderscriptMultiSrc/build.gradle b/build-system/tests/renderscriptMultiSrc/build.gradle
new file mode 100644
index 0000000..6f15777
--- /dev/null
+++ b/build-system/tests/renderscriptMultiSrc/build.gradle
@@ -0,0 +1,18 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+apply plugin: 'android'
+
+android {
+ compileSdkVersion 17
+ buildToolsVersion "18.0.1"
+
+ defaultConfig {
+ renderscriptTargetApi = 11
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/renderscriptMultiSrc/src/debug/rs/com/example/android/rs/balls/balls.rsh b/build-system/tests/renderscriptMultiSrc/src/debug/rs/com/example/android/rs/balls/balls.rsh
new file mode 100644
index 0000000..fc886f9
--- /dev/null
+++ b/build-system/tests/renderscriptMultiSrc/src/debug/rs/com/example/android/rs/balls/balls.rsh
@@ -0,0 +1,18 @@
+
+typedef struct __attribute__((packed, aligned(4))) Ball {
+ float2 delta;
+ float2 position;
+ //float3 color;
+ float size;
+ //int arcID;
+ //float arcStr;
+} Ball_t;
+Ball_t *balls;
+
+
+typedef struct BallControl {
+ uint32_t dimX;
+ rs_allocation ain;
+ rs_allocation aout;
+ float dt;
+} BallControl_t;
diff --git a/build-system/tests/renderscriptMultiSrc/src/main/AndroidManifest.xml b/build-system/tests/renderscriptMultiSrc/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..c0ae2ff
--- /dev/null
+++ b/build-system/tests/renderscriptMultiSrc/src/main/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.rs.balls">
+ <uses-sdk android:minSdkVersion="11" />
+ <application
+ android:label="RsBalls"
+ android:icon="@drawable/test_pattern">
+ <activity android:name="Balls"
+ android:screenOrientation="landscape">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/build-system/tests/renderscriptMultiSrc/src/main/java/com/example/android/rs/balls/Balls.java b/build-system/tests/renderscriptMultiSrc/src/main/java/com/example/android/rs/balls/Balls.java
new file mode 100644
index 0000000..d3b900a
--- /dev/null
+++ b/build-system/tests/renderscriptMultiSrc/src/main/java/com/example/android/rs/balls/Balls.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.rs.balls;
+
+import android.renderscript.RSSurfaceView;
+import android.renderscript.RenderScript;
+
+import android.app.Activity;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.provider.Settings.System;
+import android.util.Config;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.Window;
+import android.widget.Button;
+import android.widget.ListView;
+
+import java.lang.Runtime;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+
+public class Balls extends Activity implements SensorEventListener {
+ //EventListener mListener = new EventListener();
+
+ private static final String LOG_TAG = "libRS_jni";
+ private static final boolean DEBUG = false;
+ private static final boolean LOG_ENABLED = DEBUG ? Config.LOGD : Config.LOGV;
+
+ private BallsView mView;
+ private SensorManager mSensorManager;
+
+ // get the current looper (from your Activity UI thread for instance
+
+
+ public void onSensorChanged(SensorEvent event) {
+ //android.util.Log.d("rs", "sensor: " + event.sensor + ", x: " + event.values[0] + ", y: " + event.values[1] + ", z: " + event.values[2]);
+ synchronized (this) {
+ if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
+ if(mView != null) {
+ mView.setAccel(event.values[0], event.values[1], event.values[2]);
+ }
+ }
+ }
+ }
+
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
+
+ // Create our Preview view and set it as the content of our
+ // Activity
+ mView = new BallsView(this);
+ setContentView(mView);
+ }
+
+ @Override
+ protected void onResume() {
+ mSensorManager.registerListener(this,
+ mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
+ SensorManager.SENSOR_DELAY_FASTEST);
+
+ // Ideally a game should implement onResume() and onPause()
+ // to take appropriate action when the activity looses focus
+ super.onResume();
+ mView.resume();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mView.pause();
+ Runtime.getRuntime().exit(0);
+ }
+
+ @Override
+ protected void onStop() {
+ mSensorManager.unregisterListener(this);
+ super.onStop();
+ }
+
+ static void log(String message) {
+ if (LOG_ENABLED) {
+ Log.v(LOG_TAG, message);
+ }
+ }
+
+
+}
+
diff --git a/build-system/tests/renderscriptMultiSrc/src/main/java/com/example/android/rs/balls/BallsRS.java b/build-system/tests/renderscriptMultiSrc/src/main/java/com/example/android/rs/balls/BallsRS.java
new file mode 100644
index 0000000..8cab9b8
--- /dev/null
+++ b/build-system/tests/renderscriptMultiSrc/src/main/java/com/example/android/rs/balls/BallsRS.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.rs.balls;
+
+import android.content.res.Resources;
+import android.renderscript.*;
+import android.util.Log;
+
+public class BallsRS {
+ public static final int PART_COUNT = 900;
+
+ public BallsRS() {
+ }
+
+ private Resources mRes;
+ private RenderScriptGL mRS;
+ private ScriptC_balls mScript;
+ private ScriptC_ball_physics mPhysicsScript;
+ private ProgramFragment mPFLines;
+ private ProgramFragment mPFPoints;
+ private ProgramVertex mPV;
+ private ScriptField_Point mPoints;
+ private ScriptField_VpConsts mVpConsts;
+
+ void updateProjectionMatrices() {
+ mVpConsts = new ScriptField_VpConsts(mRS, 1,
+ Allocation.USAGE_SCRIPT |
+ Allocation.USAGE_GRAPHICS_CONSTANTS);
+ ScriptField_VpConsts.Item i = new ScriptField_VpConsts.Item();
+ Matrix4f mvp = new Matrix4f();
+ mvp.loadOrtho(0, mRS.getWidth(), mRS.getHeight(), 0, -1, 1);
+ i.MVP = mvp;
+ mVpConsts.set(i, 0, true);
+ }
+
+ private void createProgramVertex() {
+ updateProjectionMatrices();
+
+ ProgramVertex.Builder sb = new ProgramVertex.Builder(mRS);
+ String t = "varying vec4 varColor;\n" +
+ "void main() {\n" +
+ " vec4 pos = vec4(0.0, 0.0, 0.0, 1.0);\n" +
+ " pos.xy = ATTRIB_position;\n" +
+ " gl_Position = UNI_MVP * pos;\n" +
+ " varColor = vec4(1.0, 1.0, 1.0, 1.0);\n" +
+ " gl_PointSize = ATTRIB_size;\n" +
+ "}\n";
+ sb.setShader(t);
+ sb.addConstant(mVpConsts.getType());
+ sb.addInput(mPoints.getElement());
+ ProgramVertex pvs = sb.create();
+ pvs.bindConstants(mVpConsts.getAllocation(), 0);
+ mRS.bindProgramVertex(pvs);
+ }
+
+ private Allocation loadTexture(int id) {
+ final Allocation allocation =
+ Allocation.createFromBitmapResource(mRS, mRes,
+ id, Allocation.MipmapControl.MIPMAP_NONE,
+ Allocation.USAGE_GRAPHICS_TEXTURE);
+ return allocation;
+ }
+
+ ProgramStore BLEND_ADD_DEPTH_NONE(RenderScript rs) {
+ ProgramStore.Builder builder = new ProgramStore.Builder(rs);
+ builder.setDepthFunc(ProgramStore.DepthFunc.ALWAYS);
+ builder.setBlendFunc(ProgramStore.BlendSrcFunc.ONE, ProgramStore.BlendDstFunc.ONE);
+ builder.setDitherEnabled(false);
+ builder.setDepthMaskEnabled(false);
+ return builder.create();
+ }
+
+ public void init(RenderScriptGL rs, Resources res, int width, int height) {
+ mRS = rs;
+ mRes = res;
+
+ ProgramFragmentFixedFunction.Builder pfb = new ProgramFragmentFixedFunction.Builder(rs);
+ pfb.setPointSpriteTexCoordinateReplacement(true);
+ pfb.setTexture(ProgramFragmentFixedFunction.Builder.EnvMode.MODULATE,
+ ProgramFragmentFixedFunction.Builder.Format.RGBA, 0);
+ pfb.setVaryingColor(true);
+ mPFPoints = pfb.create();
+
+ pfb = new ProgramFragmentFixedFunction.Builder(rs);
+ pfb.setVaryingColor(true);
+ mPFLines = pfb.create();
+
+ android.util.Log.e("rs", "Load texture");
+ mPFPoints.bindTexture(loadTexture(R.drawable.flares), 0);
+
+ mPoints = new ScriptField_Point(mRS, PART_COUNT, Allocation.USAGE_SCRIPT);
+
+ Mesh.AllocationBuilder smb = new Mesh.AllocationBuilder(mRS);
+ smb.addVertexAllocation(mPoints.getAllocation());
+ smb.addIndexSetType(Mesh.Primitive.POINT);
+ Mesh smP = smb.create();
+
+ mPhysicsScript = new ScriptC_ball_physics(mRS, mRes, R.raw.ball_physics);
+
+ mScript = new ScriptC_balls(mRS, mRes, R.raw.balls);
+ mScript.set_partMesh(smP);
+ mScript.set_physics_script(mPhysicsScript);
+ mScript.bind_point(mPoints);
+ mScript.bind_balls1(new ScriptField_Ball(mRS, PART_COUNT, Allocation.USAGE_SCRIPT));
+ mScript.bind_balls2(new ScriptField_Ball(mRS, PART_COUNT, Allocation.USAGE_SCRIPT));
+
+ mScript.set_gPFLines(mPFLines);
+ mScript.set_gPFPoints(mPFPoints);
+ createProgramVertex();
+
+ mRS.bindProgramStore(BLEND_ADD_DEPTH_NONE(mRS));
+
+ mPhysicsScript.set_gMinPos(new Float2(5, 5));
+ mPhysicsScript.set_gMaxPos(new Float2(width - 5, height - 5));
+
+ mScript.invoke_initParts(width, height);
+
+ mRS.bindRootScript(mScript);
+ }
+
+ public void newTouchPosition(float x, float y, float pressure, int id) {
+ mPhysicsScript.invoke_touch(x, y, pressure, id);
+ }
+
+ public void setAccel(float x, float y) {
+ mPhysicsScript.set_gGravityVector(new Float2(x, y));
+ }
+
+}
diff --git a/build-system/tests/renderscriptMultiSrc/src/main/java/com/example/android/rs/balls/BallsView.java b/build-system/tests/renderscriptMultiSrc/src/main/java/com/example/android/rs/balls/BallsView.java
new file mode 100644
index 0000000..b3b3756
--- /dev/null
+++ b/build-system/tests/renderscriptMultiSrc/src/main/java/com/example/android/rs/balls/BallsView.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.rs.balls;
+
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.concurrent.Semaphore;
+
+import android.renderscript.RSSurfaceView;
+import android.renderscript.RenderScript;
+import android.renderscript.RenderScriptGL;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+public class BallsView extends RSSurfaceView {
+
+ public BallsView(Context context) {
+ super(context);
+ //setFocusable(true);
+ }
+
+ private RenderScriptGL mRS;
+ private BallsRS mRender;
+
+ public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
+ super.surfaceChanged(holder, format, w, h);
+ if (mRS == null) {
+ RenderScriptGL.SurfaceConfig sc = new RenderScriptGL.SurfaceConfig();
+ mRS = createRenderScriptGL(sc);
+ mRS.setSurface(holder, w, h);
+ mRender = new BallsRS();
+ mRender.init(mRS, getResources(), w, h);
+ }
+ mRender.updateProjectionMatrices();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ if(mRS != null) {
+ mRS = null;
+ destroyRenderScriptGL();
+ }
+ }
+
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev)
+ {
+ int act = ev.getActionMasked();
+ if (act == ev.ACTION_UP) {
+ mRender.newTouchPosition(0, 0, 0, ev.getPointerId(0));
+ return false;
+ } else if (act == MotionEvent.ACTION_POINTER_UP) {
+ // only one pointer going up, we can get the index like this
+ int pointerIndex = ev.getActionIndex();
+ int pointerId = ev.getPointerId(pointerIndex);
+ mRender.newTouchPosition(0, 0, 0, pointerId);
+ return false;
+ }
+ int count = ev.getHistorySize();
+ int pcount = ev.getPointerCount();
+
+ for (int p=0; p < pcount; p++) {
+ int id = ev.getPointerId(p);
+ mRender.newTouchPosition(ev.getX(p),
+ ev.getY(p),
+ ev.getPressure(p),
+ id);
+
+ for (int i=0; i < count; i++) {
+ mRender.newTouchPosition(ev.getHistoricalX(p, i),
+ ev.getHistoricalY(p, i),
+ ev.getHistoricalPressure(p, i),
+ id);
+ }
+ }
+ return true;
+ }
+
+ void setAccel(float x, float y, float z) {
+ if (mRender == null) {
+ return;
+ }
+ mRender.setAccel(x, -y);
+ }
+
+}
+
+
diff --git a/build-system/tests/renderscriptMultiSrc/src/main/res/drawable/flares.png b/build-system/tests/renderscriptMultiSrc/src/main/res/drawable/flares.png
new file mode 100644
index 0000000..3a5c970
--- /dev/null
+++ b/build-system/tests/renderscriptMultiSrc/src/main/res/drawable/flares.png
Binary files differ
diff --git a/build-system/tests/renderscriptMultiSrc/src/main/res/drawable/test_pattern.png b/build-system/tests/renderscriptMultiSrc/src/main/res/drawable/test_pattern.png
new file mode 100644
index 0000000..e7d1455
--- /dev/null
+++ b/build-system/tests/renderscriptMultiSrc/src/main/res/drawable/test_pattern.png
Binary files differ
diff --git a/build-system/tests/renderscriptMultiSrc/src/main/rs/com/example/android/rs/balls/ball_physics.rs b/build-system/tests/renderscriptMultiSrc/src/main/rs/com/example/android/rs/balls/ball_physics.rs
new file mode 100644
index 0000000..ee6ab1d
--- /dev/null
+++ b/build-system/tests/renderscriptMultiSrc/src/main/rs/com/example/android/rs/balls/ball_physics.rs
@@ -0,0 +1,146 @@
+#pragma version(1)
+#pragma rs java_package_name(com.example.android.rs.balls)
+
+#include "balls.rsh"
+
+float2 gGravityVector = {0.f, 9.8f};
+
+float2 gMinPos = {0.f, 0.f};
+float2 gMaxPos = {1280.f, 700.f};
+
+static float2 touchPos[10];
+static float touchPressure[10];
+
+void touch(float x, float y, float pressure, int id) {
+ if (id >= 10) {
+ return;
+ }
+
+ touchPos[id].x = x;
+ touchPos[id].y = y;
+ touchPressure[id] = pressure;
+}
+
+void root(const Ball_t *ballIn, Ball_t *ballOut, const BallControl_t *ctl, uint32_t x) {
+ float2 fv = {0, 0};
+ float2 pos = ballIn->position;
+
+ int arcID = -1;
+ float arcInvStr = 100000;
+
+ const Ball_t * bPtr = rsGetElementAt(ctl->ain, 0);
+ for (uint32_t xin = 0; xin < ctl->dimX; xin++) {
+ float2 vec = bPtr[xin].position - pos;
+ float2 vec2 = vec * vec;
+ float len2 = vec2.x + vec2.y;
+
+ if (len2 < 10000) {
+ //float minDist = ballIn->size + bPtr[xin].size;
+ float forceScale = ballIn->size * bPtr[xin].size;
+ forceScale *= forceScale;
+
+ if (len2 > 16 /* (minDist*minDist)*/) {
+ // Repulsion
+ float len = sqrt(len2);
+ fv -= (vec / (len * len * len)) * 20000.f * forceScale;
+ } else {
+ if (len2 < 1) {
+ if (xin == x) {
+ continue;
+ }
+ ballOut->delta = 0.f;
+ ballOut->position = ballIn->position;
+ if (xin > x) {
+ ballOut->position.x += 1.f;
+ } else {
+ ballOut->position.x -= 1.f;
+ }
+ //ballOut->color.rgb = 1.f;
+ //ballOut->arcID = -1;
+ //ballOut->arcStr = 0;
+ return;
+ }
+ // Collision
+ float2 axis = normalize(vec);
+ float e1 = dot(axis, ballIn->delta);
+ float e2 = dot(axis, bPtr[xin].delta);
+ float e = (e1 - e2) * 0.45f;
+ if (e1 > 0) {
+ fv -= axis * e;
+ } else {
+ fv += axis * e;
+ }
+ }
+ }
+ }
+
+ fv /= ballIn->size * ballIn->size * ballIn->size;
+ fv -= gGravityVector * 4.f;
+ fv *= ctl->dt;
+
+ for (int i=0; i < 10; i++) {
+ if (touchPressure[i] > 0.1f) {
+ float2 vec = touchPos[i] - ballIn->position;
+ float2 vec2 = vec * vec;
+ float len2 = max(2.f, vec2.x + vec2.y);
+ fv -= (vec / len2) * touchPressure[i] * 300.f;
+ }
+ }
+
+ ballOut->delta = (ballIn->delta * (1.f - 0.004f)) + fv;
+ ballOut->position = ballIn->position + (ballOut->delta * ctl->dt);
+
+ const float wallForce = 400.f;
+ if (ballOut->position.x > (gMaxPos.x - 20.f)) {
+ float d = gMaxPos.x - ballOut->position.x;
+ if (d < 0.f) {
+ if (ballOut->delta.x > 0) {
+ ballOut->delta.x *= -0.7;
+ }
+ ballOut->position.x = gMaxPos.x;
+ } else {
+ ballOut->delta.x -= min(wallForce / (d * d), 10.f);
+ }
+ }
+
+ if (ballOut->position.x < (gMinPos.x + 20.f)) {
+ float d = ballOut->position.x - gMinPos.x;
+ if (d < 0.f) {
+ if (ballOut->delta.x < 0) {
+ ballOut->delta.x *= -0.7;
+ }
+ ballOut->position.x = gMinPos.x + 1.f;
+ } else {
+ ballOut->delta.x += min(wallForce / (d * d), 10.f);
+ }
+ }
+
+ if (ballOut->position.y > (gMaxPos.y - 20.f)) {
+ float d = gMaxPos.y - ballOut->position.y;
+ if (d < 0.f) {
+ if (ballOut->delta.y > 0) {
+ ballOut->delta.y *= -0.7;
+ }
+ ballOut->position.y = gMaxPos.y;
+ } else {
+ ballOut->delta.y -= min(wallForce / (d * d), 10.f);
+ }
+ }
+
+ if (ballOut->position.y < (gMinPos.y + 20.f)) {
+ float d = ballOut->position.y - gMinPos.y;
+ if (d < 0.f) {
+ if (ballOut->delta.y < 0) {
+ ballOut->delta.y *= -0.7;
+ }
+ ballOut->position.y = gMinPos.y + 1.f;
+ } else {
+ ballOut->delta.y += min(wallForce / (d * d * d), 10.f);
+ }
+ }
+
+ ballOut->size = ballIn->size;
+
+ //rsDebug("physics pos out", ballOut->position);
+}
+
diff --git a/build-system/tests/renderscriptMultiSrc/src/main/rs/com/example/android/rs/balls/balls.rs b/build-system/tests/renderscriptMultiSrc/src/main/rs/com/example/android/rs/balls/balls.rs
new file mode 100644
index 0000000..d86b804
--- /dev/null
+++ b/build-system/tests/renderscriptMultiSrc/src/main/rs/com/example/android/rs/balls/balls.rs
@@ -0,0 +1,85 @@
+#pragma version(1)
+#pragma rs java_package_name(com.example.android.rs.balls)
+#include "rs_graphics.rsh"
+
+#include "balls.rsh"
+
+#pragma stateVertex(parent)
+#pragma stateStore(parent)
+
+rs_program_fragment gPFPoints;
+rs_program_fragment gPFLines;
+rs_mesh partMesh;
+
+typedef struct __attribute__((packed, aligned(4))) Point {
+ float2 position;
+ float size;
+} Point_t;
+Point_t *point;
+
+typedef struct VpConsts {
+ rs_matrix4x4 MVP;
+} VpConsts_t;
+VpConsts_t *vpConstants;
+
+rs_script physics_script;
+
+Ball_t *balls1;
+Ball_t *balls2;
+
+static int frame = 0;
+
+void initParts(int w, int h)
+{
+ uint32_t dimX = rsAllocationGetDimX(rsGetAllocation(balls1));
+
+ for (uint32_t ct=0; ct < dimX; ct++) {
+ balls1[ct].position.x = rsRand(0.f, (float)w);
+ balls1[ct].position.y = rsRand(0.f, (float)h);
+ balls1[ct].delta.x = 0.f;
+ balls1[ct].delta.y = 0.f;
+ balls1[ct].size = 1.f;
+
+ float r = rsRand(100.f);
+ if (r > 90.f) {
+ balls1[ct].size += pow(10.f, rsRand(0.f, 2.f)) * 0.07;
+ }
+ }
+}
+
+
+
+int root() {
+ rsgClearColor(0.f, 0.f, 0.f, 1.f);
+
+ BallControl_t bc;
+ Ball_t *bout;
+
+ if (frame & 1) {
+ rsSetObject(&bc.ain, rsGetAllocation(balls2));
+ rsSetObject(&bc.aout, rsGetAllocation(balls1));
+ bout = balls2;
+ } else {
+ rsSetObject(&bc.ain, rsGetAllocation(balls1));
+ rsSetObject(&bc.aout, rsGetAllocation(balls2));
+ bout = balls1;
+ }
+
+ bc.dimX = rsAllocationGetDimX(bc.ain);
+ bc.dt = 1.f / 30.f;
+
+ rsForEach(physics_script, bc.ain, bc.aout, &bc);
+
+ for (uint32_t ct=0; ct < bc.dimX; ct++) {
+ point[ct].position = bout[ct].position;
+ point[ct].size = 6.f /*+ bout[ct].color.g * 6.f*/ * bout[ct].size;
+ }
+
+ frame++;
+ rsgBindProgramFragment(gPFPoints);
+ rsgDrawMesh(partMesh);
+ rsClearObject(&bc.ain);
+ rsClearObject(&bc.aout);
+ return 1;
+}
+
diff --git a/build-system/tests/renderscriptMultiSrc/src/release/rs/com/example/android/rs/balls/balls.rsh b/build-system/tests/renderscriptMultiSrc/src/release/rs/com/example/android/rs/balls/balls.rsh
new file mode 100644
index 0000000..fc886f9
--- /dev/null
+++ b/build-system/tests/renderscriptMultiSrc/src/release/rs/com/example/android/rs/balls/balls.rsh
@@ -0,0 +1,18 @@
+
+typedef struct __attribute__((packed, aligned(4))) Ball {
+ float2 delta;
+ float2 position;
+ //float3 color;
+ float size;
+ //int arcID;
+ //float arcStr;
+} Ball_t;
+Ball_t *balls;
+
+
+typedef struct BallControl {
+ uint32_t dimX;
+ rs_allocation ain;
+ rs_allocation aout;
+ float dt;
+} BallControl_t;
diff --git a/build-system/tests/repo/.gitignore b/build-system/tests/repo/.gitignore
new file mode 100644
index 0000000..6971bfa
--- /dev/null
+++ b/build-system/tests/repo/.gitignore
@@ -0,0 +1 @@
+testrepo
\ No newline at end of file
diff --git a/build-system/tests/repo/app/build.gradle b/build-system/tests/repo/app/build.gradle
new file mode 100644
index 0000000..f2131fe
--- /dev/null
+++ b/build-system/tests/repo/app/build.gradle
@@ -0,0 +1,26 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+
+apply plugin: 'android'
+apply plugin: 'maven'
+
+repositories {
+ maven { url '../testrepo' }
+ mavenCentral()
+}
+
+dependencies {
+ compile 'com.example.android.multiproject:lib:1.0'
+}
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
+
diff --git a/build-system/tests/repo/app/src/main/AndroidManifest.xml b/build-system/tests/repo/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..71e7a47
--- /dev/null
+++ b/build-system/tests/repo/app/src/main/AndroidManifest.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.multiproject"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <application android:label="@string/app_name" android:icon="@drawable/ic_launcher">
+ <activity android:name="MainActivity"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/build-system/tests/repo/app/src/main/java/com/example/android/multiproject/MainActivity.java b/build-system/tests/repo/app/src/main/java/com/example/android/multiproject/MainActivity.java
new file mode 100644
index 0000000..6a8b95b
--- /dev/null
+++ b/build-system/tests/repo/app/src/main/java/com/example/android/multiproject/MainActivity.java
@@ -0,0 +1,30 @@
+package com.example.android.multiproject;
+
+import android.app.Activity;
+import android.view.View;
+import android.content.Intent;
+import android.os.Bundle;
+
+import java.util.List;
+import com.example.android.multiproject.person.Person;
+import com.google.common.collect.Lists;
+
+import com.example.android.multiproject.library.ShowPeopleActivity;
+
+public class MainActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ // some random code to test dependencies on util and guava
+ Person p = new Person("foo");
+ List<Person> persons = Lists.newArrayList();
+ persons.add(p);
+ }
+
+ public void sendMessage(View view) {
+ Intent intent = new Intent(this, ShowPeopleActivity.class);
+ startActivity(intent);
+ }
+}
diff --git a/templates/gradle-projects/NewAndroidLibrary/root/res/drawable-hdpi/ic_launcher.png b/build-system/tests/repo/app/src/main/res/drawable-hdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
copy from templates/gradle-projects/NewAndroidLibrary/root/res/drawable-hdpi/ic_launcher.png
copy to build-system/tests/repo/app/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/repo/app/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/tests/repo/app/src/main/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..9923872
--- /dev/null
+++ b/build-system/tests/repo/app/src/main/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/templates/gradle-projects/NewAndroidLibrary/root/res/drawable-mdpi/ic_launcher.png b/build-system/tests/repo/app/src/main/res/drawable-mdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
copy from templates/gradle-projects/NewAndroidLibrary/root/res/drawable-mdpi/ic_launcher.png
copy to build-system/tests/repo/app/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/templates/gradle-projects/NewAndroidLibrary/root/res/drawable-xhdpi/ic_launcher.png b/build-system/tests/repo/app/src/main/res/drawable-xhdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
copy from templates/gradle-projects/NewAndroidLibrary/root/res/drawable-xhdpi/ic_launcher.png
copy to build-system/tests/repo/app/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/repo/app/src/main/res/layout/main.xml b/build-system/tests/repo/app/src/main/res/layout/main.xml
new file mode 100644
index 0000000..ccc59fb
--- /dev/null
+++ b/build-system/tests/repo/app/src/main/res/layout/main.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/button_send"
+ android:onClick="sendMessage" />
+</LinearLayout>
diff --git a/build-system/tests/repo/app/src/main/res/values/strings.xml b/build-system/tests/repo/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..e1f49b6
--- /dev/null
+++ b/build-system/tests/repo/app/src/main/res/values/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">Composite App</string>
+ <string name="button_send">Go</string>
+</resources>
diff --git a/build-system/tests/repo/baseLibrary/build.gradle b/build-system/tests/repo/baseLibrary/build.gradle
new file mode 100644
index 0000000..9e3bf41
--- /dev/null
+++ b/build-system/tests/repo/baseLibrary/build.gradle
@@ -0,0 +1,38 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+
+apply plugin: 'android-library'
+apply plugin: 'maven'
+
+repositories {
+ maven { url '../testrepo' }
+ mavenCentral()
+}
+
+dependencies {
+ compile 'com.example.android.multiproject:util:1.0'
+ releaseCompile 'com.google.guava:guava:11.0.2'
+}
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
+
+group = 'com.example.android.multiproject'
+archivesBaseName = 'baseLib'
+version = '1.0'
+
+uploadArchives {
+ repositories {
+ mavenDeployer {
+ repository(url: uri("../testrepo"))
+ }
+ }
+}
diff --git a/build-system/tests/repo/baseLibrary/src/main/AndroidManifest.xml b/build-system/tests/repo/baseLibrary/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..54d079c
--- /dev/null
+++ b/build-system/tests/repo/baseLibrary/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.multiproject.library.base">
+</manifest>
diff --git a/build-system/tests/repo/baseLibrary/src/main/java/com/sample/android/multiproject/library/PersonView.java b/build-system/tests/repo/baseLibrary/src/main/java/com/sample/android/multiproject/library/PersonView.java
new file mode 100644
index 0000000..b218532
--- /dev/null
+++ b/build-system/tests/repo/baseLibrary/src/main/java/com/sample/android/multiproject/library/PersonView.java
@@ -0,0 +1,13 @@
+package com.example.android.multiproject.library;
+
+import android.widget.TextView;
+import android.content.Context;
+import com.example.android.multiproject.person.Person;
+
+class PersonView extends TextView {
+ public PersonView(Context context, Person person) {
+ super(context);
+ setTextSize(20);
+ setText(person.getName());
+ }
+}
diff --git a/build-system/tests/repo/library/build.gradle b/build-system/tests/repo/library/build.gradle
new file mode 100644
index 0000000..9e5c7c9
--- /dev/null
+++ b/build-system/tests/repo/library/build.gradle
@@ -0,0 +1,37 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+
+apply plugin: 'android-library'
+apply plugin: 'maven'
+
+repositories {
+ maven { url '../testrepo' }
+ mavenCentral()
+}
+
+dependencies {
+ compile 'com.example.android.multiproject:baseLib:1.0'
+}
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
+
+group = 'com.example.android.multiproject'
+archivesBaseName = 'lib'
+version = '1.0'
+
+uploadArchives {
+ repositories {
+ mavenDeployer {
+ repository(url: uri("../testrepo"))
+ }
+ }
+}
diff --git a/build-system/tests/repo/library/src/main/AndroidManifest.xml b/build-system/tests/repo/library/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..2bc9331
--- /dev/null
+++ b/build-system/tests/repo/library/src/main/AndroidManifest.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.multiproject.library">
+ <application>
+ <activity
+ android:name="ShowPeopleActivity"
+ android:label="@string/title_activity_display_message" >
+ </activity>
+ </application>
+</manifest>
diff --git a/build-system/tests/repo/library/src/main/java/com/example/android/multiproject/library/ShowPeopleActivity.java b/build-system/tests/repo/library/src/main/java/com/example/android/multiproject/library/ShowPeopleActivity.java
new file mode 100644
index 0000000..a3f2195
--- /dev/null
+++ b/build-system/tests/repo/library/src/main/java/com/example/android/multiproject/library/ShowPeopleActivity.java
@@ -0,0 +1,30 @@
+package com.example.android.multiproject.library;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.content.Intent;
+import android.widget.TextView;
+import android.widget.LinearLayout;
+
+import java.lang.String;
+import java.util.Arrays;
+
+import com.example.android.multiproject.person.Person;
+import com.example.android.multiproject.person.People;
+
+public class ShowPeopleActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ LinearLayout group = new LinearLayout(this);
+ group.setOrientation(LinearLayout.VERTICAL);
+
+ Iterable<Person> people = new People();
+ for (Person person : people) {
+ group.addView(new PersonView(this, person));
+ }
+
+ setContentView(group);
+ }
+}
diff --git a/build-system/tests/repo/library/src/main/res/values/strings.xml b/build-system/tests/repo/library/src/main/res/values/strings.xml
new file mode 100644
index 0000000..45e9dbb
--- /dev/null
+++ b/build-system/tests/repo/library/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="title_activity_display_message">People</string>
+</resources>
diff --git a/build-system/tests/repo/util/build.gradle b/build-system/tests/repo/util/build.gradle
new file mode 100644
index 0000000..150b997
--- /dev/null
+++ b/build-system/tests/repo/util/build.gradle
@@ -0,0 +1,21 @@
+apply plugin: 'java'
+apply plugin: 'maven'
+
+repositories {
+ mavenCentral()
+}
+
+group = 'com.example.android.multiproject'
+archivesBaseName = 'util'
+version = '1.0'
+
+sourceCompatibility = "1.6"
+targetCompatibility = "1.6"
+
+uploadArchives {
+ repositories {
+ mavenDeployer {
+ repository(url: uri("../testrepo"))
+ }
+ }
+}
diff --git a/build-system/tests/repo/util/src/main/java/com/example/android/multiproject/person/People.java b/build-system/tests/repo/util/src/main/java/com/example/android/multiproject/person/People.java
new file mode 100644
index 0000000..2dbc9b5
--- /dev/null
+++ b/build-system/tests/repo/util/src/main/java/com/example/android/multiproject/person/People.java
@@ -0,0 +1,13 @@
+package com.example.android.multiproject.person;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+public class People implements Iterable<Person> {
+ public Iterator<Person> iterator() {
+ ArrayList<Person> list = new ArrayList<Person>();
+ list.add(new Person("Fred"));
+ list.add(new Person("Barney"));
+ return list.iterator();
+ }
+}
diff --git a/build-system/tests/repo/util/src/main/java/com/example/android/multiproject/person/Person.java b/build-system/tests/repo/util/src/main/java/com/example/android/multiproject/person/Person.java
new file mode 100644
index 0000000..2f4aa9f
--- /dev/null
+++ b/build-system/tests/repo/util/src/main/java/com/example/android/multiproject/person/Person.java
@@ -0,0 +1,13 @@
+package com.example.android.multiproject.person;
+
+public class Person {
+ private final String name;
+
+ public Person(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/build-system/tests/rsSupportMode/build.gradle b/build-system/tests/rsSupportMode/build.gradle
new file mode 100644
index 0000000..3909321
--- /dev/null
+++ b/build-system/tests/rsSupportMode/build.gradle
@@ -0,0 +1,39 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+apply plugin: 'android'
+
+android {
+ compileSdkVersion 18
+ buildToolsVersion "18.1.1"
+
+ defaultConfig {
+ minSdkVersion 8
+ targetSdkVersion 16
+ renderscriptTargetApi 18
+ renderscriptSupportMode true
+ }
+
+ productFlavors {
+ x86 {
+ ndk {
+ abiFilter "x86"
+ }
+ }
+ arm {
+ ndk {
+ abiFilter "armeabi-v7a"
+ }
+ }
+ mips {
+ ndk {
+ abiFilter "mips"
+ }
+ }
+ }
+}
diff --git a/build-system/tests/rsSupportMode/proguard-project.txt b/build-system/tests/rsSupportMode/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/build-system/tests/rsSupportMode/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/build-system/tests/rsSupportMode/project.properties b/build-system/tests/rsSupportMode/project.properties
new file mode 100644
index 0000000..ca810ec
--- /dev/null
+++ b/build-system/tests/rsSupportMode/project.properties
@@ -0,0 +1,22 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-18
+renderscript.target=18
+renderscript.support.mode=true
+
+
+key.store=/Users/xav/.android/debug.keystore
+key.store.password=android
+key.alias=androiddebugkey
+key.alias.password=android
diff --git a/build-system/tests/rsSupportMode/src/main/AndroidManifest.xml b/build-system/tests/rsSupportMode/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..0129fa8
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/AndroidManifest.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.rs.image2">
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-sdk android:minSdkVersion="8" />
+ <application android:label="IP GB">
+ <activity android:name="ImageProcessingActivity2">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/BWFilter.java b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/BWFilter.java
new file mode 100644
index 0000000..4b19856
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/BWFilter.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.rs.image2;
+
+import java.lang.Math;
+
+
+public class BWFilter extends TestBase {
+ private ScriptC_bwfilter mScript;
+
+ public void createTest(android.content.res.Resources res) {
+ mScript = new ScriptC_bwfilter(mRS);
+ }
+
+ public void runTest() {
+ mScript.invoke_prepareBwFilter(50, 50, 50);
+ mScript.forEach_bwFilterKernel(mInPixelsAllocation, mOutPixelsAllocation);
+ }
+
+}
diff --git a/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Blend.java b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Blend.java
new file mode 100644
index 0000000..d81ba88
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Blend.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.rs.image2;
+
+import java.lang.Math;
+import java.lang.Short;
+
+import android.support.v8.renderscript.*;
+import android.util.Log;
+import android.widget.SeekBar;
+import android.widget.TextView;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.view.View;
+import android.widget.Spinner;
+
+public class Blend extends TestBase {
+ private ScriptIntrinsicBlend mBlend;
+ private ScriptC_blend mBlendHelper;
+ private short image1Alpha = 128;
+ private short image2Alpha = 128;
+
+ String mIntrinsicNames[];
+
+ private Allocation image1;
+ private Allocation image2;
+ private int currentIntrinsic = 0;
+
+ private AdapterView.OnItemSelectedListener mIntrinsicSpinnerListener =
+ new AdapterView.OnItemSelectedListener() {
+ public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
+ currentIntrinsic = pos;
+ if (mRS != null) {
+ runTest();
+ act.updateDisplay();
+ }
+ }
+
+ public void onNothingSelected(AdapterView parent) {
+
+ }
+ };
+
+ public void createTest(android.content.res.Resources res) {
+ mBlend = ScriptIntrinsicBlend.create(mRS, Element.U8_4(mRS));
+ mBlendHelper = new ScriptC_blend(mRS);
+ mBlendHelper.set_alpha((short)128);
+
+ image1 = Allocation.createTyped(mRS, mInPixelsAllocation.getType());
+ image2 = Allocation.createTyped(mRS, mInPixelsAllocation2.getType());
+
+ mIntrinsicNames = new String[14];
+ mIntrinsicNames[0] = "Source";
+ mIntrinsicNames[1] = "Destination";
+ mIntrinsicNames[2] = "Source Over";
+ mIntrinsicNames[3] = "Destination Over";
+ mIntrinsicNames[4] = "Source In";
+ mIntrinsicNames[5] = "Destination In";
+ mIntrinsicNames[6] = "Source Out";
+ mIntrinsicNames[7] = "Destination Out";
+ mIntrinsicNames[8] = "Source Atop";
+ mIntrinsicNames[9] = "Destination Atop";
+ mIntrinsicNames[10] = "XOR";
+ mIntrinsicNames[11] = "Add";
+ mIntrinsicNames[12] = "Subtract";
+ mIntrinsicNames[13] = "Multiply";
+ }
+
+ public boolean onSpinner1Setup(Spinner s) {
+ s.setAdapter(new ArrayAdapter<String>(
+ act, R.layout.spinner_layout, mIntrinsicNames));
+ s.setOnItemSelectedListener(mIntrinsicSpinnerListener);
+ return true;
+ }
+
+ public boolean onBar1Setup(SeekBar b, TextView t) {
+ t.setText("Image 1 Alpha");
+ b.setMax(255);
+ b.setProgress(image1Alpha);
+ return true;
+ }
+
+ public void onBar1Changed(int progress) {
+ image1Alpha = (short)progress;
+ }
+
+ public boolean onBar2Setup(SeekBar b, TextView t) {
+ t.setText("Image 2 Alpha");
+ b.setMax(255);
+ b.setProgress(image2Alpha);
+ return true;
+ }
+
+ public void onBar2Changed(int progress) {
+ image2Alpha = (short)progress;
+ }
+
+ public void runTest() {
+ image1.copy2DRangeFrom(0, 0, mInPixelsAllocation.getType().getX(), mInPixelsAllocation.getType().getY(), mInPixelsAllocation, 0, 0);
+ image2.copy2DRangeFrom(0, 0, mInPixelsAllocation2.getType().getX(), mInPixelsAllocation2.getType().getY(), mInPixelsAllocation2, 0, 0);
+
+ mBlendHelper.set_alpha(image1Alpha);
+ mBlendHelper.forEach_setImageAlpha(image1);
+
+ mBlendHelper.set_alpha(image2Alpha);
+ mBlendHelper.forEach_setImageAlpha(image2);
+
+ switch (currentIntrinsic) {
+ case 0:
+ mBlend.forEachSrc(image1, image2);
+ break;
+ case 1:
+ mBlend.forEachDst(image1, image2);
+ break;
+ case 2:
+ mBlend.forEachSrcOver(image1, image2);
+ break;
+ case 3:
+ mBlend.forEachDstOver(image1, image2);
+ break;
+ case 4:
+ mBlend.forEachSrcIn(image1, image2);
+ break;
+ case 5:
+ mBlend.forEachDstIn(image1, image2);
+ break;
+ case 6:
+ mBlend.forEachSrcOut(image1, image2);
+ break;
+ case 7:
+ mBlend.forEachDstOut(image1, image2);
+ break;
+ case 8:
+ mBlend.forEachSrcAtop(image1, image2);
+ break;
+ case 9:
+ mBlend.forEachDstAtop(image1, image2);
+ break;
+ case 10:
+ mBlend.forEachXor(image1, image2);
+ break;
+ case 11:
+ mBlend.forEachAdd(image1, image2);
+ break;
+ case 12:
+ mBlend.forEachSubtract(image1, image2);
+ break;
+ case 13:
+ mBlend.forEachMultiply(image1, image2);
+ break;
+ }
+
+ mOutPixelsAllocation.copy2DRangeFrom(0, 0, image2.getType().getX(), image2.getType().getY(), image2, 0, 0);
+ }
+
+}
diff --git a/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Blur25.java b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Blur25.java
new file mode 100644
index 0000000..b518b02
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Blur25.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.rs.image2;
+
+import java.lang.Math;
+
+import android.support.v8.renderscript.*;
+import android.util.Log;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+public class Blur25 extends TestBase {
+ private boolean mUseIntrinsic = false;
+ private ScriptIntrinsicBlur mIntrinsic;
+
+ private int MAX_RADIUS = 25;
+ private ScriptC_threshold mScript;
+ private float mRadius = MAX_RADIUS;
+ private float mSaturation = 1.0f;
+ private Allocation mScratchPixelsAllocation1;
+ private Allocation mScratchPixelsAllocation2;
+
+
+ public Blur25(boolean useIntrinsic) {
+ mUseIntrinsic = useIntrinsic;
+ }
+
+ public boolean onBar1Setup(SeekBar b, TextView t) {
+ t.setText("Radius");
+ b.setProgress(100);
+ return true;
+ }
+
+
+ public void onBar1Changed(int progress) {
+ mRadius = ((float)progress) / 100.0f * MAX_RADIUS;
+ if (mRadius <= 0.10f) {
+ mRadius = 0.10f;
+ }
+ if (mUseIntrinsic) {
+ mIntrinsic.setRadius(mRadius);
+ } else {
+ mScript.invoke_setRadius((int)mRadius);
+ }
+ }
+
+
+ public void createTest(android.content.res.Resources res) {
+ int width = mInPixelsAllocation.getType().getX();
+ int height = mInPixelsAllocation.getType().getY();
+
+ if (mUseIntrinsic) {
+ mIntrinsic = ScriptIntrinsicBlur.create(mRS, Element.U8_4(mRS));
+ mIntrinsic.setRadius(MAX_RADIUS);
+ mIntrinsic.setInput(mInPixelsAllocation);
+ } else {
+
+ Type.Builder tb = new Type.Builder(mRS, Element.F32_4(mRS));
+ tb.setX(width);
+ tb.setY(height);
+ mScratchPixelsAllocation1 = Allocation.createTyped(mRS, tb.create());
+ mScratchPixelsAllocation2 = Allocation.createTyped(mRS, tb.create());
+
+ mScript = new ScriptC_threshold(mRS, res, R.raw.threshold);
+ mScript.set_width(width);
+ mScript.set_height(height);
+ mScript.invoke_setRadius(MAX_RADIUS);
+
+ mScript.set_InPixel(mInPixelsAllocation);
+ mScript.set_ScratchPixel1(mScratchPixelsAllocation1);
+ mScript.set_ScratchPixel2(mScratchPixelsAllocation2);
+ }
+ }
+
+ public void runTest() {
+ if (mUseIntrinsic) {
+ mIntrinsic.forEach(mOutPixelsAllocation);
+ } else {
+ mScript.forEach_copyIn(mInPixelsAllocation, mScratchPixelsAllocation1);
+ mScript.forEach_horz(mScratchPixelsAllocation2);
+ mScript.forEach_vert(mOutPixelsAllocation);
+ }
+ }
+
+ public void setupBenchmark() {
+ if (mUseIntrinsic) {
+ mIntrinsic.setRadius(MAX_RADIUS);
+ } else {
+ mScript.invoke_setRadius(MAX_RADIUS);
+ }
+ }
+
+ public void exitBenchmark() {
+ if (mUseIntrinsic) {
+ mIntrinsic.setRadius(mRadius);
+ } else {
+ mScript.invoke_setRadius((int)mRadius);
+ }
+ }
+}
diff --git a/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Blur25G.java b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Blur25G.java
new file mode 100644
index 0000000..19aa9f7
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Blur25G.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.rs.image2;
+
+import java.lang.Math;
+
+import android.graphics.Bitmap;
+import android.support.v8.renderscript.*;
+import android.util.Log;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+public class Blur25G extends TestBase {
+ private final int MAX_RADIUS = 25;
+ private float mRadius = MAX_RADIUS;
+
+ private ScriptIntrinsicBlur mIntrinsic;
+
+ private ScriptC_greyscale mScript;
+ private Allocation mScratchPixelsAllocation1;
+ private Allocation mScratchPixelsAllocation2;
+
+
+ public Blur25G() {
+ }
+
+ public boolean onBar1Setup(SeekBar b, TextView t) {
+ t.setText("Radius");
+ b.setProgress(100);
+ return true;
+ }
+
+
+ public void onBar1Changed(int progress) {
+ mRadius = ((float)progress) / 100.0f * MAX_RADIUS;
+ if (mRadius <= 0.10f) {
+ mRadius = 0.10f;
+ }
+ mIntrinsic.setRadius(mRadius);
+ }
+
+
+ public void createTest(android.content.res.Resources res) {
+ int width = mInPixelsAllocation.getType().getX();
+ int height = mInPixelsAllocation.getType().getY();
+
+ Type.Builder tb = new Type.Builder(mRS, Element.U8(mRS));
+ tb.setX(width);
+ tb.setY(height);
+ mScratchPixelsAllocation1 = Allocation.createTyped(mRS, tb.create());
+ mScratchPixelsAllocation2 = Allocation.createTyped(mRS, tb.create());
+
+ mScript = new ScriptC_greyscale(mRS);
+ mScript.forEach_toU8(mInPixelsAllocation, mScratchPixelsAllocation1);
+
+ mIntrinsic = ScriptIntrinsicBlur.create(mRS, Element.U8(mRS));
+ mIntrinsic.setRadius(MAX_RADIUS);
+ mIntrinsic.setInput(mScratchPixelsAllocation1);
+ }
+
+ public void runTest() {
+ mIntrinsic.forEach(mScratchPixelsAllocation2);
+ }
+
+ public void setupBenchmark() {
+ mIntrinsic.setRadius(MAX_RADIUS);
+ }
+
+ public void exitBenchmark() {
+ mIntrinsic.setRadius(mRadius);
+ }
+
+ public void updateBitmap(Bitmap b) {
+ mScript.forEach_toU8_4(mScratchPixelsAllocation2, mOutPixelsAllocation);
+ mOutPixelsAllocation.copyTo(b);
+ }
+
+}
+
diff --git a/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/ColorCube.java b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/ColorCube.java
new file mode 100644
index 0000000..e960385
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/ColorCube.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.rs.image2;
+
+import java.lang.Math;
+
+import android.support.v8.renderscript.*;
+import android.util.Log;
+
+public class ColorCube extends TestBase {
+ private Allocation mCube;
+ private ScriptC_colorcube mScript;
+ private ScriptIntrinsic3DLUT mIntrinsic;
+ private boolean mUseIntrinsic;
+
+ public ColorCube(boolean useIntrinsic) {
+ mUseIntrinsic = useIntrinsic;
+ }
+
+ private void initCube() {
+ final int sx = 32;
+ final int sy = 32;
+ final int sz = 16;
+
+ Type.Builder tb = new Type.Builder(mRS, Element.U8_4(mRS));
+ tb.setX(sx);
+ tb.setY(sy);
+ tb.setZ(sz);
+ Type t = tb.create();
+ mCube = Allocation.createTyped(mRS, t);
+
+ int dat[] = new int[sx * sy * sz];
+ for (int z = 0; z < sz; z++) {
+ for (int y = 0; y < sy; y++) {
+ for (int x = 0; x < sx; x++ ) {
+ int v = 0xff000000;
+ v |= (0xff * x / (sx - 1));
+ v |= (0xff * y / (sy - 1)) << 8;
+ v |= (0xff * z / (sz - 1)) << 16;
+ dat[z*sy*sx + y*sx + x] = v;
+ }
+ }
+ }
+
+ mCube.copyFromUnchecked(dat);
+ }
+
+ public void createTest(android.content.res.Resources res) {
+ mScript = new ScriptC_colorcube(mRS, res, R.raw.colorcube);
+ mIntrinsic = ScriptIntrinsic3DLUT.create(mRS, Element.U8_4(mRS));
+
+ initCube();
+ mScript.invoke_setCube(mCube);
+ mIntrinsic.setLUT(mCube);
+ }
+
+ public void runTest() {
+ if (mUseIntrinsic) {
+ mIntrinsic.forEach(mInPixelsAllocation, mOutPixelsAllocation);
+ } else {
+ mScript.forEach_root(mInPixelsAllocation, mOutPixelsAllocation);
+ }
+ }
+
+}
diff --git a/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/ColorMatrix.java b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/ColorMatrix.java
new file mode 100644
index 0000000..3b0f86a
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/ColorMatrix.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.rs.image2;
+
+import java.lang.Math;
+
+import android.support.v8.renderscript.*;
+import android.util.Log;
+
+public class ColorMatrix extends TestBase {
+ private ScriptC_colormatrix mScript;
+ private ScriptIntrinsicColorMatrix mIntrinsic;
+ private boolean mUseIntrinsic;
+ private boolean mUseGrey;
+
+ public ColorMatrix(boolean useIntrinsic, boolean useGrey) {
+ mUseIntrinsic = useIntrinsic;
+ mUseGrey = useGrey;
+ }
+
+ public void createTest(android.content.res.Resources res) {
+ Matrix4f m = new Matrix4f();
+ m.set(1, 0, 0.2f);
+ m.set(1, 1, 0.9f);
+ m.set(1, 2, 0.2f);
+
+ if (mUseIntrinsic) {
+ mIntrinsic = ScriptIntrinsicColorMatrix.create(mRS, Element.U8_4(mRS));
+ if (mUseGrey) {
+ mIntrinsic.setGreyscale();
+ } else {
+ mIntrinsic.setColorMatrix(m);
+ }
+ } else {
+ mScript = new ScriptC_colormatrix(mRS, res, R.raw.colormatrix);
+ mScript.invoke_setMatrix(m);
+ }
+ }
+
+ public void runTest() {
+ if (mUseIntrinsic) {
+ mIntrinsic.forEach(mInPixelsAllocation, mOutPixelsAllocation);
+ } else {
+ mScript.forEach_root(mInPixelsAllocation, mOutPixelsAllocation);
+ }
+ }
+
+}
diff --git a/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Contrast.java b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Contrast.java
new file mode 100644
index 0000000..3ae5d2a
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Contrast.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.rs.image2;
+
+import java.lang.Math;
+
+
+public class Contrast extends TestBase {
+ private ScriptC_contrast mScript;
+
+ public void createTest(android.content.res.Resources res) {
+ mScript = new ScriptC_contrast(mRS);
+ }
+
+ public void runTest() {
+ mScript.invoke_setBright(50.f);
+ mScript.forEach_contrast(mInPixelsAllocation, mOutPixelsAllocation);
+ }
+
+}
diff --git a/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Convolve3x3.java b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Convolve3x3.java
new file mode 100644
index 0000000..d4852f0
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Convolve3x3.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.rs.image2;
+
+import java.lang.Math;
+
+import android.support.v8.renderscript.*;
+import android.util.Log;
+
+public class Convolve3x3 extends TestBase {
+ private ScriptC_ip2_convolve3x3 mScript;
+ private ScriptIntrinsicConvolve3x3 mIntrinsic;
+
+ private int mWidth;
+ private int mHeight;
+ private boolean mUseIntrinsic;
+
+ public Convolve3x3(boolean useIntrinsic) {
+ mUseIntrinsic = useIntrinsic;
+ }
+
+ public void createTest(android.content.res.Resources res) {
+ mWidth = mInPixelsAllocation.getType().getX();
+ mHeight = mInPixelsAllocation.getType().getY();
+
+ float f[] = new float[9];
+ f[0] = 0.f; f[1] = -1.f; f[2] = 0.f;
+ f[3] = -1.f; f[4] = 5.f; f[5] = -1.f;
+ f[6] = 0.f; f[7] = -1.f; f[8] = 0.f;
+
+ if (mUseIntrinsic) {
+ mIntrinsic = ScriptIntrinsicConvolve3x3.create(mRS, Element.U8_4(mRS));
+ mIntrinsic.setCoefficients(f);
+ mIntrinsic.setInput(mInPixelsAllocation);
+ } else {
+ mScript = new ScriptC_ip2_convolve3x3(mRS, res, R.raw.ip2_convolve3x3);
+ mScript.set_gCoeffs(f);
+ mScript.set_gIn(mInPixelsAllocation);
+ mScript.set_gWidth(mWidth);
+ mScript.set_gHeight(mHeight);
+ }
+ }
+
+ public void runTest() {
+ if (mUseIntrinsic) {
+ mIntrinsic.forEach(mOutPixelsAllocation);
+ } else {
+ mScript.forEach_root(mOutPixelsAllocation);
+ }
+ }
+
+}
diff --git a/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Convolve5x5.java b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Convolve5x5.java
new file mode 100644
index 0000000..d2da3c4
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Convolve5x5.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.rs.image2;
+
+import java.lang.Math;
+
+import android.support.v8.renderscript.*;
+import android.util.Log;
+
+public class Convolve5x5 extends TestBase {
+ private ScriptC_convolve5x5 mScript;
+ private ScriptIntrinsicConvolve5x5 mIntrinsic;
+
+ private int mWidth;
+ private int mHeight;
+ private boolean mUseIntrinsic;
+
+ public Convolve5x5(boolean useIntrinsic) {
+ mUseIntrinsic = useIntrinsic;
+ }
+
+ public void createTest(android.content.res.Resources res) {
+ mWidth = mInPixelsAllocation.getType().getX();
+ mHeight = mInPixelsAllocation.getType().getY();
+
+ float f[] = new float[25];
+ //f[0] = 0.012f; f[1] = 0.025f; f[2] = 0.031f; f[3] = 0.025f; f[4] = 0.012f;
+ //f[5] = 0.025f; f[6] = 0.057f; f[7] = 0.075f; f[8] = 0.057f; f[9] = 0.025f;
+ //f[10]= 0.031f; f[11]= 0.075f; f[12]= 0.095f; f[13]= 0.075f; f[14]= 0.031f;
+ //f[15]= 0.025f; f[16]= 0.057f; f[17]= 0.075f; f[18]= 0.057f; f[19]= 0.025f;
+ //f[20]= 0.012f; f[21]= 0.025f; f[22]= 0.031f; f[23]= 0.025f; f[24]= 0.012f;
+
+ //f[0] = 1.f; f[1] = 2.f; f[2] = 0.f; f[3] = -2.f; f[4] = -1.f;
+ //f[5] = 4.f; f[6] = 8.f; f[7] = 0.f; f[8] = -8.f; f[9] = -4.f;
+ //f[10]= 6.f; f[11]=12.f; f[12]= 0.f; f[13]=-12.f; f[14]= -6.f;
+ //f[15]= 4.f; f[16]= 8.f; f[17]= 0.f; f[18]= -8.f; f[19]= -4.f;
+ //f[20]= 1.f; f[21]= 2.f; f[22]= 0.f; f[23]= -2.f; f[24]= -1.f;
+
+ f[0] = -1.f; f[1] = -3.f; f[2] = -4.f; f[3] = -3.f; f[4] = -1.f;
+ f[5] = -3.f; f[6] = 0.f; f[7] = 6.f; f[8] = 0.f; f[9] = -3.f;
+ f[10]= -4.f; f[11]= 6.f; f[12]= 20.f; f[13]= 6.f; f[14]= -4.f;
+ f[15]= -3.f; f[16]= 0.f; f[17]= 6.f; f[18]= 0.f; f[19]= -3.f;
+ f[20]= -1.f; f[21]= -3.f; f[22]= -4.f; f[23]= -3.f; f[24]= -1.f;
+
+ if (mUseIntrinsic) {
+ mIntrinsic = ScriptIntrinsicConvolve5x5.create(mRS, Element.U8_4(mRS));
+ mIntrinsic.setCoefficients(f);
+ mIntrinsic.setInput(mInPixelsAllocation);
+ } else {
+ mScript = new ScriptC_convolve5x5(mRS, res, R.raw.convolve5x5);
+ mScript.set_gCoeffs(f);
+ mScript.set_gIn(mInPixelsAllocation);
+ mScript.set_gWidth(mWidth);
+ mScript.set_gHeight(mHeight);
+ }
+ }
+
+ public void runTest() {
+ if (mUseIntrinsic) {
+ mIntrinsic.forEach(mOutPixelsAllocation);
+ } else {
+ mScript.forEach_root(mOutPixelsAllocation);
+ }
+ }
+
+}
diff --git a/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Copy.java b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Copy.java
new file mode 100644
index 0000000..ef71907
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Copy.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.rs.image2;
+
+import java.lang.Math;
+
+import android.support.v8.renderscript.*;
+import android.util.Log;
+
+public class Copy extends TestBase {
+ private ScriptC_copy mScript;
+
+ public void createTest(android.content.res.Resources res) {
+ mScript = new ScriptC_copy(mRS, res, R.raw.copy);
+ }
+
+ public void runTest() {
+ mScript.forEach_root(mInPixelsAllocation, mOutPixelsAllocation);
+ }
+
+}
diff --git a/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/CrossProcess.java b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/CrossProcess.java
new file mode 100644
index 0000000..96787d7
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/CrossProcess.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.rs.image2;
+
+import java.lang.Math;
+
+import android.support.v8.renderscript.*;
+import android.util.Log;
+
+public class CrossProcess extends TestBase {
+ private ScriptIntrinsicLUT mIntrinsic;
+
+ public void createTest(android.content.res.Resources res) {
+ mIntrinsic = ScriptIntrinsicLUT.create(mRS, Element.U8_4(mRS));
+ for (int ct=0; ct < 256; ct++) {
+ float f = ((float)ct) / 255.f;
+
+ float r = f;
+ if (r < 0.5f) {
+ r = 4.0f * r * r * r;
+ } else {
+ r = 1.0f - r;
+ r = 1.0f - (4.0f * r * r * r);
+ }
+ mIntrinsic.setRed(ct, (int)(r * 255.f + 0.5f));
+
+ float g = f;
+ if (g < 0.5f) {
+ g = 2.0f * g * g;
+ } else {
+ g = 1.0f - g;
+ g = 1.0f - (2.0f * g * g);
+ }
+ mIntrinsic.setGreen(ct, (int)(g * 255.f + 0.5f));
+
+ float b = f * 0.5f + 0.25f;
+ mIntrinsic.setBlue(ct, (int)(b * 255.f + 0.5f));
+ }
+
+ }
+
+ public void runTest() {
+ mIntrinsic.forEach(mInPixelsAllocation, mOutPixelsAllocation);
+ }
+
+}
diff --git a/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Exposure.java b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Exposure.java
new file mode 100644
index 0000000..deb6b46
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Exposure.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.rs.image2;
+
+import java.lang.Math;
+
+import android.support.v8.renderscript.*;
+
+public class Exposure extends TestBase {
+ private ScriptC_exposure mScript;
+
+ public void createTest(android.content.res.Resources res) {
+ mScript = new ScriptC_exposure(mRS);
+ }
+
+ public void runTest() {
+ mScript.invoke_setBright(50.f);
+ mScript.forEach_exposure(mInPixelsAllocation, mOutPixelsAllocation);
+ }
+
+}
diff --git a/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Fisheye.java b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Fisheye.java
new file mode 100644
index 0000000..97beb88
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Fisheye.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.rs.image2;
+
+import android.support.v8.renderscript.*;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+public class Fisheye extends TestBase {
+ private ScriptC_fisheye_full mScript_full = null;
+ private ScriptC_fisheye_relaxed mScript_relaxed = null;
+ private ScriptC_fisheye_approx_full mScript_approx_full = null;
+ private ScriptC_fisheye_approx_relaxed mScript_approx_relaxed = null;
+ private final boolean approx;
+ private final boolean relaxed;
+ private float center_x = 0.5f;
+ private float center_y = 0.5f;
+ private float scale = 0.5f;
+
+ public Fisheye(boolean approx, boolean relaxed) {
+ this.approx = approx;
+ this.relaxed = relaxed;
+ }
+
+ public boolean onBar1Setup(SeekBar b, TextView t) {
+ t.setText("Scale");
+ b.setMax(100);
+ b.setProgress(25);
+ return true;
+ }
+ public boolean onBar2Setup(SeekBar b, TextView t) {
+ t.setText("Shift center X");
+ b.setMax(100);
+ b.setProgress(50);
+ return true;
+ }
+ public boolean onBar3Setup(SeekBar b, TextView t) {
+ t.setText("Shift center Y");
+ b.setMax(100);
+ b.setProgress(50);
+ return true;
+ }
+
+ public void onBar1Changed(int progress) {
+ scale = progress / 50.0f;
+ do_init();
+ }
+ public void onBar2Changed(int progress) {
+ center_x = progress / 100.0f;
+ do_init();
+ }
+ public void onBar3Changed(int progress) {
+ center_y = progress / 100.0f;
+ do_init();
+ }
+
+ private void do_init() {
+ if (approx) {
+ if (relaxed)
+ mScript_approx_relaxed.invoke_init_filter(
+ mInPixelsAllocation.getType().getX(),
+ mInPixelsAllocation.getType().getY(), center_x,
+ center_y, scale);
+ else
+ mScript_approx_full.invoke_init_filter(
+ mInPixelsAllocation.getType().getX(),
+ mInPixelsAllocation.getType().getY(), center_x,
+ center_y, scale);
+ } else if (relaxed)
+ mScript_relaxed.invoke_init_filter(
+ mInPixelsAllocation.getType().getX(),
+ mInPixelsAllocation.getType().getY(), center_x, center_y,
+ scale);
+ else
+ mScript_full.invoke_init_filter(
+ mInPixelsAllocation.getType().getX(),
+ mInPixelsAllocation.getType().getY(), center_x, center_y,
+ scale);
+ }
+
+ public void createTest(android.content.res.Resources res) {
+ if (approx) {
+ if (relaxed) {
+ mScript_approx_relaxed = new ScriptC_fisheye_approx_relaxed(mRS,
+ res, R.raw.fisheye_approx_relaxed);
+ mScript_approx_relaxed.set_in_alloc(mInPixelsAllocation);
+ mScript_approx_relaxed.set_sampler(Sampler.CLAMP_LINEAR(mRS));
+ } else {
+ mScript_approx_full = new ScriptC_fisheye_approx_full(mRS, res,
+ R.raw.fisheye_approx_full);
+ mScript_approx_full.set_in_alloc(mInPixelsAllocation);
+ mScript_approx_full.set_sampler(Sampler.CLAMP_LINEAR(mRS));
+ }
+ } else if (relaxed) {
+ mScript_relaxed = new ScriptC_fisheye_relaxed(mRS, res,
+ R.raw.fisheye_relaxed);
+ mScript_relaxed.set_in_alloc(mInPixelsAllocation);
+ mScript_relaxed.set_sampler(Sampler.CLAMP_LINEAR(mRS));
+ } else {
+ mScript_full = new ScriptC_fisheye_full(mRS, res,
+ R.raw.fisheye_full);
+ mScript_full.set_in_alloc(mInPixelsAllocation);
+ mScript_full.set_sampler(Sampler.CLAMP_LINEAR(mRS));
+ }
+ do_init();
+ }
+
+ public void runTest() {
+ if (approx) {
+ if (relaxed)
+ mScript_approx_relaxed.forEach_root(mOutPixelsAllocation);
+ else
+ mScript_approx_full.forEach_root(mOutPixelsAllocation);
+ } else if (relaxed)
+ mScript_relaxed.forEach_root(mOutPixelsAllocation);
+ else
+ mScript_full.forEach_root(mOutPixelsAllocation);
+ }
+
+}
+
diff --git a/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Grain.java b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Grain.java
new file mode 100644
index 0000000..dfd3c32
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Grain.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.rs.image2;
+
+import java.lang.Math;
+
+import android.support.v8.renderscript.*;
+import android.util.Log;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+public class Grain extends TestBase {
+ private ScriptC_grain mScript;
+ private Allocation mNoise;
+ private Allocation mNoise2;
+
+
+ public boolean onBar1Setup(SeekBar b, TextView t) {
+ t.setText("Strength");
+ b.setProgress(50);
+ return true;
+ }
+
+ public void onBar1Changed(int progress) {
+ float s = progress / 100.0f;
+ mScript.set_gNoiseStrength(s);
+ }
+
+ private int findHighBit(int v) {
+ int bit = 0;
+ while (v > 1) {
+ bit++;
+ v >>= 1;
+ }
+ return bit;
+ }
+
+
+ public void createTest(android.content.res.Resources res) {
+ int width = mInPixelsAllocation.getType().getX();
+ int height = mInPixelsAllocation.getType().getY();
+
+ int noiseW = findHighBit(width);
+ int noiseH = findHighBit(height);
+ if (noiseW > 9) {
+ noiseW = 9;
+ }
+ if (noiseH > 9) {
+ noiseH = 9;
+ }
+ noiseW = 1 << noiseW;
+ noiseH = 1 << noiseH;
+
+ Type.Builder tb = new Type.Builder(mRS, Element.U8(mRS));
+ tb.setX(noiseW);
+ tb.setY(noiseH);
+ mNoise = Allocation.createTyped(mRS, tb.create());
+ mNoise2 = Allocation.createTyped(mRS, tb.create());
+
+ mScript = new ScriptC_grain(mRS, res, R.raw.grain);
+ mScript.set_gWMask(noiseW - 1);
+ mScript.set_gHMask(noiseH - 1);
+ mScript.set_gNoiseStrength(0.5f);
+ mScript.set_gBlendSource(mNoise);
+ mScript.set_gNoise(mNoise2);
+ }
+
+ public void runTest() {
+ mScript.forEach_genRand(mNoise);
+ mScript.forEach_blend9(mNoise2);
+ mScript.forEach_root(mInPixelsAllocation, mOutPixelsAllocation);
+ }
+
+}
+
diff --git a/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Greyscale.java b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Greyscale.java
new file mode 100644
index 0000000..5b16e24
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Greyscale.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.rs.image2;
+
+import android.util.Log;
+
+public class Greyscale extends TestBase {
+ private ScriptC_greyscale mScript;
+
+ public void createTest(android.content.res.Resources res) {
+ mScript = new ScriptC_greyscale(mRS, res, R.raw.greyscale);
+ }
+
+ public void runTest() {
+ mScript.forEach_root(mInPixelsAllocation, mOutPixelsAllocation);
+ }
+
+}
diff --git a/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/GroupTest.java b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/GroupTest.java
new file mode 100644
index 0000000..a7ceebe
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/GroupTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.rs.image2;
+
+import java.lang.Math;
+
+import android.support.v8.renderscript.*;
+import android.util.Log;
+
+public class GroupTest extends TestBase {
+ private ScriptIntrinsicConvolve3x3 mConvolve;
+ private ScriptIntrinsicColorMatrix mMatrix;
+
+ private Allocation mScratchPixelsAllocation1;
+ private ScriptGroup mGroup;
+
+ private int mWidth;
+ private int mHeight;
+ private boolean mUseNative;
+
+
+ public GroupTest(boolean useNative) {
+ mUseNative = useNative;
+ }
+
+ public void createTest(android.content.res.Resources res) {
+ mWidth = mInPixelsAllocation.getType().getX();
+ mHeight = mInPixelsAllocation.getType().getY();
+
+ mConvolve = ScriptIntrinsicConvolve3x3.create(mRS, Element.U8_4(mRS));
+ mMatrix = ScriptIntrinsicColorMatrix.create(mRS, Element.U8_4(mRS));
+
+ float f[] = new float[9];
+ f[0] = 0.f; f[1] = -1.f; f[2] = 0.f;
+ f[3] = -1.f; f[4] = 5.f; f[5] = -1.f;
+ f[6] = 0.f; f[7] = -1.f; f[8] = 0.f;
+ mConvolve.setCoefficients(f);
+
+ Matrix4f m = new Matrix4f();
+ m.set(1, 0, 0.2f);
+ m.set(1, 1, 0.9f);
+ m.set(1, 2, 0.2f);
+ mMatrix.setColorMatrix(m);
+
+ Type.Builder tb = new Type.Builder(mRS, Element.U8_4(mRS));
+ tb.setX(mWidth);
+ tb.setY(mHeight);
+ Type connect = tb.create();
+
+ if (mUseNative) {
+ ScriptGroup.Builder b = new ScriptGroup.Builder(mRS);
+ b.addKernel(mConvolve.getKernelID());
+ b.addKernel(mMatrix.getKernelID());
+ b.addConnection(connect, mConvolve.getKernelID(), mMatrix.getKernelID());
+ mGroup = b.create();
+ } else {
+ mScratchPixelsAllocation1 = Allocation.createTyped(mRS, connect);
+ }
+ }
+
+ public void runTest() {
+ mConvolve.setInput(mInPixelsAllocation);
+ if (mUseNative) {
+ mGroup.setOutput(mMatrix.getKernelID(), mOutPixelsAllocation);
+ mGroup.execute();
+ } else {
+ mConvolve.forEach(mScratchPixelsAllocation1);
+ mMatrix.forEach(mScratchPixelsAllocation1, mOutPixelsAllocation);
+ }
+ }
+
+}
diff --git a/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/ImageProcessingActivity2.java b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/ImageProcessingActivity2.java
new file mode 100644
index 0000000..4b0e2dd
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/ImageProcessingActivity2.java
@@ -0,0 +1,470 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.rs.image2;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.graphics.BitmapFactory;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.support.v8.renderscript.*;
+import android.view.SurfaceView;
+import android.view.SurfaceHolder;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.SeekBar;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.view.View;
+import android.util.Log;
+import java.lang.Math;
+
+import android.os.Environment;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+public class ImageProcessingActivity2 extends Activity
+ implements SeekBar.OnSeekBarChangeListener {
+ private final String TAG = "Img";
+ public final String RESULT_FILE = "image_processing_result.csv";
+
+ RenderScript mRS;
+ Allocation mInPixelsAllocation;
+ Allocation mInPixelsAllocation2;
+ Allocation mOutPixelsAllocation;
+
+ /**
+ * Define enum type for test names
+ */
+ public enum TestName {
+ // totally there are 38 test cases
+ LEVELS_VEC3_RELAXED ("Levels Vec3 Relaxed"),
+ LEVELS_VEC4_RELAXED ("Levels Vec4 Relaxed"),
+ LEVELS_VEC3_FULL ("Levels Vec3 Full"),
+ LEVELS_VEC4_FULL ("Levels Vec4 Full"),
+ BLUR_RADIUS_25 ("Blur radius 25"),
+ INTRINSIC_BLUE_RADIUS_25 ("Intrinsic Blur radius 25"),
+ GREYSCALE ("Greyscale"),
+ GRAIN ("Grain"),
+ FISHEYE_FULL ("Fisheye Full"),
+ FISHEYE_RELAXED ("Fisheye Relaxed"),
+ FISHEYE_APPROXIMATE_FULL ("Fisheye Approximate Full"),
+ FISHEYE_APPROXIMATE_RELAXED ("Fisheye Approximate Relaxed"),
+ VIGNETTE_FULL ("Vignette Full"),
+ VIGNETTE_RELAXED ("Vignette Relaxed"),
+ VIGNETTE_APPROXIMATE_FULL ("Vignette Approximate Full"),
+ VIGNETTE_APPROXIMATE_RELAXED ("Vignette Approximate Relaxed"),
+ GROUP_TEST_EMULATED ("Group Test (emulated)"),
+ GROUP_TEST_NATIVE ("Group Test (native)"),
+ CONVOLVE_3X3 ("Convolve 3x3"),
+ INTRINSICS_CONVOLVE_3X3 ("Intrinsics Convolve 3x3"),
+ COLOR_MATRIX ("ColorMatrix"),
+ INTRINSICS_COLOR_MATRIX ("Intrinsics ColorMatrix"),
+ INTRINSICS_COLOR_MATRIX_GREY ("Intrinsics ColorMatrix Grey"),
+ COPY ("Copy"),
+ CROSS_PROCESS_USING_LUT ("CrossProcess (using LUT)"),
+ CONVOLVE_5X5 ("Convolve 5x5"),
+ INTRINSICS_CONVOLVE_5X5 ("Intrinsics Convolve 5x5"),
+ MANDELBROT ("Mandelbrot"),
+ INTRINSICS_BLEND ("Intrinsics Blend"),
+ INTRINSICS_BLUR_25G ("Intrinsics Blur 25 uchar"),
+ VIBRANCE ("Vibrance"),
+ BW_FILTER ("BW Filter"),
+ SHADOWS ("Shadows"),
+ CONTRAST ("Contrast"),
+ EXPOSURE ("Exposure"),
+ WHITE_BALANCE ("White Balance"),
+ COLOR_CUBE ("Color Cube"),
+ COLOR_CUBE_3D_INTRINSIC ("Color Cube (3D LUT intrinsic)");
+
+
+ private final String name;
+
+ private TestName(String s) {
+ name = s;
+ }
+
+ // return quoted string as displayed test name
+ public String toString() {
+ return name;
+ }
+ }
+
+ Bitmap mBitmapIn;
+ Bitmap mBitmapIn2;
+ Bitmap mBitmapOut;
+
+ private Spinner mSpinner;
+ private SeekBar mBar1;
+ private SeekBar mBar2;
+ private SeekBar mBar3;
+ private SeekBar mBar4;
+ private SeekBar mBar5;
+ private TextView mText1;
+ private TextView mText2;
+ private TextView mText3;
+ private TextView mText4;
+ private TextView mText5;
+
+ private float mSaturation = 1.0f;
+
+ private TextView mBenchmarkResult;
+ private Spinner mTestSpinner;
+
+ private SurfaceView mSurfaceView;
+ private ImageView mDisplayView;
+
+ private boolean mDoingBenchmark;
+
+ private TestBase mTest;
+ private int mRunCount;
+
+ public void updateDisplay() {
+ mTest.updateBitmap(mBitmapOut);
+ mDisplayView.invalidate();
+ }
+
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (fromUser) {
+
+ if (seekBar == mBar1) {
+ mTest.onBar1Changed(progress);
+ } else if (seekBar == mBar2) {
+ mTest.onBar2Changed(progress);
+ } else if (seekBar == mBar3) {
+ mTest.onBar3Changed(progress);
+ } else if (seekBar == mBar4) {
+ mTest.onBar4Changed(progress);
+ } else if (seekBar == mBar5) {
+ mTest.onBar5Changed(progress);
+ }
+
+ mTest.runTest();
+ updateDisplay();
+ }
+ }
+
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ }
+
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ }
+
+ void setupBars() {
+ mSpinner.setVisibility(View.VISIBLE);
+ mTest.onSpinner1Setup(mSpinner);
+
+ mBar1.setVisibility(View.VISIBLE);
+ mText1.setVisibility(View.VISIBLE);
+ mTest.onBar1Setup(mBar1, mText1);
+
+ mBar2.setVisibility(View.VISIBLE);
+ mText2.setVisibility(View.VISIBLE);
+ mTest.onBar2Setup(mBar2, mText2);
+
+ mBar3.setVisibility(View.VISIBLE);
+ mText3.setVisibility(View.VISIBLE);
+ mTest.onBar3Setup(mBar3, mText3);
+
+ mBar4.setVisibility(View.VISIBLE);
+ mText4.setVisibility(View.VISIBLE);
+ mTest.onBar4Setup(mBar4, mText4);
+
+ mBar5.setVisibility(View.VISIBLE);
+ mText5.setVisibility(View.VISIBLE);
+ mTest.onBar5Setup(mBar5, mText5);
+ }
+
+
+ void changeTest(TestName testName) {
+ if (mTest != null) {
+ mTest.destroy();
+ }
+ switch(testName) {
+ case LEVELS_VEC3_RELAXED:
+ mTest = new LevelsV4(false, false);
+ break;
+ case LEVELS_VEC4_RELAXED:
+ mTest = new LevelsV4(false, true);
+ break;
+ case LEVELS_VEC3_FULL:
+ mTest = new LevelsV4(true, false);
+ break;
+ case LEVELS_VEC4_FULL:
+ mTest = new LevelsV4(true, true);
+ break;
+ case BLUR_RADIUS_25:
+ mTest = new Blur25(false);
+ break;
+ case INTRINSIC_BLUE_RADIUS_25:
+ mTest = new Blur25(true);
+ break;
+ case GREYSCALE:
+ mTest = new Greyscale();
+ break;
+ case GRAIN:
+ mTest = new Grain();
+ break;
+ case FISHEYE_FULL:
+ mTest = new Fisheye(false, false);
+ break;
+ case FISHEYE_RELAXED:
+ mTest = new Fisheye(false, true);
+ break;
+ case FISHEYE_APPROXIMATE_FULL:
+ mTest = new Fisheye(true, false);
+ break;
+ case FISHEYE_APPROXIMATE_RELAXED:
+ mTest = new Fisheye(true, true);
+ break;
+ case VIGNETTE_FULL:
+ mTest = new Vignette(false, false);
+ break;
+ case VIGNETTE_RELAXED:
+ mTest = new Vignette(false, true);
+ break;
+ case VIGNETTE_APPROXIMATE_FULL:
+ mTest = new Vignette(true, false);
+ break;
+ case VIGNETTE_APPROXIMATE_RELAXED:
+ mTest = new Vignette(true, true);
+ break;
+ case GROUP_TEST_EMULATED:
+ mTest = new GroupTest(false);
+ break;
+ case GROUP_TEST_NATIVE:
+ mTest = new GroupTest(true);
+ break;
+ case CONVOLVE_3X3:
+ mTest = new Convolve3x3(false);
+ break;
+ case INTRINSICS_CONVOLVE_3X3:
+ mTest = new Convolve3x3(true);
+ break;
+ case COLOR_MATRIX:
+ mTest = new ColorMatrix(false, false);
+ break;
+ case INTRINSICS_COLOR_MATRIX:
+ mTest = new ColorMatrix(true, false);
+ break;
+ case INTRINSICS_COLOR_MATRIX_GREY:
+ mTest = new ColorMatrix(true, true);
+ break;
+ case COPY:
+ mTest = new Copy();
+ break;
+ case CROSS_PROCESS_USING_LUT:
+ mTest = new CrossProcess();
+ break;
+ case CONVOLVE_5X5:
+ mTest = new Convolve5x5(false);
+ break;
+ case INTRINSICS_CONVOLVE_5X5:
+ mTest = new Convolve5x5(true);
+ break;
+ case MANDELBROT:
+ mTest = new Mandelbrot();
+ break;
+ case INTRINSICS_BLEND:
+ mTest = new Blend();
+ break;
+ case INTRINSICS_BLUR_25G:
+ mTest = new Blur25G();
+ break;
+ case VIBRANCE:
+ mTest = new Vibrance();
+ break;
+ case BW_FILTER:
+ mTest = new BWFilter();
+ break;
+ case SHADOWS:
+ mTest = new Shadows();
+ break;
+ case CONTRAST:
+ mTest = new Contrast();
+ break;
+ case EXPOSURE:
+ mTest = new Exposure();
+ break;
+ case WHITE_BALANCE:
+ mTest = new WhiteBalance();
+ break;
+ case COLOR_CUBE:
+ mTest = new ColorCube(false);
+ break;
+ case COLOR_CUBE_3D_INTRINSIC:
+ mTest = new ColorCube(true);
+ break;
+ }
+
+ mTest.createBaseTest(this, mBitmapIn, mBitmapIn2, mBitmapOut);
+ setupBars();
+
+ mTest.runTest();
+ updateDisplay();
+ mBenchmarkResult.setText("Result: not run");
+ }
+
+ void setupTests() {
+ mTestSpinner.setAdapter(new ArrayAdapter<TestName>(
+ this, R.layout.spinner_layout, TestName.values()));
+ }
+
+ private AdapterView.OnItemSelectedListener mTestSpinnerListener =
+ new AdapterView.OnItemSelectedListener() {
+ public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
+ changeTest(TestName.values()[pos]);
+ }
+
+ public void onNothingSelected(AdapterView parent) {
+
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ mBitmapIn = loadBitmap(R.drawable.img1600x1067);
+ mBitmapIn2 = loadBitmap(R.drawable.img1600x1067b);
+ mBitmapOut = Bitmap.createBitmap(mBitmapIn.getWidth(), mBitmapIn.getHeight(),
+ mBitmapIn.getConfig());
+
+ mSurfaceView = (SurfaceView) findViewById(R.id.surface);
+
+ mDisplayView = (ImageView) findViewById(R.id.display);
+ mDisplayView.setImageBitmap(mBitmapOut);
+
+ mSpinner = (Spinner) findViewById(R.id.spinner1);
+
+ mBar1 = (SeekBar) findViewById(R.id.slider1);
+ mBar2 = (SeekBar) findViewById(R.id.slider2);
+ mBar3 = (SeekBar) findViewById(R.id.slider3);
+ mBar4 = (SeekBar) findViewById(R.id.slider4);
+ mBar5 = (SeekBar) findViewById(R.id.slider5);
+
+ mBar1.setOnSeekBarChangeListener(this);
+ mBar2.setOnSeekBarChangeListener(this);
+ mBar3.setOnSeekBarChangeListener(this);
+ mBar4.setOnSeekBarChangeListener(this);
+ mBar5.setOnSeekBarChangeListener(this);
+
+ mText1 = (TextView) findViewById(R.id.slider1Text);
+ mText2 = (TextView) findViewById(R.id.slider2Text);
+ mText3 = (TextView) findViewById(R.id.slider3Text);
+ mText4 = (TextView) findViewById(R.id.slider4Text);
+ mText5 = (TextView) findViewById(R.id.slider5Text);
+
+ mTestSpinner = (Spinner) findViewById(R.id.filterselection);
+ mTestSpinner.setOnItemSelectedListener(mTestSpinnerListener);
+
+ mBenchmarkResult = (TextView) findViewById(R.id.benchmarkText);
+ mBenchmarkResult.setText("Result: not run");
+
+
+ mRS = RenderScript.create(this);
+ mInPixelsAllocation = Allocation.createFromBitmap(mRS, mBitmapIn);
+ mInPixelsAllocation2 = Allocation.createFromBitmap(mRS, mBitmapIn2);
+ mOutPixelsAllocation = Allocation.createFromBitmap(mRS, mBitmapOut);
+
+
+ setupTests();
+ changeTest(TestName.LEVELS_VEC3_RELAXED);
+ }
+
+
+ private Bitmap loadBitmap(int resource) {
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+ return BitmapFactory.decodeResource(getResources(), resource, options);
+ }
+
+ // button hook
+ public void benchmark(View v) {
+ float t = getBenchmark();
+ //long javaTime = javaFilter();
+ //mBenchmarkResult.setText("RS: " + t + " ms Java: " + javaTime + " ms");
+ mBenchmarkResult.setText("Result: " + t + " ms");
+ Log.v(TAG, "getBenchmark: Renderscript frame time core ms " + t);
+ }
+
+ public void benchmark_all(View v) {
+ // write result into a file
+ File externalStorage = Environment.getExternalStorageDirectory();
+ if (!externalStorage.canWrite()) {
+ Log.v(TAG, "sdcard is not writable");
+ return;
+ }
+ File resultFile = new File(externalStorage, RESULT_FILE);
+ //resultFile.setWritable(true, false);
+ try {
+ BufferedWriter rsWriter = new BufferedWriter(new FileWriter(resultFile));
+ Log.v(TAG, "Saved results in: " + resultFile.getAbsolutePath());
+ for (TestName tn: TestName.values()) {
+ changeTest(tn);
+ float t = getBenchmark();
+ String s = new String("" + tn.toString() + ", " + t);
+ rsWriter.write(s + "\n");
+ Log.v(TAG, "Test " + s + "ms\n");
+ }
+ rsWriter.close();
+ } catch (IOException e) {
+ Log.v(TAG, "Unable to write result file " + e.getMessage());
+ }
+ changeTest(TestName.LEVELS_VEC3_RELAXED);
+ }
+
+ // For benchmark test
+ public float getBenchmark() {
+ mDoingBenchmark = true;
+
+ mTest.setupBenchmark();
+ long result = 0;
+
+ //Log.v(TAG, "Warming");
+ long t = java.lang.System.currentTimeMillis() + 250;
+ do {
+ mTest.runTest();
+ mTest.finish();
+ } while (t > java.lang.System.currentTimeMillis());
+
+ //Log.v(TAG, "Benchmarking");
+ int ct = 0;
+ t = java.lang.System.currentTimeMillis();
+ do {
+ mTest.runTest();
+ mTest.finish();
+ ct++;
+ } while ((t+1000) > java.lang.System.currentTimeMillis());
+ t = java.lang.System.currentTimeMillis() - t;
+ float ft = (float)t;
+ ft /= ct;
+
+ mTest.exitBenchmark();
+ mDoingBenchmark = false;
+
+ return ft;
+ }
+}
diff --git a/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/LevelsV4.java b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/LevelsV4.java
new file mode 100644
index 0000000..fbe3727
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/LevelsV4.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.rs.image2;
+
+import java.lang.Math;
+
+import android.support.v8.renderscript.*;
+import android.util.Log;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+
+public class LevelsV4 extends TestBase {
+ private ScriptC_levels_relaxed mScriptR;
+ private ScriptC_levels_full mScriptF;
+ private float mInBlack = 0.0f;
+ private float mOutBlack = 0.0f;
+ private float mInWhite = 255.0f;
+ private float mOutWhite = 255.0f;
+ private float mSaturation = 1.0f;
+
+ Matrix3f satMatrix = new Matrix3f();
+ float mInWMinInB;
+ float mOutWMinOutB;
+ float mOverInWMinInB;
+
+ boolean mUseFull;
+ boolean mUseV4;
+
+ LevelsV4(boolean useFull, boolean useV4) {
+ mUseFull = useFull;
+ mUseV4 = useV4;
+ }
+
+
+ private void setLevels() {
+ mInWMinInB = mInWhite - mInBlack;
+ mOutWMinOutB = mOutWhite - mOutBlack;
+ mOverInWMinInB = 1.f / mInWMinInB;
+
+ mScriptR.set_inBlack(mInBlack);
+ mScriptR.set_outBlack(mOutBlack);
+ mScriptR.set_inWMinInB(mInWMinInB);
+ mScriptR.set_outWMinOutB(mOutWMinOutB);
+ mScriptR.set_overInWMinInB(mOverInWMinInB);
+ mScriptF.set_inBlack(mInBlack);
+ mScriptF.set_outBlack(mOutBlack);
+ mScriptF.set_inWMinInB(mInWMinInB);
+ mScriptF.set_outWMinOutB(mOutWMinOutB);
+ mScriptF.set_overInWMinInB(mOverInWMinInB);
+ }
+
+ private void setSaturation() {
+ float rWeight = 0.299f;
+ float gWeight = 0.587f;
+ float bWeight = 0.114f;
+ float oneMinusS = 1.0f - mSaturation;
+
+ satMatrix.set(0, 0, oneMinusS * rWeight + mSaturation);
+ satMatrix.set(0, 1, oneMinusS * rWeight);
+ satMatrix.set(0, 2, oneMinusS * rWeight);
+ satMatrix.set(1, 0, oneMinusS * gWeight);
+ satMatrix.set(1, 1, oneMinusS * gWeight + mSaturation);
+ satMatrix.set(1, 2, oneMinusS * gWeight);
+ satMatrix.set(2, 0, oneMinusS * bWeight);
+ satMatrix.set(2, 1, oneMinusS * bWeight);
+ satMatrix.set(2, 2, oneMinusS * bWeight + mSaturation);
+ mScriptR.set_colorMat(satMatrix);
+ mScriptF.set_colorMat(satMatrix);
+ }
+
+ public boolean onBar1Setup(SeekBar b, TextView t) {
+ b.setProgress(50);
+ t.setText("Saturation");
+ return true;
+ }
+ public boolean onBar2Setup(SeekBar b, TextView t) {
+ b.setMax(128);
+ b.setProgress(0);
+ t.setText("In Black");
+ return true;
+ }
+ public boolean onBar3Setup(SeekBar b, TextView t) {
+ b.setMax(128);
+ b.setProgress(0);
+ t.setText("Out Black");
+ return true;
+ }
+ public boolean onBar4Setup(SeekBar b, TextView t) {
+ b.setMax(128);
+ b.setProgress(128);
+ t.setText("Out White");
+ return true;
+ }
+ public boolean onBar5Setup(SeekBar b, TextView t) {
+ b.setMax(128);
+ b.setProgress(128);
+ t.setText("Out White");
+ return true;
+ }
+
+ public void onBar1Changed(int progress) {
+ mSaturation = (float)progress / 50.0f;
+ setSaturation();
+ }
+ public void onBar2Changed(int progress) {
+ mInBlack = (float)progress;
+ setLevels();
+ }
+ public void onBar3Changed(int progress) {
+ mOutBlack = (float)progress;
+ setLevels();
+ }
+ public void onBar4Changed(int progress) {
+ mInWhite = (float)progress + 127.0f;
+ setLevels();
+ }
+ public void onBar5Changed(int progress) {
+ mOutWhite = (float)progress + 127.0f;
+ setLevels();
+ }
+
+ public void createTest(android.content.res.Resources res) {
+ mScriptR = new ScriptC_levels_relaxed(mRS, res, R.raw.levels_relaxed);
+ mScriptF = new ScriptC_levels_full(mRS, res, R.raw.levels_full);
+ setSaturation();
+ setLevels();
+ }
+
+ public void runTest() {
+ if (mUseFull) {
+ if (mUseV4) {
+ mScriptF.forEach_root4(mInPixelsAllocation, mOutPixelsAllocation);
+ } else {
+ mScriptF.forEach_root(mInPixelsAllocation, mOutPixelsAllocation);
+ }
+ } else {
+ if (mUseV4) {
+ mScriptR.forEach_root4(mInPixelsAllocation, mOutPixelsAllocation);
+ } else {
+ mScriptR.forEach_root(mInPixelsAllocation, mOutPixelsAllocation);
+ }
+ }
+ }
+
+}
+
diff --git a/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Mandelbrot.java b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Mandelbrot.java
new file mode 100644
index 0000000..1780587
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Mandelbrot.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.rs.image2;
+
+import java.lang.Math;
+
+import android.util.Log;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+public class Mandelbrot extends TestBase {
+ private ScriptC_mandelbrot mScript;
+
+ public boolean onBar1Setup(SeekBar b, TextView t) {
+ t.setText("Iterations");
+ b.setProgress(0);
+ return true;
+ }
+
+ public void onBar1Changed(int progress) {
+ int iters = progress * 3 + 50;
+ mScript.set_gMaxIteration(iters);
+ }
+
+ public boolean onBar2Setup(SeekBar b, TextView t) {
+ t.setText("Lower Bound: X");
+ b.setProgress(0);
+ return true;
+ }
+
+ public void onBar2Changed(int progress) {
+ float scaleFactor = mScript.get_scaleFactor();
+ // allow viewport to be moved by 2x scale factor
+ float lowerBoundX = -2.f + ((progress / scaleFactor) / 50.f);
+ mScript.set_lowerBoundX(lowerBoundX);
+ }
+
+ public boolean onBar3Setup(SeekBar b, TextView t) {
+ t.setText("Lower Bound: Y");
+ b.setProgress(0);
+ return true;
+ }
+
+ public void onBar3Changed(int progress) {
+ float scaleFactor = mScript.get_scaleFactor();
+ // allow viewport to be moved by 2x scale factor
+ float lowerBoundY = -2.f + ((progress / scaleFactor) / 50.f);
+ mScript.set_lowerBoundY(lowerBoundY);
+ }
+
+ public boolean onBar4Setup(SeekBar b, TextView t) {
+ t.setText("Scale Factor");
+ b.setProgress(0);
+ return true;
+ }
+
+ public void onBar4Changed(int progress) {
+ float scaleFactor = 4.f - (3.96f * (progress / 100.f));
+ mScript.set_scaleFactor(scaleFactor);
+ }
+
+ public void createTest(android.content.res.Resources res) {
+ int width = mOutPixelsAllocation.getType().getX();
+ int height = mOutPixelsAllocation.getType().getY();
+
+ mScript = new ScriptC_mandelbrot(mRS, res, R.raw.mandelbrot);
+ mScript.set_gDimX(width);
+ mScript.set_gDimY(height);
+ mScript.set_gMaxIteration(50);
+ }
+
+ public void runTest() {
+ mScript.forEach_root(mOutPixelsAllocation);
+ mRS.finish();
+ }
+
+}
+
diff --git a/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Shadows.java b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Shadows.java
new file mode 100644
index 0000000..353c56d
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Shadows.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.rs.image2;
+
+
+public class Shadows extends TestBase {
+ private ScriptC_shadows mScript;
+
+ public void createTest(android.content.res.Resources res) {
+ mScript = new ScriptC_shadows(mRS);
+ }
+
+ public void runTest() {
+ mScript.invoke_prepareShadows(50.f);
+ mScript.forEach_shadowsKernel(mInPixelsAllocation, mOutPixelsAllocation);
+ }
+
+}
diff --git a/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/TestBase.java b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/TestBase.java
new file mode 100644
index 0000000..eeabc73
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/TestBase.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.rs.image2;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.graphics.BitmapFactory;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.support.v8.renderscript.*;
+import android.view.SurfaceView;
+import android.view.SurfaceHolder;
+import android.widget.ImageView;
+import android.widget.SeekBar;
+import android.widget.TextView;
+import android.view.View;
+import android.util.Log;
+import java.lang.Math;
+import android.widget.Spinner;
+
+public class TestBase {
+ protected final String TAG = "Img";
+
+ protected RenderScript mRS;
+ protected Allocation mInPixelsAllocation;
+ protected Allocation mInPixelsAllocation2;
+ protected Allocation mOutPixelsAllocation;
+
+ protected ImageProcessingActivity2 act;
+
+ // Override to use UI elements
+ public void onBar1Changed(int progress) {
+ }
+ public void onBar2Changed(int progress) {
+ }
+ public void onBar3Changed(int progress) {
+ }
+ public void onBar4Changed(int progress) {
+ }
+ public void onBar5Changed(int progress) {
+ }
+
+ // Override to use UI elements
+ // Unused bars will be hidden.
+ public boolean onBar1Setup(SeekBar b, TextView t) {
+ b.setVisibility(View.INVISIBLE);
+ t.setVisibility(View.INVISIBLE);
+ return false;
+ }
+ public boolean onBar2Setup(SeekBar b, TextView t) {
+ b.setVisibility(View.INVISIBLE);
+ t.setVisibility(View.INVISIBLE);
+ return false;
+ }
+ public boolean onBar3Setup(SeekBar b, TextView t) {
+ b.setVisibility(View.INVISIBLE);
+ t.setVisibility(View.INVISIBLE);
+ return false;
+ }
+ public boolean onBar4Setup(SeekBar b, TextView t) {
+ b.setVisibility(View.INVISIBLE);
+ t.setVisibility(View.INVISIBLE);
+ return false;
+ }
+ public boolean onBar5Setup(SeekBar b, TextView t) {
+ b.setVisibility(View.INVISIBLE);
+ t.setVisibility(View.INVISIBLE);
+ return false;
+ }
+
+ public boolean onSpinner1Setup(Spinner s) {
+ s.setVisibility(View.INVISIBLE);
+ return false;
+ }
+
+ public final void createBaseTest(ImageProcessingActivity2 ipact, Bitmap b, Bitmap b2, Bitmap outb) {
+ act = ipact;
+ mRS = ipact.mRS;
+
+ mInPixelsAllocation = ipact.mInPixelsAllocation;
+ mInPixelsAllocation2 = ipact.mInPixelsAllocation2;
+ mOutPixelsAllocation = ipact.mOutPixelsAllocation;
+
+ createTest(act.getResources());
+ }
+
+ // Must override
+ public void createTest(android.content.res.Resources res) {
+ }
+
+ // Must override
+ public void runTest() {
+ }
+
+ public void finish() {
+ mRS.finish();
+ }
+
+ public void destroy() {
+ }
+
+ public void updateBitmap(Bitmap b) {
+ mOutPixelsAllocation.copyTo(b);
+ }
+
+ // Override to configure specific benchmark config.
+ public void setupBenchmark() {
+ }
+
+ // Override to reset after benchmark.
+ public void exitBenchmark() {
+ }
+}
diff --git a/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Vibrance.java b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Vibrance.java
new file mode 100644
index 0000000..37c0aa7
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Vibrance.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.rs.image2;
+
+public class Vibrance extends TestBase {
+ private ScriptC_vibrance mScript;
+
+ public void createTest(android.content.res.Resources res) {
+ mScript = new ScriptC_vibrance(mRS);
+ }
+
+ public void runTest() {
+ mScript.set_vibrance(50.f);
+ mScript.invoke_prepareVibrance();
+ mScript.forEach_vibranceKernel(mInPixelsAllocation, mOutPixelsAllocation);
+ }
+
+}
diff --git a/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Vignette.java b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Vignette.java
new file mode 100644
index 0000000..9f6d34d3
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/Vignette.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.rs.image2;
+
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+public class Vignette extends TestBase {
+ private ScriptC_vignette_full mScript_full = null;
+ private ScriptC_vignette_relaxed mScript_relaxed = null;
+ private ScriptC_vignette_approx_full mScript_approx_full = null;
+ private ScriptC_vignette_approx_relaxed mScript_approx_relaxed = null;
+ private final boolean approx;
+ private final boolean relaxed;
+ private float center_x = 0.5f;
+ private float center_y = 0.5f;
+ private float scale = 0.5f;
+ private float shade = 0.5f;
+ private float slope = 20.0f;
+
+ public Vignette(boolean approx, boolean relaxed) {
+ this.approx = approx;
+ this.relaxed = relaxed;
+ }
+
+ public boolean onBar1Setup(SeekBar b, TextView t) {
+ t.setText("Scale");
+ b.setMax(100);
+ b.setProgress(25);
+ return true;
+ }
+ public boolean onBar2Setup(SeekBar b, TextView t) {
+ t.setText("Shade");
+ b.setMax(100);
+ b.setProgress(50);
+ return true;
+ }
+ public boolean onBar3Setup(SeekBar b, TextView t) {
+ t.setText("Slope");
+ b.setMax(100);
+ b.setProgress(20);
+ return true;
+ }
+ public boolean onBar4Setup(SeekBar b, TextView t) {
+ t.setText("Shift center X");
+ b.setMax(100);
+ b.setProgress(50);
+ return true;
+ }
+ public boolean onBar5Setup(SeekBar b, TextView t) {
+ t.setText("Shift center Y");
+ b.setMax(100);
+ b.setProgress(50);
+ return true;
+ }
+
+ public void onBar1Changed(int progress) {
+ scale = progress / 50.0f;
+ do_init();
+ }
+ public void onBar2Changed(int progress) {
+ shade = progress / 100.0f;
+ do_init();
+ }
+ public void onBar3Changed(int progress) {
+ slope = (float)progress;
+ do_init();
+ }
+ public void onBar4Changed(int progress) {
+ center_x = progress / 100.0f;
+ do_init();
+ }
+ public void onBar5Changed(int progress) {
+ center_y = progress / 100.0f;
+ do_init();
+ }
+
+ private void do_init() {
+ if (approx) {
+ if (relaxed)
+ mScript_approx_relaxed.invoke_init_vignette(
+ mInPixelsAllocation.getType().getX(),
+ mInPixelsAllocation.getType().getY(), center_x,
+ center_y, scale, shade, slope);
+ else
+ mScript_approx_full.invoke_init_vignette(
+ mInPixelsAllocation.getType().getX(),
+ mInPixelsAllocation.getType().getY(), center_x,
+ center_y, scale, shade, slope);
+ } else if (relaxed)
+ mScript_relaxed.invoke_init_vignette(
+ mInPixelsAllocation.getType().getX(),
+ mInPixelsAllocation.getType().getY(), center_x, center_y,
+ scale, shade, slope);
+ else
+ mScript_full.invoke_init_vignette(
+ mInPixelsAllocation.getType().getX(),
+ mInPixelsAllocation.getType().getY(), center_x, center_y,
+ scale, shade, slope);
+ }
+
+ public void createTest(android.content.res.Resources res) {
+ if (approx) {
+ if (relaxed)
+ mScript_approx_relaxed = new ScriptC_vignette_approx_relaxed(
+ mRS, res, R.raw.vignette_approx_relaxed);
+ else
+ mScript_approx_full = new ScriptC_vignette_approx_full(
+ mRS, res, R.raw.vignette_approx_full);
+ } else if (relaxed)
+ mScript_relaxed = new ScriptC_vignette_relaxed(mRS, res,
+ R.raw.vignette_relaxed);
+ else
+ mScript_full = new ScriptC_vignette_full(mRS, res,
+ R.raw.vignette_full);
+ do_init();
+ }
+
+ public void runTest() {
+ if (approx) {
+ if (relaxed)
+ mScript_approx_relaxed.forEach_root(mInPixelsAllocation,
+ mOutPixelsAllocation);
+ else
+ mScript_approx_full.forEach_root(mInPixelsAllocation,
+ mOutPixelsAllocation);
+ } else if (relaxed)
+ mScript_relaxed.forEach_root(mInPixelsAllocation,
+ mOutPixelsAllocation);
+ else
+ mScript_full.forEach_root(mInPixelsAllocation,
+ mOutPixelsAllocation);
+ }
+
+}
+
diff --git a/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/WhiteBalance.java b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/WhiteBalance.java
new file mode 100644
index 0000000..658e3b1
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/java/com/android/rs/image/WhiteBalance.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.rs.image2;
+
+import java.lang.Math;
+
+import android.support.v8.renderscript.*;
+
+public class WhiteBalance extends TestBase {
+ private ScriptC_wbalance mScript;
+
+ public void createTest(android.content.res.Resources res) {
+ mScript = new ScriptC_wbalance(mRS);
+ }
+
+ public void runTest() {
+ mScript.set_histogramSource(mInPixelsAllocation);
+ mScript.set_histogramWidth(mInPixelsAllocation.getType().getX());
+ mScript.set_histogramHeight(mInPixelsAllocation.getType().getY());
+ mScript.invoke_prepareWhiteBalance();
+ mScript.forEach_whiteBalanceKernel(mInPixelsAllocation, mOutPixelsAllocation);
+ }
+
+}
diff --git a/build-system/tests/rsSupportMode/src/main/res/drawable-nodpi/city.png b/build-system/tests/rsSupportMode/src/main/res/drawable-nodpi/city.png
new file mode 100644
index 0000000..856eeff
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/res/drawable-nodpi/city.png
Binary files differ
diff --git a/build-system/tests/rsSupportMode/src/main/res/drawable-nodpi/img1600x1067.jpg b/build-system/tests/rsSupportMode/src/main/res/drawable-nodpi/img1600x1067.jpg
new file mode 100644
index 0000000..05d3ee2
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/res/drawable-nodpi/img1600x1067.jpg
Binary files differ
diff --git a/build-system/tests/rsSupportMode/src/main/res/drawable-nodpi/img1600x1067b.jpg b/build-system/tests/rsSupportMode/src/main/res/drawable-nodpi/img1600x1067b.jpg
new file mode 100644
index 0000000..aed0781
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/res/drawable-nodpi/img1600x1067b.jpg
Binary files differ
diff --git a/build-system/tests/rsSupportMode/src/main/res/layout/main.xml b/build-system/tests/rsSupportMode/src/main/res/layout/main.xml
new file mode 100644
index 0000000..f0a2b92
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/res/layout/main.xml
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:id="@+id/toplevel">
+ <SurfaceView
+ android:id="@+id/surface"
+ android:layout_width="1dip"
+ android:layout_height="1dip" />
+ <ScrollView
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+ <ImageView
+ android:id="@+id/display"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/benchmark"
+ android:onClick="benchmark"/>
+ <TextView
+ android:id="@+id/benchmarkText"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="8pt"
+ android:text="@string/saturation"/>
+ </LinearLayout>
+ <Spinner
+ android:id="@+id/filterselection"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"/>
+ <Spinner
+ android:id="@+id/spinner1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"/>
+ <TextView
+ android:id="@+id/slider1Text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="8pt"
+ android:layout_marginLeft="10sp"
+ android:layout_marginTop="15sp"
+ android:text="@string/saturation"/>
+ <SeekBar
+ android:id="@+id/slider1"
+ android:layout_marginLeft="10sp"
+ android:layout_marginRight="10sp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+ <TextView
+ android:id="@+id/slider2Text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="8pt"
+ android:layout_marginLeft="10sp"
+ android:layout_marginTop="15sp"
+ android:text="@string/gamma"/>
+ <SeekBar
+ android:id="@+id/slider2"
+ android:layout_marginLeft="10sp"
+ android:layout_marginRight="10sp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+ <TextView
+ android:id="@+id/slider3Text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="10sp"
+ android:layout_marginTop="15sp"
+ android:textSize="8pt"
+ android:text="@string/out_white"/>
+ <SeekBar
+ android:id="@+id/slider3"
+ android:layout_marginLeft="10sp"
+ android:layout_marginRight="10sp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+ <TextView
+ android:id="@+id/slider4Text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="8pt"
+ android:layout_marginLeft="10sp"
+ android:layout_marginTop="15sp"
+ android:text="@string/in_white"/>
+ <SeekBar
+ android:id="@+id/slider4"
+ android:layout_marginLeft="10sp"
+ android:layout_marginRight="10sp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+ <TextView
+ android:id="@+id/slider5Text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="8pt"
+ android:layout_marginLeft="10sp"
+ android:layout_marginTop="15sp"
+ android:text="@string/in_white"/>
+ <SeekBar
+ android:id="@+id/slider5"
+ android:layout_marginLeft="10sp"
+ android:layout_marginRight="10sp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/benchmark_all"
+ android:onClick="benchmark_all"/>
+ </LinearLayout>
+ </ScrollView>
+</LinearLayout>
+
diff --git a/build-system/tests/rsSupportMode/src/main/res/layout/spinner_layout.xml b/build-system/tests/rsSupportMode/src/main/res/layout/spinner_layout.xml
new file mode 100644
index 0000000..8196bbf
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/res/layout/spinner_layout.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2012 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:padding="10dp"
+ android:textSize="16sp"
+/>
diff --git a/build-system/tests/rsSupportMode/src/main/res/values/strings.xml b/build-system/tests/rsSupportMode/src/main/res/values/strings.xml
new file mode 100644
index 0000000..a7dd165
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/res/values/strings.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- General -->
+ <skip />
+ <!--slider label -->
+ <string name="blur_description">Blur Radius</string>
+ <string name="in_white">In White</string>
+ <string name="out_white">Out White</string>
+ <string name="in_black">In Black</string>
+ <string name="out_black">Out Black</string>
+ <string name="gamma">Gamma</string>
+ <string name="saturation">Saturation</string>
+ <string name="benchmark">Benchmark</string>
+ <string name="benchmark_all">Benchmark All</string>
+
+</resources>
diff --git a/build-system/tests/rsSupportMode/src/main/rs/blend.rs b/build-system/tests/rsSupportMode/src/main/rs/blend.rs
new file mode 100644
index 0000000..9ec1246
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/rs/blend.rs
@@ -0,0 +1,23 @@
+// Copyright (C) 2011 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "ip.rsh"
+
+uchar alpha = 0x0;
+
+void setImageAlpha(uchar4 *v_out, uint32_t x, uint32_t y) {
+ v_out->rgba = convert_uchar4((convert_uint4(v_out->rgba) * alpha) >> (uint4)8);
+ v_out->a = alpha;
+}
+
diff --git a/build-system/tests/rsSupportMode/src/main/rs/bwfilter.rs b/build-system/tests/rsSupportMode/src/main/rs/bwfilter.rs
new file mode 100644
index 0000000..e706d44
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/rs/bwfilter.rs
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ip.rsh"
+//#pragma rs_fp_relaxed
+
+static float sr = 0.f;
+static float sg = 0.f;
+static float sb = 0.f;
+
+void prepareBwFilter(uint32_t rw, uint32_t gw, uint32_t bw) {
+
+ sr = rw;
+ sg = gw;
+ sb = bw;
+
+ float imageMin = min(sg,sb);
+ imageMin = fmin(sr,imageMin);
+ float imageMax = max(sg,sb);
+ imageMax = fmax(sr,imageMax);
+ float avg = (imageMin + imageMax)/2;
+ sb /= avg;
+ sg /= avg;
+ sr /= avg;
+
+}
+
+void bwFilterKernel(const uchar4 *in, uchar4 *out) {
+ float r = in->r * sr;
+ float g = in->g * sg;
+ float b = in->b * sb;
+ float localMin, localMax, avg;
+ localMin = fmin(g,b);
+ localMin = fmin(r,localMin);
+ localMax = fmax(g,b);
+ localMax = fmax(r,localMax);
+ avg = (localMin+localMax) * 0.5f;
+ out->r = out->g = out->b = rsClamp(avg, 0, 255);
+}
diff --git a/build-system/tests/rsSupportMode/src/main/rs/colorcube.rs b/build-system/tests/rsSupportMode/src/main/rs/colorcube.rs
new file mode 100644
index 0000000..c0d6ace
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/rs/colorcube.rs
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ip.rsh"
+#pragma rs_fp_relaxed
+
+
+static rs_allocation gCube;
+static int4 gDims;
+static int4 gCoordMul;
+
+
+void setCube(rs_allocation c) {
+ gCube = c;
+ gDims.x = rsAllocationGetDimX(gCube);
+ gDims.y = rsAllocationGetDimY(gCube);
+ gDims.z = rsAllocationGetDimZ(gCube);
+ gDims.w = 0;
+
+ float4 m = (float4)(1.f / 255.f) * convert_float4(gDims - 1);
+ gCoordMul = convert_int4(m * (float4)0x10000);
+
+ rsDebug("dims", gDims);
+ rsDebug("gCoordMul", gCoordMul);
+}
+
+void root(const uchar4 *in, uchar4 *out, uint32_t x, uint32_t y) {
+ //rsDebug("root", in);
+
+ int4 baseCoord = convert_int4(*in) * gCoordMul;
+ int4 coord1 = baseCoord >> (int4)16;
+ int4 coord2 = min(coord1 + 1, gDims - 1);
+
+ int4 weight2 = baseCoord & 0xffff;
+ int4 weight1 = (int4)0x10000 - weight2;
+
+ uint4 v000 = convert_uint4(rsGetElementAt_uchar4(gCube, coord1.x, coord1.y, coord1.z));
+ uint4 v100 = convert_uint4(rsGetElementAt_uchar4(gCube, coord2.x, coord1.y, coord1.z));
+ uint4 v010 = convert_uint4(rsGetElementAt_uchar4(gCube, coord1.x, coord2.y, coord1.z));
+ uint4 v110 = convert_uint4(rsGetElementAt_uchar4(gCube, coord2.x, coord2.y, coord1.z));
+ uint4 v001 = convert_uint4(rsGetElementAt_uchar4(gCube, coord1.x, coord1.y, coord2.z));
+ uint4 v101 = convert_uint4(rsGetElementAt_uchar4(gCube, coord2.x, coord1.y, coord2.z));
+ uint4 v011 = convert_uint4(rsGetElementAt_uchar4(gCube, coord1.x, coord2.y, coord2.z));
+ uint4 v111 = convert_uint4(rsGetElementAt_uchar4(gCube, coord2.x, coord2.y, coord2.z));
+
+ uint4 yz00 = ((v000 * weight1.x) + (v100 * weight2.x)) >> (uint4)8;
+ uint4 yz10 = ((v010 * weight1.x) + (v110 * weight2.x)) >> (uint4)8;
+ uint4 yz01 = ((v001 * weight1.x) + (v101 * weight2.x)) >> (uint4)8;
+ uint4 yz11 = ((v011 * weight1.x) + (v111 * weight2.x)) >> (uint4)8;
+
+ uint4 z0 = ((yz00 * weight1.y) + (yz10 * weight2.y)) >> (uint4)16;
+ uint4 z1 = ((yz01 * weight1.y) + (yz11 * weight2.y)) >> (uint4)16;
+
+ uint4 v = ((z0 * weight1.z) + (z1 * weight2.z)) >> (uint4)16;
+ uint4 v2 = (v + 0x7f) >> (uint4)8;
+
+ *out = convert_uchar4(v2);
+ out->a = 0xff;
+
+ #if 0
+ if (in->r != out->r) {
+ rsDebug("dr", in->r - out->r);
+ //rsDebug("in", convert_int4(*in));
+ //rsDebug("coord1", coord1);
+ //rsDebug("coord2", coord2);
+ //rsDebug("weight1", weight1);
+ //rsDebug("weight2", weight2);
+ //rsDebug("yz00", yz00);
+ //rsDebug("z0", z0);
+ //rsDebug("v", v);
+ //rsDebug("v2", v2);
+ //rsDebug("out", convert_int4(*out));
+ }
+ #endif
+}
+
diff --git a/build-system/tests/rsSupportMode/src/main/rs/colormatrix.fs b/build-system/tests/rsSupportMode/src/main/rs/colormatrix.fs
new file mode 100644
index 0000000..86fb248
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/rs/colormatrix.fs
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ip.rsh"
+
+static rs_matrix4x4 Mat;
+
+void init() {
+ rsMatrixLoadIdentity(&Mat);
+}
+
+void setMatrix(rs_matrix4x4 m) {
+ Mat = m;
+}
+
+uchar4 __attribute__((kernel)) root(uchar4 in) {
+ float4 f = convert_float4(in);
+ f = rsMatrixMultiply(&Mat, f);
+ f = clamp(f, 0.f, 255.f);
+ return convert_uchar4(f);
+}
+
diff --git a/build-system/tests/rsSupportMode/src/main/rs/contrast.rs b/build-system/tests/rsSupportMode/src/main/rs/contrast.rs
new file mode 100644
index 0000000..d3743d3
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/rs/contrast.rs
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ip.rsh"
+
+static float brightM = 0.f;
+static float brightC = 0.f;
+
+void setBright(float v) {
+ brightM = pow(2.f, v / 100.f);
+ brightC = 127.f - brightM * 127.f;
+}
+
+void contrast(const uchar4 *in, uchar4 *out)
+{
+#if 0
+ out->r = rsClamp((int)(brightM * in->r + brightC), 0, 255);
+ out->g = rsClamp((int)(brightM * in->g + brightC), 0, 255);
+ out->b = rsClamp((int)(brightM * in->b + brightC), 0, 255);
+#else
+ float3 v = convert_float3(in->rgb) * brightM + brightC;
+ out->rgb = convert_uchar3(clamp(v, 0.f, 255.f));
+#endif
+}
diff --git a/build-system/tests/rsSupportMode/src/main/rs/convolve5x5.fs b/build-system/tests/rsSupportMode/src/main/rs/convolve5x5.fs
new file mode 100644
index 0000000..922a593
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/rs/convolve5x5.fs
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ip.rsh"
+
+int32_t gWidth;
+int32_t gHeight;
+rs_allocation gIn;
+
+float gCoeffs[25];
+
+uchar4 __attribute__((kernel)) root(uint32_t x, uint32_t y) {
+ uint32_t x0 = max((int32_t)x-2, 0);
+ uint32_t x1 = max((int32_t)x-1, 0);
+ uint32_t x2 = x;
+ uint32_t x3 = min((int32_t)x+1, gWidth-1);
+ uint32_t x4 = min((int32_t)x+2, gWidth-1);
+
+ uint32_t y0 = max((int32_t)y-2, 0);
+ uint32_t y1 = max((int32_t)y-1, 0);
+ uint32_t y2 = y;
+ uint32_t y3 = min((int32_t)y+1, gHeight-1);
+ uint32_t y4 = min((int32_t)y+2, gHeight-1);
+
+ float4 p0 = convert_float4(rsGetElementAt_uchar4(gIn, x0, y0)) * gCoeffs[0]
+ + convert_float4(rsGetElementAt_uchar4(gIn, x1, y0)) * gCoeffs[1]
+ + convert_float4(rsGetElementAt_uchar4(gIn, x2, y0)) * gCoeffs[2]
+ + convert_float4(rsGetElementAt_uchar4(gIn, x3, y0)) * gCoeffs[3]
+ + convert_float4(rsGetElementAt_uchar4(gIn, x4, y0)) * gCoeffs[4];
+
+ float4 p1 = convert_float4(rsGetElementAt_uchar4(gIn, x0, y1)) * gCoeffs[5]
+ + convert_float4(rsGetElementAt_uchar4(gIn, x1, y1)) * gCoeffs[6]
+ + convert_float4(rsGetElementAt_uchar4(gIn, x2, y1)) * gCoeffs[7]
+ + convert_float4(rsGetElementAt_uchar4(gIn, x3, y1)) * gCoeffs[8]
+ + convert_float4(rsGetElementAt_uchar4(gIn, x4, y1)) * gCoeffs[9];
+
+ float4 p2 = convert_float4(rsGetElementAt_uchar4(gIn, x0, y2)) * gCoeffs[10]
+ + convert_float4(rsGetElementAt_uchar4(gIn, x1, y2)) * gCoeffs[11]
+ + convert_float4(rsGetElementAt_uchar4(gIn, x2, y2)) * gCoeffs[12]
+ + convert_float4(rsGetElementAt_uchar4(gIn, x3, y2)) * gCoeffs[13]
+ + convert_float4(rsGetElementAt_uchar4(gIn, x4, y2)) * gCoeffs[14];
+
+ float4 p3 = convert_float4(rsGetElementAt_uchar4(gIn, x0, y3)) * gCoeffs[15]
+ + convert_float4(rsGetElementAt_uchar4(gIn, x1, y3)) * gCoeffs[16]
+ + convert_float4(rsGetElementAt_uchar4(gIn, x2, y3)) * gCoeffs[17]
+ + convert_float4(rsGetElementAt_uchar4(gIn, x3, y3)) * gCoeffs[18]
+ + convert_float4(rsGetElementAt_uchar4(gIn, x4, y3)) * gCoeffs[19];
+
+ float4 p4 = convert_float4(rsGetElementAt_uchar4(gIn, x0, y4)) * gCoeffs[20]
+ + convert_float4(rsGetElementAt_uchar4(gIn, x1, y4)) * gCoeffs[21]
+ + convert_float4(rsGetElementAt_uchar4(gIn, x2, y4)) * gCoeffs[22]
+ + convert_float4(rsGetElementAt_uchar4(gIn, x3, y4)) * gCoeffs[23]
+ + convert_float4(rsGetElementAt_uchar4(gIn, x4, y4)) * gCoeffs[24];
+
+ p0 = clamp(p0 + p1 + p2 + p3 + p4, 0.f, 255.f);
+ return convert_uchar4(p0);
+}
+
+
diff --git a/build-system/tests/rsSupportMode/src/main/rs/copy.fs b/build-system/tests/rsSupportMode/src/main/rs/copy.fs
new file mode 100644
index 0000000..6595874
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/rs/copy.fs
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ip.rsh"
+
+uchar4 __attribute__((kernel)) root(uchar4 v_in) {
+ return v_in;
+}
+
+
diff --git a/build-system/tests/rsSupportMode/src/main/rs/exposure.rs b/build-system/tests/rsSupportMode/src/main/rs/exposure.rs
new file mode 100644
index 0000000..0f05cb9
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/rs/exposure.rs
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ip.rsh"
+
+static float bright = 0.f;
+
+void setBright(float v) {
+ bright = 255.f / (255.f - v);
+}
+
+void exposure(const uchar4 *in, uchar4 *out)
+{
+ out->r = rsClamp((int)(bright * in->r), 0, 255);
+ out->g = rsClamp((int)(bright * in->g), 0, 255);
+ out->b = rsClamp((int)(bright * in->b), 0, 255);
+}
+
diff --git a/build-system/tests/rsSupportMode/src/main/rs/fisheye.rsh b/build-system/tests/rsSupportMode/src/main/rs/fisheye.rsh
new file mode 100644
index 0000000..2eacb7d
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/rs/fisheye.rsh
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+rs_allocation in_alloc;
+rs_sampler sampler;
+
+static float2 center, neg_center, inv_dimensions, axis_scale;
+static float alpha, radius2, factor;
+
+void init_filter(uint32_t dim_x, uint32_t dim_y, float center_x, float center_y, float k) {
+ center.x = center_x;
+ center.y = center_y;
+ neg_center = -center;
+ inv_dimensions.x = 1.f / (float)dim_x;
+ inv_dimensions.y = 1.f / (float)dim_y;
+ alpha = k * 2.0f + 0.75f;
+
+ axis_scale = (float2)1.f;
+ if (dim_x > dim_y)
+ axis_scale.y = (float)dim_y / (float)dim_x;
+ else
+ axis_scale.x = (float)dim_x / (float)dim_y;
+
+ const float bound2 = 0.25f * (axis_scale.x*axis_scale.x + axis_scale.y*axis_scale.y);
+ const float bound = sqrt(bound2);
+ const float radius = 1.15f * bound;
+ radius2 = radius*radius;
+ const float max_radian = M_PI_2 - atan(alpha / bound * sqrt(radius2 - bound2));
+ factor = bound / max_radian;
+}
+
+uchar4 __attribute__((kernel)) root(uint32_t x, uint32_t y) {
+ // Convert x and y to floating point coordinates with center as origin
+ const float2 inCoord = {(float)x, (float)y};
+ const float2 coord = mad(inCoord, inv_dimensions, neg_center);
+ const float2 scaledCoord = axis_scale * coord;
+ const float dist2 = scaledCoord.x*scaledCoord.x + scaledCoord.y*scaledCoord.y;
+ const float inv_dist = rsqrt(dist2);
+ const float radian = M_PI_2 - atan((alpha * sqrt(radius2 - dist2)) * inv_dist);
+ const float scalar = radian * factor * inv_dist;
+ const float2 new_coord = mad(coord, scalar, center);
+ const float4 fout = rsSample(in_alloc, sampler, new_coord);
+ return rsPackColorTo8888(fout);
+}
+
diff --git a/build-system/tests/rsSupportMode/src/main/rs/fisheye_approx.rsh b/build-system/tests/rsSupportMode/src/main/rs/fisheye_approx.rsh
new file mode 100644
index 0000000..fcf0a3d
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/rs/fisheye_approx.rsh
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+rs_allocation in_alloc;
+rs_sampler sampler;
+
+static float2 center, neg_center, inv_dimensions, axis_scale;
+static float alpha, radius2, factor;
+
+void init_filter(uint32_t dim_x, uint32_t dim_y, float center_x, float center_y, float k) {
+ center.x = center_x;
+ center.y = center_y;
+ neg_center = -center;
+ inv_dimensions.x = 1.f / (float)dim_x;
+ inv_dimensions.y = 1.f / (float)dim_y;
+ alpha = k * 2.0f + 0.75f;
+
+ axis_scale = (float2)1.f;
+ if (dim_x > dim_y)
+ axis_scale.y = (float)dim_y / (float)dim_x;
+ else
+ axis_scale.x = (float)dim_x / (float)dim_y;
+
+ const float bound2 = 0.25f * (axis_scale.x*axis_scale.x + axis_scale.y*axis_scale.y);
+ const float bound = sqrt(bound2);
+ const float radius = 1.15f * bound;
+ radius2 = radius*radius;
+ const float max_radian = M_PI_2 - atan(alpha / bound * sqrt(radius2 - bound2));
+ factor = bound / max_radian;
+}
+
+uchar4 __attribute__((kernel)) root(uint32_t x, uint32_t y) {
+ // Convert x and y to floating point coordinates with center as origin
+ const float2 inCoord = {(float)x, (float)y};
+ const float2 coord = mad(inCoord, inv_dimensions, neg_center);
+ const float2 scaledCoord = axis_scale * coord;
+ const float dist2 = scaledCoord.x*scaledCoord.x + scaledCoord.y*scaledCoord.y;
+ const float inv_dist = half_rsqrt(dist2);
+ const float radian = M_PI_2 - atan((alpha * half_sqrt(radius2 - dist2)) * inv_dist);
+ const float scalar = radian * factor * inv_dist;
+ const float2 new_coord = mad(coord, scalar, center);
+ const float4 fout = rsSample(in_alloc, sampler, new_coord);
+ return rsPackColorTo8888(fout);
+}
+
diff --git a/build-system/tests/rsSupportMode/src/main/rs/fisheye_approx_full.rs b/build-system/tests/rsSupportMode/src/main/rs/fisheye_approx_full.rs
new file mode 100644
index 0000000..ed69ff4
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/rs/fisheye_approx_full.rs
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ip.rsh"
+
+#include "fisheye_approx.rsh"
+
diff --git a/build-system/tests/rsSupportMode/src/main/rs/fisheye_approx_relaxed.fs b/build-system/tests/rsSupportMode/src/main/rs/fisheye_approx_relaxed.fs
new file mode 100644
index 0000000..ed69ff4
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/rs/fisheye_approx_relaxed.fs
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ip.rsh"
+
+#include "fisheye_approx.rsh"
+
diff --git a/build-system/tests/rsSupportMode/src/main/rs/fisheye_full.rs b/build-system/tests/rsSupportMode/src/main/rs/fisheye_full.rs
new file mode 100644
index 0000000..f986b5d
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/rs/fisheye_full.rs
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ip.rsh"
+
+#include "fisheye.rsh"
+
diff --git a/build-system/tests/rsSupportMode/src/main/rs/fisheye_relaxed.fs b/build-system/tests/rsSupportMode/src/main/rs/fisheye_relaxed.fs
new file mode 100644
index 0000000..f986b5d
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/rs/fisheye_relaxed.fs
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ip.rsh"
+
+#include "fisheye.rsh"
+
diff --git a/build-system/tests/rsSupportMode/src/main/rs/grain.fs b/build-system/tests/rsSupportMode/src/main/rs/grain.fs
new file mode 100644
index 0000000..2e62cd7
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/rs/grain.fs
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ip.rsh"
+
+uchar __attribute__((kernel)) genRand() {
+ return (uchar)rsRand(0xff);
+}
+
+/*
+ * Convolution matrix of distance 2 with fixed point of 'kShiftBits' bits
+ * shifted. Thus the sum of this matrix should be 'kShiftValue'. Entries of
+ * small values are not calculated to gain efficiency.
+ * The order ot pixels represented in this matrix is:
+ * 1 2 3
+ * 4 0 5
+ * 6 7 8
+ * and the matrix should be: {230, 56, 114, 56, 114, 114, 56, 114, 56}.
+ * However, since most of the valus are identical, we only use the first three
+ * entries and the entries corresponding to the pixels is:
+ * 1 2 1
+ * 2 0 2
+ * 1 2 1
+ */
+
+int32_t gWMask;
+int32_t gHMask;
+
+rs_allocation gBlendSource;
+uchar __attribute__((kernel)) blend9(uint32_t x, uint32_t y) {
+ uint32_t x1 = (x-1) & gWMask;
+ uint32_t x2 = (x+1) & gWMask;
+ uint32_t y1 = (y-1) & gHMask;
+ uint32_t y2 = (y+1) & gHMask;
+
+ uint p00 = 56 * rsGetElementAt_uchar(gBlendSource, x1, y1);
+ uint p01 = 114 * rsGetElementAt_uchar(gBlendSource, x, y1);
+ uint p02 = 56 * rsGetElementAt_uchar(gBlendSource, x2, y1);
+ uint p10 = 114 * rsGetElementAt_uchar(gBlendSource, x1, y);
+ uint p11 = 230 * rsGetElementAt_uchar(gBlendSource, x, y);
+ uint p12 = 114 * rsGetElementAt_uchar(gBlendSource, x2, y);
+ uint p20 = 56 * rsGetElementAt_uchar(gBlendSource, x1, y2);
+ uint p21 = 114 * rsGetElementAt_uchar(gBlendSource, x, y2);
+ uint p22 = 56 * rsGetElementAt_uchar(gBlendSource, x2, y2);
+
+ p00 += p01;
+ p02 += p10;
+ p11 += p12;
+ p20 += p21;
+
+ p22 += p00;
+ p02 += p11;
+
+ p20 += p22;
+ p20 += p02;
+
+ p20 = min(p20 >> 10, (uint)255);
+ return (uchar)p20;
+}
+
+float gNoiseStrength;
+
+rs_allocation gNoise;
+uchar4 __attribute__((kernel)) root(uchar4 in, uint32_t x, uint32_t y) {
+ float4 ip = convert_float4(in);
+ float pnoise = (float) rsGetElementAt_uchar(gNoise, x & gWMask, y & gHMask);
+
+ float energy_level = ip.r + ip.g + ip.b;
+ float energy_mask = (28.f - sqrt(energy_level)) * 0.03571f;
+ pnoise = (pnoise - 128.f) * energy_mask;
+
+ ip += pnoise * gNoiseStrength;
+ ip = clamp(ip, 0.f, 255.f);
+
+ uchar4 p = convert_uchar4(ip);
+ p.a = 0xff;
+ return p;
+}
diff --git a/build-system/tests/rsSupportMode/src/main/rs/greyscale.fs b/build-system/tests/rsSupportMode/src/main/rs/greyscale.fs
new file mode 100644
index 0000000..4e13072
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/rs/greyscale.fs
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ip.rsh"
+
+const static float3 gMonoMult = {0.299f, 0.587f, 0.114f};
+
+uchar4 __attribute__((kernel)) root(uchar4 v_in) {
+ float4 f4 = rsUnpackColor8888(v_in);
+
+ float3 mono = dot(f4.rgb, gMonoMult);
+ return rsPackColorTo8888(mono);
+}
+
+uchar __attribute__((kernel)) toU8(uchar4 v_in) {
+ float4 f4 = convert_float4(v_in);
+ return (uchar)dot(f4.rgb, gMonoMult);
+}
+
+uchar4 __attribute__((kernel)) toU8_4(uchar v_in) {
+ return (uchar4)v_in;
+}
+
diff --git a/build-system/tests/rsSupportMode/src/main/rs/ip.rsh b/build-system/tests/rsSupportMode/src/main/rs/ip.rsh
new file mode 100644
index 0000000..34e213c
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/rs/ip.rsh
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma version(1)
+#pragma rs java_package_name(com.android.rs.image2)
+
+
diff --git a/build-system/tests/rsSupportMode/src/main/rs/ip2_convolve3x3.rs b/build-system/tests/rsSupportMode/src/main/rs/ip2_convolve3x3.rs
new file mode 100644
index 0000000..177e86e
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/rs/ip2_convolve3x3.rs
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ip.rsh"
+
+int32_t gWidth;
+int32_t gHeight;
+rs_allocation gIn;
+
+float gCoeffs[9];
+
+uchar4 __attribute__((kernel)) root(uint32_t x, uint32_t y) {
+ uint32_t x1 = min((int32_t)x+1, gWidth-1);
+ uint32_t x2 = max((int32_t)x-1, 0);
+ uint32_t y1 = min((int32_t)y+1, gHeight-1);
+ uint32_t y2 = max((int32_t)y-1, 0);
+
+ float4 p00 = convert_float4(rsGetElementAt_uchar4(gIn, x1, y1));
+ float4 p01 = convert_float4(rsGetElementAt_uchar4(gIn, x, y1));
+ float4 p02 = convert_float4(rsGetElementAt_uchar4(gIn, x2, y1));
+ float4 p10 = convert_float4(rsGetElementAt_uchar4(gIn, x1, y));
+ float4 p11 = convert_float4(rsGetElementAt_uchar4(gIn, x, y));
+ float4 p12 = convert_float4(rsGetElementAt_uchar4(gIn, x2, y));
+ float4 p20 = convert_float4(rsGetElementAt_uchar4(gIn, x1, y2));
+ float4 p21 = convert_float4(rsGetElementAt_uchar4(gIn, x, y2));
+ float4 p22 = convert_float4(rsGetElementAt_uchar4(gIn, x2, y2));
+ p00 *= gCoeffs[0];
+ p01 *= gCoeffs[1];
+ p02 *= gCoeffs[2];
+ p10 *= gCoeffs[3];
+ p11 *= gCoeffs[4];
+ p12 *= gCoeffs[5];
+ p20 *= gCoeffs[6];
+ p21 *= gCoeffs[7];
+ p22 *= gCoeffs[8];
+
+ p00 += p01;
+ p02 += p10;
+ p11 += p12;
+ p20 += p21;
+
+ p22 += p00;
+ p02 += p11;
+
+ p20 += p22;
+ p20 += p02;
+
+ p20 = clamp(p20, 0.f, 255.f);
+ return convert_uchar4(p20);
+}
+
+
diff --git a/build-system/tests/rsSupportMode/src/main/rs/levels.rsh b/build-system/tests/rsSupportMode/src/main/rs/levels.rsh
new file mode 100644
index 0000000..e289906
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/rs/levels.rsh
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+float inBlack;
+float outBlack;
+float inWMinInB;
+float outWMinOutB;
+float overInWMinInB;
+rs_matrix3x3 colorMat;
+
+uchar4 __attribute__((kernel)) root(uchar4 in, uint32_t x, uint32_t y) {
+ uchar4 out;
+ float3 pixel = convert_float4(in).rgb;
+ pixel = rsMatrixMultiply(&colorMat, pixel);
+ pixel = clamp(pixel, 0.f, 255.f);
+ pixel = (pixel - inBlack) * overInWMinInB;
+ pixel = pixel * outWMinOutB + outBlack;
+ pixel = clamp(pixel, 0.f, 255.f);
+ out.xyz = convert_uchar3(pixel);
+ out.w = 0xff;
+ return out;
+}
+
+uchar4 __attribute__((kernel)) root4(uchar4 in, uint32_t x, uint32_t y) {
+ float4 pixel = convert_float4(in);
+ pixel.rgb = rsMatrixMultiply(&colorMat, pixel.rgb);
+ pixel = clamp(pixel, 0.f, 255.f);
+ pixel = (pixel - inBlack) * overInWMinInB;
+ pixel = pixel * outWMinOutB + outBlack;
+ pixel = clamp(pixel, 0.f, 255.f);
+ return convert_uchar4(pixel);
+}
+
diff --git a/build-system/tests/rsSupportMode/src/main/rs/levels_full.rs b/build-system/tests/rsSupportMode/src/main/rs/levels_full.rs
new file mode 100644
index 0000000..28596ba
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/rs/levels_full.rs
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ip.rsh"
+
+#include "levels.rsh"
+
diff --git a/build-system/tests/rsSupportMode/src/main/rs/levels_relaxed.fs b/build-system/tests/rsSupportMode/src/main/rs/levels_relaxed.fs
new file mode 100644
index 0000000..28596ba
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/rs/levels_relaxed.fs
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ip.rsh"
+
+#include "levels.rsh"
+
diff --git a/build-system/tests/rsSupportMode/src/main/rs/mandelbrot.rs b/build-system/tests/rsSupportMode/src/main/rs/mandelbrot.rs
new file mode 100644
index 0000000..de0bd00
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/rs/mandelbrot.rs
@@ -0,0 +1,55 @@
+// Copyright (C) 2011 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "ip.rsh"
+
+uint32_t gMaxIteration = 500;
+uint32_t gDimX = 1024;
+uint32_t gDimY = 1024;
+
+float lowerBoundX = -2.f;
+float lowerBoundY = -2.f;
+float scaleFactor = 4.f;
+
+uchar4 __attribute__((kernel)) root(uint32_t x, uint32_t y) {
+ float2 p;
+ p.x = lowerBoundX + ((float)x / gDimX) * scaleFactor;
+ p.y = lowerBoundY + ((float)y / gDimY) * scaleFactor;
+
+ float2 t = 0;
+ float2 t2 = t * t;
+ int iter = 0;
+ while((t2.x + t2.y < 4.f) && (iter < gMaxIteration)) {
+ float xtemp = t2.x - t2.y + p.x;
+ t.y = 2 * t.x * t.y + p.y;
+ t.x = xtemp;
+ iter++;
+ t2 = t * t;
+ }
+
+ if(iter >= gMaxIteration) {
+ // write a non-transparent black pixel
+ return (uchar4){0, 0, 0, 0xff};
+ } else {
+ float mi3 = gMaxIteration / 3.f;
+ if (iter <= (gMaxIteration / 3))
+ return (uchar4){0xff * (iter / mi3), 0, 0, 0xff};
+ else if (iter <= (((gMaxIteration / 3) * 2)))
+ return (uchar4){0xff - (0xff * ((iter - mi3) / mi3)),
+ (0xff * ((iter - mi3) / mi3)), 0, 0xff};
+ else
+ return (uchar4){0, 0xff - (0xff * ((iter - (mi3 * 2)) / mi3)),
+ (0xff * ((iter - (mi3 * 2)) / mi3)), 0xff};
+ }
+}
diff --git a/build-system/tests/rsSupportMode/src/main/rs/shadows.rs b/build-system/tests/rsSupportMode/src/main/rs/shadows.rs
new file mode 100644
index 0000000..f6c149d
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/rs/shadows.rs
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ip.rsh"
+//#pragma rs_fp_relaxed
+
+static double shadowFilterMap[] = {
+ -0.00591, 0.0001,
+ 1.16488, 0.01668,
+ -0.18027, -0.06791,
+ -0.12625, 0.09001,
+ 0.15065, -0.03897
+};
+
+static double poly[] = {
+ 0., 0.,
+ 0., 0.,
+ 0.
+};
+
+static const int ABITS = 4;
+static const int HSCALE = 256;
+static const int k1=255 << ABITS;
+static const int k2=HSCALE << ABITS;
+
+static double fastevalPoly(double *poly,int n, double x){
+
+ double f =x;
+ double sum = poly[0]+poly[1]*f;
+ int i;
+ for (i = 2; i < n; i++) {
+ f*=x;
+ sum += poly[i]*f;
+ }
+ return sum;
+}
+
+static ushort3 rgb2hsv( uchar4 rgb)
+{
+ int iMin,iMax,chroma;
+
+ int ri = rgb.r;
+ int gi = rgb.g;
+ int bi = rgb.b;
+ short rv,rs,rh;
+
+ if (ri > gi) {
+ iMax = max (ri, bi);
+ iMin = min (gi, bi);
+ } else {
+ iMax = max (gi, bi);
+ iMin = min (ri, bi);
+ }
+
+ chroma = iMax - iMin;
+ // set value
+ rv = (short)( iMax << ABITS);
+
+ // set saturation
+ if (rv == 0)
+ rs = 0;
+ else
+ rs = (short)((k1*chroma)/iMax);
+
+ // set hue
+ if (rs == 0)
+ rh = 0;
+ else {
+ if ( ri == iMax ) {
+ rh = (short)( (k2*(6*chroma+gi - bi))/(6*chroma));
+ if (rh >= k2) rh -= k2;
+ } else if (gi == iMax)
+ rh = (short)( (k2*(2*chroma+bi - ri ))/(6*chroma));
+ else // (bi == iMax )
+ rh = (short)( (k2*(4*chroma+ri - gi ))/(6*chroma));
+ }
+
+ ushort3 out;
+ out.x = rv;
+ out.y = rs;
+ out.z = rh;
+ return out;
+}
+
+static uchar4 hsv2rgb(ushort3 hsv)
+{
+ int ABITS = 4;
+ int HSCALE = 256;
+ int m;
+ int H,X,ih,is,iv;
+ int k1=255<<ABITS;
+ int k2=HSCALE<<ABITS;
+ int k3=1<<(ABITS-1);
+ int rr=0;
+ int rg=0;
+ int rb=0;
+ short cv = hsv.x;
+ short cs = hsv.y;
+ short ch = hsv.z;
+
+ // set chroma and min component value m
+ //chroma = ( cv * cs )/k1;
+ //m = cv - chroma;
+ m = ((int)cv*(k1 - (int)cs ))/k1;
+
+ // chroma == 0 <-> cs == 0 --> m=cv
+ if (cs == 0) {
+ rb = ( rg = ( rr =( cv >> ABITS) ));
+ } else {
+ ih=(int)ch;
+ is=(int)cs;
+ iv=(int)cv;
+
+ H = (6*ih)/k2;
+ X = ((iv*is)/k2)*(k2- abs(6*ih- 2*(H>>1)*k2 - k2)) ;
+
+ // removing additional bits --> unit8
+ X=( (X+iv*(k1 - is ))/k1 + k3 ) >> ABITS;
+ m=m >> ABITS;
+
+ // ( chroma + m ) --> cv ;
+ cv=(short) (cv >> ABITS);
+ switch (H) {
+ case 0:
+ rr = cv;
+ rg = X;
+ rb = m;
+ break;
+ case 1:
+ rr = X;
+ rg = cv;
+ rb = m;
+ break;
+ case 2:
+ rr = m;
+ rg = cv;
+ rb = X;
+ break;
+ case 3:
+ rr = m;
+ rg = X;
+ rb = cv;
+ break;
+ case 4:
+ rr = X;
+ rg = m;
+ rb = cv;
+ break;
+ case 5:
+ rr = cv;
+ rg = m ;
+ rb = X;
+ break;
+ }
+ }
+
+ uchar4 rgb;
+
+ rgb.r = rr;
+ rgb.g = rg;
+ rgb.b = rb;
+
+ return rgb;
+}
+
+void prepareShadows(float scale) {
+ double s = (scale>=0)?scale:scale/5;
+ for (int i = 0; i < 5; i++) {
+ poly[i] = fastevalPoly(shadowFilterMap+i*2,2 , s);
+ }
+}
+
+void shadowsKernel(const uchar4 *in, uchar4 *out) {
+ ushort3 hsv = rgb2hsv(*in);
+ double v = (fastevalPoly(poly,5,hsv.x/4080.)*4080);
+ if (v>4080) v = 4080;
+ hsv.x = (unsigned short) ((v>0)?v:0);
+ *out = hsv2rgb(hsv);
+}
diff --git a/build-system/tests/rsSupportMode/src/main/rs/threshold.fs b/build-system/tests/rsSupportMode/src/main/rs/threshold.fs
new file mode 100644
index 0000000..0b2c2e8
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/rs/threshold.fs
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ip.rsh"
+
+
+int height;
+int width;
+static int radius;
+
+rs_allocation InPixel;
+rs_allocation ScratchPixel1;
+rs_allocation ScratchPixel2;
+
+const int MAX_RADIUS = 25;
+
+// Store our coefficients here
+static float gaussian[MAX_RADIUS * 2 + 1];
+
+void setRadius(int rad) {
+ radius = rad;
+ // Compute gaussian weights for the blur
+ // e is the euler's number
+ float e = 2.718281828459045f;
+ float pi = 3.1415926535897932f;
+ // g(x) = ( 1 / sqrt( 2 * pi ) * sigma) * e ^ ( -x^2 / 2 * sigma^2 )
+ // x is of the form [-radius .. 0 .. radius]
+ // and sigma varies with radius.
+ // Based on some experimental radius values and sigma's
+ // we approximately fit sigma = f(radius) as
+ // sigma = radius * 0.4 + 0.6
+ // The larger the radius gets, the more our gaussian blur
+ // will resemble a box blur since with large sigma
+ // the gaussian curve begins to lose its shape
+ float sigma = 0.4f * (float)radius + 0.6f;
+
+ // Now compute the coefficints
+ // We will store some redundant values to save some math during
+ // the blur calculations
+ // precompute some values
+ float coeff1 = 1.0f / (sqrt( 2.0f * pi ) * sigma);
+ float coeff2 = - 1.0f / (2.0f * sigma * sigma);
+
+ float normalizeFactor = 0.0f;
+ float floatR = 0.0f;
+ for (int r = -radius; r <= radius; r ++) {
+ floatR = (float)r;
+ gaussian[r + radius] = coeff1 * pow(e, floatR * floatR * coeff2);
+ normalizeFactor += gaussian[r + radius];
+ }
+
+ //Now we need to normalize the weights because all our coefficients need to add up to one
+ normalizeFactor = 1.0f / normalizeFactor;
+ for (int r = -radius; r <= radius; r ++) {
+ floatR = (float)r;
+ gaussian[r + radius] *= normalizeFactor;
+ }
+}
+
+float4 __attribute__((kernel)) copyIn(uchar4 in) {
+ return convert_float4(in);
+}
+
+uchar4 __attribute__((kernel)) vert(uint32_t x, uint32_t y) {
+ float3 blurredPixel = 0;
+ int gi = 0;
+ uchar4 out;
+ if ((y > radius) && (y < (height - radius))) {
+ for (int r = -radius; r <= radius; r ++) {
+ float4 i = rsGetElementAt_float4(ScratchPixel2, x, y + r);
+ blurredPixel += i.xyz * gaussian[gi++];
+ }
+ } else {
+ for (int r = -radius; r <= radius; r ++) {
+ int validH = rsClamp((int)y + r, (int)0, (int)(height - 1));
+ float4 i = rsGetElementAt_float4(ScratchPixel2, x, validH);
+ blurredPixel += i.xyz * gaussian[gi++];
+ }
+ }
+
+ out.xyz = convert_uchar3(clamp(blurredPixel, 0.f, 255.f));
+ out.w = 0xff;
+ return out;
+}
+
+float4 __attribute__((kernel)) horz(uint32_t x, uint32_t y) {
+ float4 blurredPixel = 0;
+ int gi = 0;
+ if ((x > radius) && (x < (width - radius))) {
+ for (int r = -radius; r <= radius; r ++) {
+ float4 i = rsGetElementAt_float4(ScratchPixel1, x + r, y);
+ blurredPixel += i * gaussian[gi++];
+ }
+ } else {
+ for (int r = -radius; r <= radius; r ++) {
+ // Stepping left and right away from the pixel
+ int validX = rsClamp((int)x + r, (int)0, (int)(width - 1));
+ float4 i = rsGetElementAt_float4(ScratchPixel1, validX, y);
+ blurredPixel += i * gaussian[gi++];
+ }
+ }
+
+ return blurredPixel;
+}
+
diff --git a/build-system/tests/rsSupportMode/src/main/rs/vibrance.rs b/build-system/tests/rsSupportMode/src/main/rs/vibrance.rs
new file mode 100644
index 0000000..ad4de58
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/rs/vibrance.rs
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ip.rsh"
+
+float vibrance = 0.f;
+
+static const float Rf = 0.2999f;
+static const float Gf = 0.587f;
+static const float Bf = 0.114f;
+
+static float S = 0.f;
+static float MS = 0.f;
+static float Rt = 0.f;
+static float Gt = 0.f;
+static float Bt = 0.f;
+static float Vib = 0.f;
+
+void vibranceKernel(const uchar4 *in, uchar4 *out) {
+
+ float R, G, B;
+
+ int r = in->r;
+ int g = in->g;
+ int b = in->b;
+ float red = (r-max(g, b))/256.f;
+ float sx = (float)(Vib/(1+native_exp(-red*3)));
+ S = sx+1;
+ MS = 1.0f - S;
+ Rt = Rf * MS;
+ Gt = Gf * MS;
+ Bt = Bf * MS;
+ int t = (r + g) / 2;
+ R = r;
+ G = g;
+ B = b;
+
+ float Rc = R * (Rt + S) + G * Gt + B * Bt;
+ float Gc = R * Rt + G * (Gt + S) + B * Bt;
+ float Bc = R * Rt + G * Gt + B * (Bt + S);
+
+ out->r = rsClamp(Rc, 0, 255);
+ out->g = rsClamp(Gc, 0, 255);
+ out->b = rsClamp(Bc, 0, 255);
+
+}
+
+void prepareVibrance() {
+
+ Vib = vibrance/100.f;
+ S = Vib + 1;
+ MS = 1.0f - S;
+ Rt = Rf * MS;
+ Gt = Gf * MS;
+ Bt = Bf * MS;
+
+}
diff --git a/build-system/tests/rsSupportMode/src/main/rs/vignette.rsh b/build-system/tests/rsSupportMode/src/main/rs/vignette.rsh
new file mode 100644
index 0000000..04ca1f1
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/rs/vignette.rsh
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+static float2 neg_center, axis_scale, inv_dimensions;
+static float sloped_neg_range, sloped_inv_max_dist, shade, opp_shade;
+
+void init_vignette(uint32_t dim_x, uint32_t dim_y, float center_x, float center_y,
+ float desired_scale, float desired_shade, float desired_slope) {
+
+ neg_center.x = -center_x;
+ neg_center.y = -center_y;
+ inv_dimensions.x = 1.f / (float)dim_x;
+ inv_dimensions.y = 1.f / (float)dim_y;
+
+ axis_scale = (float2)1.f;
+ if (dim_x > dim_y)
+ axis_scale.y = (float)dim_y / (float)dim_x;
+ else
+ axis_scale.x = (float)dim_x / (float)dim_y;
+
+ const float max_dist = 0.5f * length(axis_scale);
+ sloped_inv_max_dist = desired_slope * 1.f/max_dist;
+
+ // Range needs to be between 1.3 to 0.6. When scale is zero then range is
+ // 1.3 which means no vignette at all because the luminousity difference is
+ // less than 1/256. Expect input scale to be between 0.0 and 1.0.
+ const float neg_range = 0.7f*sqrt(desired_scale) - 1.3f;
+ sloped_neg_range = exp(neg_range * desired_slope);
+
+ shade = desired_shade;
+ opp_shade = 1.f - desired_shade;
+}
+
+uchar4 __attribute__((kernel)) root(uchar4 in, uint32_t x, uint32_t y) {
+ // Convert x and y to floating point coordinates with center as origin
+ const float4 fin = convert_float4(in);
+ const float2 inCoord = {(float)x, (float)y};
+ const float2 coord = mad(inCoord, inv_dimensions, neg_center);
+ const float sloped_dist_ratio = length(axis_scale * coord) * sloped_inv_max_dist;
+ const float lumen = opp_shade + shade / ( 1.0f + sloped_neg_range * exp(sloped_dist_ratio) );
+ float4 fout;
+ fout.rgb = fin.rgb * lumen;
+ fout.w = fin.w;
+ return convert_uchar4(fout);
+}
+
diff --git a/build-system/tests/rsSupportMode/src/main/rs/vignette_approx.rsh b/build-system/tests/rsSupportMode/src/main/rs/vignette_approx.rsh
new file mode 100644
index 0000000..5668621
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/rs/vignette_approx.rsh
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+static float2 neg_center, axis_scale, inv_dimensions;
+static float sloped_neg_range, sloped_inv_max_dist, shade, opp_shade;
+
+void init_vignette(uint32_t dim_x, uint32_t dim_y, float center_x, float center_y,
+ float desired_scale, float desired_shade, float desired_slope) {
+
+ neg_center.x = -center_x;
+ neg_center.y = -center_y;
+ inv_dimensions.x = 1.f / (float)dim_x;
+ inv_dimensions.y = 1.f / (float)dim_y;
+
+ axis_scale = (float2)1.f;
+ if (dim_x > dim_y)
+ axis_scale.y = (float)dim_y / (float)dim_x;
+ else
+ axis_scale.x = (float)dim_x / (float)dim_y;
+
+ const float max_dist = 0.5f * length(axis_scale);
+ sloped_inv_max_dist = desired_slope * 1.f/max_dist;
+
+ // Range needs to be between 1.3 to 0.6. When scale is zero then range is
+ // 1.3 which means no vignette at all because the luminousity difference is
+ // less than 1/256. Expect input scale to be between 0.0 and 1.0.
+ const float neg_range = 0.7f*sqrt(desired_scale) - 1.3f;
+ sloped_neg_range = exp(neg_range * desired_slope);
+
+ shade = desired_shade;
+ opp_shade = 1.f - desired_shade;
+}
+
+uchar4 __attribute__((kernel)) root(uchar4 in, uint32_t x, uint32_t y) {
+ // Convert x and y to floating point coordinates with center as origin
+ const float4 fin = convert_float4(in);
+ const float2 inCoord = {(float)x, (float)y};
+ const float2 coord = mad(inCoord, inv_dimensions, neg_center);
+ const float sloped_dist_ratio = fast_length(axis_scale * coord) * sloped_inv_max_dist;
+ const float lumen = opp_shade + shade * half_recip(1.f + sloped_neg_range * native_exp(sloped_dist_ratio));
+ float4 fout;
+ fout.rgb = fin.rgb * lumen;
+ fout.w = fin.w;
+ return convert_uchar4(fout);
+}
+
diff --git a/build-system/tests/rsSupportMode/src/main/rs/vignette_approx_full.rs b/build-system/tests/rsSupportMode/src/main/rs/vignette_approx_full.rs
new file mode 100644
index 0000000..00cbbc4
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/rs/vignette_approx_full.rs
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ip.rsh"
+
+#include "vignette_approx.rsh"
+
diff --git a/build-system/tests/rsSupportMode/src/main/rs/vignette_approx_relaxed.fs b/build-system/tests/rsSupportMode/src/main/rs/vignette_approx_relaxed.fs
new file mode 100644
index 0000000..00cbbc4
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/rs/vignette_approx_relaxed.fs
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ip.rsh"
+
+#include "vignette_approx.rsh"
+
diff --git a/build-system/tests/rsSupportMode/src/main/rs/vignette_full.rs b/build-system/tests/rsSupportMode/src/main/rs/vignette_full.rs
new file mode 100644
index 0000000..8202c5c
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/rs/vignette_full.rs
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ip.rsh"
+
+#include "vignette.rsh"
+
diff --git a/build-system/tests/rsSupportMode/src/main/rs/vignette_relaxed.fs b/build-system/tests/rsSupportMode/src/main/rs/vignette_relaxed.fs
new file mode 100644
index 0000000..8202c5c
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/rs/vignette_relaxed.fs
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ip.rsh"
+
+#include "vignette.rsh"
+
diff --git a/build-system/tests/rsSupportMode/src/main/rs/wbalance.rs b/build-system/tests/rsSupportMode/src/main/rs/wbalance.rs
new file mode 100644
index 0000000..6650671
--- /dev/null
+++ b/build-system/tests/rsSupportMode/src/main/rs/wbalance.rs
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ip.rsh"
+//#pragma rs_fp_relaxed
+
+static int histR[256] = {0}, histG[256] = {0}, histB[256] = {0};
+
+rs_allocation histogramSource;
+uint32_t histogramHeight;
+uint32_t histogramWidth;
+
+static float scaleR;
+static float scaleG;
+static float scaleB;
+
+static uchar4 estimateWhite() {
+
+ for (int i = 0; i < 256; i++) {
+ histR[i] = 0; histG[i] = 0; histB[i] = 0;
+ }
+
+ for (uint32_t i = 0; i < histogramHeight; i++) {
+ for (uint32_t j = 0; j < histogramWidth; j++) {
+ uchar4 in = rsGetElementAt_uchar4(histogramSource, j, i);
+ histR[in.r]++;
+ histG[in.g]++;
+ histB[in.b]++;
+ }
+ }
+
+ int min_r = -1, min_g = -1, min_b = -1;
+ int max_r = 0, max_g = 0, max_b = 0;
+ int sum_r = 0, sum_g = 0, sum_b = 0;
+
+ for (int i = 1; i < 255; i++) {
+ int r = histR[i];
+ int g = histG[i];
+ int b = histB[i];
+ sum_r += r;
+ sum_g += g;
+ sum_b += b;
+
+ if (r>0){
+ if (min_r < 0) min_r = i;
+ max_r = i;
+ }
+ if (g>0){
+ if (min_g < 0) min_g = i;
+ max_g = i;
+ }
+ if (b>0){
+ if (min_b < 0) min_b = i;
+ max_b = i;
+ }
+ }
+
+ int sum15r = 0, sum15g = 0, sum15b = 0;
+ int count15r = 0, count15g = 0, count15b = 0;
+ int tmp_r = 0, tmp_g = 0, tmp_b = 0;
+
+ for (int i = 254; i >0; i--) {
+ int r = histR[i];
+ int g = histG[i];
+ int b = histB[i];
+ tmp_r += r;
+ tmp_g += g;
+ tmp_b += b;
+
+ if ((tmp_r > sum_r/20) && (tmp_r < sum_r/5)) {
+ sum15r += r*i;
+ count15r += r;
+ }
+ if ((tmp_g > sum_g/20) && (tmp_g < sum_g/5)) {
+ sum15g += g*i;
+ count15g += g;
+ }
+ if ((tmp_b > sum_b/20) && (tmp_b < sum_b/5)) {
+ sum15b += b*i;
+ count15b += b;
+ }
+
+ }
+
+ uchar4 out;
+
+ if ((count15r>0) && (count15g>0) && (count15b>0) ){
+ out.r = sum15r/count15r;
+ out.g = sum15g/count15g;
+ out.b = sum15b/count15b;
+ }else {
+ out.r = out.g = out.b = 255;
+ }
+
+ return out;
+
+}
+
+void prepareWhiteBalance() {
+ uchar4 estimation = estimateWhite();
+ int minimum = min(estimation.r, min(estimation.g, estimation.b));
+ int maximum = max(estimation.r, max(estimation.g, estimation.b));
+ float avg = (minimum + maximum) / 2.f;
+
+ scaleR = avg/estimation.r;
+ scaleG = avg/estimation.g;
+ scaleB = avg/estimation.b;
+
+}
+
+static unsigned char contrastClamp(int c)
+{
+ int N = 255;
+ c &= ~(c >> 31);
+ c -= N;
+ c &= (c >> 31);
+ c += N;
+ return (unsigned char) c;
+}
+
+void whiteBalanceKernel(const uchar4 *in, uchar4 *out) {
+ float Rc = in->r*scaleR;
+ float Gc = in->g*scaleG;
+ float Bc = in->b*scaleB;
+
+ out->r = contrastClamp(Rc);
+ out->g = contrastClamp(Gc);
+ out->b = contrastClamp(Bc);
+}
diff --git a/build-system/tests/sameNamedLibs/app/build.gradle b/build-system/tests/sameNamedLibs/app/build.gradle
new file mode 100644
index 0000000..ea8974b
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/app/build.gradle
@@ -0,0 +1,15 @@
+apply plugin: 'android'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
+
+//
+// A basic Android application split over a library and a main project.
+//
+dependencies {
+ compile project(':lib1:libs')
+ compile project(':lib2b:libs')
+ compile project(':libapp:libs')
+}
diff --git a/build-system/tests/sameNamedLibs/app/proguard-project.txt b/build-system/tests/sameNamedLibs/app/proguard-project.txt
new file mode 100644
index 0000000..349f80f
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/app/proguard-project.txt
@@ -0,0 +1,22 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+-adaptclassstrings
\ No newline at end of file
diff --git a/build-system/tests/sameNamedLibs/app/src/instrumentTest/java/com/android/tests/libstest/app/MainActivityTest.java b/build-system/tests/sameNamedLibs/app/src/instrumentTest/java/com/android/tests/libstest/app/MainActivityTest.java
new file mode 100644
index 0000000..61a0a31
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/app/src/instrumentTest/java/com/android/tests/libstest/app/MainActivityTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.libstest.app;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private TextView mAppTextView1;
+ private TextView mAppTextView2;
+ private TextView mLib1TextView1;
+ private TextView mLib1TextView2;
+ private TextView mLib2TextView1;
+ private TextView mLib2TextView2;
+ private TextView mLib2bTextView1;
+ private TextView mLib2bTextView2;
+ private TextView mLibappTextView1;
+ private TextView mLibappTextView2;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public MainActivityTest() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final MainActivity a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ mAppTextView1 = (TextView) a.findViewById(R.id.app_text1);
+ mAppTextView2 = (TextView) a.findViewById(R.id.app_text1);
+ mLib1TextView1 = (TextView) a.findViewById(R.id.lib1_text1);
+ mLib1TextView2 = (TextView) a.findViewById(R.id.lib1_text2);
+ mLib2TextView1 = (TextView) a.findViewById(R.id.lib2_text1);
+ mLib2TextView2 = (TextView) a.findViewById(R.id.lib2_text2);
+ mLib2bTextView1 = (TextView) a.findViewById(R.id.lib2b_text1);
+ mLib2bTextView2 = (TextView) a.findViewById(R.id.lib2b_text2);
+ mLibappTextView1 = (TextView) a.findViewById(R.id.libapp_text1);
+ mLibappTextView2 = (TextView) a.findViewById(R.id.libapp_text2);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mAppTextView1);
+ assertNotNull(mAppTextView2);
+ assertNotNull(mLib1TextView1);
+ assertNotNull(mLib1TextView2);
+ assertNotNull(mLib2TextView1);
+ assertNotNull(mLib2TextView2);
+ assertNotNull(mLib2bTextView1);
+ assertNotNull(mLib2bTextView2);
+ assertNotNull(mLibappTextView1);
+ assertNotNull(mLibappTextView2);
+ }
+
+ @MediumTest
+ public void testAndroidStrings() {
+ assertEquals(mAppTextView1.getText(), "SUCCESS-APP");
+ assertEquals(mLib1TextView1.getText(), "SUCCESS-LIB1");
+ assertEquals(mLib2TextView1.getText(), "SUCCESS-LIB2");
+ assertEquals(mLib2bTextView1.getText(), "SUCCESS-LIB2b");
+ assertEquals(mLibappTextView1.getText(), "SUCCESS-LIBAPP");
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals(mAppTextView2.getText(), "SUCCESS-APP");
+ assertEquals(mLib1TextView2.getText(), "SUCCESS-LIB1");
+ assertEquals(mLib2TextView2.getText(), "SUCCESS-LIB2");
+ assertEquals(mLib2bTextView2.getText(), "SUCCESS-LIB2b");
+ assertEquals(mLibappTextView2.getText(), "SUCCESS-LIBAPP");
+ }
+}
diff --git a/build-system/tests/sameNamedLibs/app/src/main/AndroidManifest.xml b/build-system/tests/sameNamedLibs/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..74f0ff2
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/app/src/main/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.libstest.app"
+ android:versionCode="1"
+ android:versionName="1.0" xmlns:tools="http://schemas.android.com/tools">
+
+ <uses-sdk
+ android:minSdkVersion="15"
+ tools:ignore="UsesMinSdkAttributes" />
+
+ <application
+ android:icon="@drawable/icon"
+ android:label="@string/app_name" >
+ <activity
+ android:name="com.android.tests.libstest.app.MainActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/build-system/tests/sameNamedLibs/app/src/main/java/com/android/tests/libstest/app/App.java b/build-system/tests/sameNamedLibs/app/src/main/java/com/android/tests/libstest/app/App.java
new file mode 100644
index 0000000..54e2a09
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/app/src/main/java/com/android/tests/libstest/app/App.java
@@ -0,0 +1,43 @@
+package com.android.tests.libstest.app;
+
+import android.app.Activity;
+import android.widget.TextView;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+public class App {
+
+ public static void handleTextView(Activity a) {
+ TextView tv = (TextView) a.findViewById(R.id.app_text2);
+ if (tv != null) {
+ tv.setText(getContent());
+ }
+ }
+
+ private static String getContent() {
+ InputStream input = App.class.getResourceAsStream("App.txt");
+ if (input == null) {
+ return "FAILED TO FIND App.txt";
+ }
+
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+
+ return reader.readLine();
+ } catch (IOException e) {
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ return "FAILED TO READ CONTENT";
+ }
+}
diff --git a/build-system/tests/sameNamedLibs/app/src/main/java/com/android/tests/libstest/app/MainActivity.java b/build-system/tests/sameNamedLibs/app/src/main/java/com/android/tests/libstest/app/MainActivity.java
new file mode 100644
index 0000000..739f91e
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/app/src/main/java/com/android/tests/libstest/app/MainActivity.java
@@ -0,0 +1,23 @@
+package com.android.tests.libstest.app;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.tests.libstest.lib1.Lib1;
+import com.android.tests.libstest.lib2.Lib2;
+import com.android.tests.libstest.lib2.Lib2b;
+
+public class MainActivity extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ App.handleTextView(this);
+ Lib1.handleTextView(this);
+ Lib2.handleTextView(this);
+ Lib2b.handleTextView(this);
+ LibApp.handleTextView(this);
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/sameNamedLibs/app/src/main/res/drawable-hdpi/icon.png b/build-system/tests/sameNamedLibs/app/src/main/res/drawable-hdpi/icon.png
new file mode 100644
index 0000000..8074c4c
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/app/src/main/res/drawable-hdpi/icon.png
Binary files differ
diff --git a/build-system/tests/sameNamedLibs/app/src/main/res/drawable-ldpi/icon.png b/build-system/tests/sameNamedLibs/app/src/main/res/drawable-ldpi/icon.png
new file mode 100644
index 0000000..1095584
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/app/src/main/res/drawable-ldpi/icon.png
Binary files differ
diff --git a/build-system/tests/sameNamedLibs/app/src/main/res/drawable-mdpi/icon.png b/build-system/tests/sameNamedLibs/app/src/main/res/drawable-mdpi/icon.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/app/src/main/res/drawable-mdpi/icon.png
Binary files differ
diff --git a/build-system/tests/sameNamedLibs/app/src/main/res/layout/main.xml b/build-system/tests/sameNamedLibs/app/src/main/res/layout/main.xml
new file mode 100644
index 0000000..fa1aaba
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/app/src/main/res/layout/main.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/app_text1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/app_string" />
+
+ <TextView
+ android:id="@+id/app_text2"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+ <include layout="@layout/lib1_main" />
+
+ <include layout="@layout/lib2b_main" />
+
+ <include layout="@layout/libapp_main" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/build-system/tests/sameNamedLibs/app/src/main/res/values/strings.xml b/build-system/tests/sameNamedLibs/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..2a2e006
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/app/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="app_name">libsTest-app</string>
+ <string name="app_string">SUCCESS-APP</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/sameNamedLibs/app/src/main/resources/com/android/tests/libstest/app/App.txt b/build-system/tests/sameNamedLibs/app/src/main/resources/com/android/tests/libstest/app/App.txt
new file mode 100644
index 0000000..084e7d0
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/app/src/main/resources/com/android/tests/libstest/app/App.txt
@@ -0,0 +1 @@
+SUCCESS-APP
\ No newline at end of file
diff --git a/build-system/tests/sameNamedLibs/build.gradle b/build-system/tests/sameNamedLibs/build.gradle
new file mode 100644
index 0000000..a8fdb64
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/build.gradle
@@ -0,0 +1,10 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+
+apply plugin: 'android-reporting'
\ No newline at end of file
diff --git a/build-system/tests/sameNamedLibs/lib1/libs/build.gradle b/build-system/tests/sameNamedLibs/lib1/libs/build.gradle
new file mode 100644
index 0000000..32e056b
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib1/libs/build.gradle
@@ -0,0 +1,15 @@
+apply plugin: 'android-library'
+
+dependencies {
+ compile project(':lib2:libs')
+}
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+
+ defaultConfig {
+ minSdkVersion 14
+ targetSdkVersion 15
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/sameNamedLibs/lib1/libs/proguard-project.txt b/build-system/tests/sameNamedLibs/lib1/libs/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib1/libs/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/build-system/tests/sameNamedLibs/lib1/libs/src/instrumentTest/java/com/android/tests/libstest/lib1/MainActivityTest.java b/build-system/tests/sameNamedLibs/lib1/libs/src/instrumentTest/java/com/android/tests/libstest/lib1/MainActivityTest.java
new file mode 100644
index 0000000..4ed7ae6
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib1/libs/src/instrumentTest/java/com/android/tests/libstest/lib1/MainActivityTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.libstest.lib1;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private TextView mLib1TextView1;
+ private TextView mLib1TextView2;
+ private TextView mLib2TextView1;
+ private TextView mLib2TextView2;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public MainActivityTest() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final MainActivity a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ mLib1TextView1 = (TextView) a.findViewById(R.id.lib1_text1);
+ mLib1TextView2 = (TextView) a.findViewById(R.id.lib1_text2);
+ mLib2TextView1 = (TextView) a.findViewById(R.id.lib2_text1);
+ mLib2TextView2 = (TextView) a.findViewById(R.id.lib2_text2);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mLib1TextView1);
+ assertNotNull(mLib1TextView2);
+ assertNotNull(mLib2TextView1);
+ assertNotNull(mLib2TextView2);
+ }
+
+ @MediumTest
+ public void testAndroidStrings() {
+ assertEquals("SUCCESS-LIB1", mLib1TextView1.getText());
+ assertEquals("SUCCESS-LIB2", mLib2TextView1.getText());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB1", mLib1TextView2.getText());
+ assertEquals("SUCCESS-LIB2", mLib2TextView2.getText());
+ }
+}
diff --git a/build-system/tests/sameNamedLibs/lib1/libs/src/main/AndroidManifest.xml b/build-system/tests/sameNamedLibs/lib1/libs/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..7739b4a
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib1/libs/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.libstest.lib1"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/lib1_name" >
+ <activity
+ android:name="MainActivity"
+ android:label="@string/lib1_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/build-system/tests/sameNamedLibs/lib1/libs/src/main/java/com/android/tests/libstest/lib1/Lib1.java b/build-system/tests/sameNamedLibs/lib1/libs/src/main/java/com/android/tests/libstest/lib1/Lib1.java
new file mode 100644
index 0000000..c62bec2
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib1/libs/src/main/java/com/android/tests/libstest/lib1/Lib1.java
@@ -0,0 +1,43 @@
+package com.android.tests.libstest.lib1;
+
+import android.app.Activity;
+import android.widget.TextView;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+public class Lib1 {
+
+ public static void handleTextView(Activity a) {
+ TextView tv = (TextView) a.findViewById(R.id.lib1_text2);
+ if (tv != null) {
+ tv.setText(Lib1.getContent());
+ }
+ }
+
+ public static String getContent() {
+ InputStream input = Lib1.class.getResourceAsStream("Lib1.txt");
+ if (input == null) {
+ return "FAILED TO FIND Lib1.txt";
+ }
+
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+
+ return reader.readLine();
+ } catch (IOException e) {
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ return "FAILED TO READ CONTENT";
+ }
+}
diff --git a/build-system/tests/sameNamedLibs/lib1/libs/src/main/java/com/android/tests/libstest/lib1/MainActivity.java b/build-system/tests/sameNamedLibs/lib1/libs/src/main/java/com/android/tests/libstest/lib1/MainActivity.java
new file mode 100644
index 0000000..078bf64
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib1/libs/src/main/java/com/android/tests/libstest/lib1/MainActivity.java
@@ -0,0 +1,18 @@
+package com.android.tests.libstest.lib1;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.tests.libstest.lib2.Lib2;
+
+public class MainActivity extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.lib1_main);
+
+ Lib1.handleTextView(this);
+ Lib2.handleTextView(this);
+ }
+}
diff --git a/build-system/tests/sameNamedLibs/lib1/libs/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/tests/sameNamedLibs/lib1/libs/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..8074c4c
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib1/libs/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/sameNamedLibs/lib1/libs/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/tests/sameNamedLibs/lib1/libs/src/main/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..1095584
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib1/libs/src/main/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/sameNamedLibs/lib1/libs/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/tests/sameNamedLibs/lib1/libs/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib1/libs/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/sameNamedLibs/lib1/libs/src/main/res/layout/lib1_main.xml b/build-system/tests/sameNamedLibs/lib1/libs/src/main/res/layout/lib1_main.xml
new file mode 100644
index 0000000..3666d12
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib1/libs/src/main/res/layout/lib1_main.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/lib1_text1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/lib1_string" />
+
+ <TextView
+ android:id="@+id/lib1_text2"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+ <include layout="@layout/lib2_main" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/build-system/tests/sameNamedLibs/lib1/libs/src/main/res/values/strings.xml b/build-system/tests/sameNamedLibs/lib1/libs/src/main/res/values/strings.xml
new file mode 100644
index 0000000..8d20610
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib1/libs/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="lib1_name">LibsTest-lib1</string>
+ <string name="lib1_string">SUCCESS-LIB1</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/sameNamedLibs/lib1/libs/src/main/resources/com/android/tests/libstest/lib1/Lib1.txt b/build-system/tests/sameNamedLibs/lib1/libs/src/main/resources/com/android/tests/libstest/lib1/Lib1.txt
new file mode 100644
index 0000000..452e397
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib1/libs/src/main/resources/com/android/tests/libstest/lib1/Lib1.txt
@@ -0,0 +1 @@
+SUCCESS-LIB1
\ No newline at end of file
diff --git a/build-system/tests/sameNamedLibs/lib2/libs/build.gradle b/build-system/tests/sameNamedLibs/lib2/libs/build.gradle
new file mode 100644
index 0000000..4b2a733
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib2/libs/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'android-library'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
\ No newline at end of file
diff --git a/build-system/tests/sameNamedLibs/lib2/libs/proguard-project.txt b/build-system/tests/sameNamedLibs/lib2/libs/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib2/libs/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/build-system/tests/sameNamedLibs/lib2/libs/src/instrumentTest/java/com/android/tests/libstest/lib2/MainActivityTest.java b/build-system/tests/sameNamedLibs/lib2/libs/src/instrumentTest/java/com/android/tests/libstest/lib2/MainActivityTest.java
new file mode 100644
index 0000000..6ac4a5c
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib2/libs/src/instrumentTest/java/com/android/tests/libstest/lib2/MainActivityTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.libstest.lib2;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+import com.android.tests.libstest.lib2.R;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
+
+ private TextView mTextView1;
+ private TextView mTextView2;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public MainActivityTest() {
+ super(MainActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final MainActivity a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ mTextView1 = (TextView) a.findViewById(R.id.lib2_text1);
+ mTextView2 = (TextView) a.findViewById(R.id.lib2_text2);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView1);
+ assertNotNull(mTextView2);
+ }
+
+ @MediumTest
+ public void testAndroidStrings() {
+ assertEquals("SUCCESS-LIB2", mTextView1.getText());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB2", mTextView2.getText());
+ }
+}
diff --git a/build-system/tests/sameNamedLibs/lib2/libs/src/main/AndroidManifest.xml b/build-system/tests/sameNamedLibs/lib2/libs/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..9374b53
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib2/libs/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.libstest.lib2"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/lib2_name" >
+ <activity
+ android:name="MainActivity"
+ android:label="@string/lib2_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/build-system/tests/sameNamedLibs/lib2/libs/src/main/java/com/android/tests/libstest/lib2/Lib2.java b/build-system/tests/sameNamedLibs/lib2/libs/src/main/java/com/android/tests/libstest/lib2/Lib2.java
new file mode 100644
index 0000000..bb8e4db
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib2/libs/src/main/java/com/android/tests/libstest/lib2/Lib2.java
@@ -0,0 +1,43 @@
+package com.android.tests.libstest.lib2;
+
+import android.app.Activity;
+import android.widget.TextView;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+public class Lib2 {
+
+ public static void handleTextView(Activity a) {
+ TextView tv = (TextView) a.findViewById(R.id.lib2_text2);
+ if (tv != null) {
+ tv.setText(getContent());
+ }
+ }
+
+ private static String getContent() {
+ InputStream input = Lib2.class.getResourceAsStream("Lib2.txt");
+ if (input == null) {
+ return "FAILED TO FIND Lib2.txt";
+ }
+
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+
+ return reader.readLine();
+ } catch (IOException e) {
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ return "FAILED TO READ CONTENT";
+ }
+}
diff --git a/build-system/tests/sameNamedLibs/lib2/libs/src/main/java/com/android/tests/libstest/lib2/MainActivity.java b/build-system/tests/sameNamedLibs/lib2/libs/src/main/java/com/android/tests/libstest/lib2/MainActivity.java
new file mode 100644
index 0000000..012f203
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib2/libs/src/main/java/com/android/tests/libstest/lib2/MainActivity.java
@@ -0,0 +1,15 @@
+package com.android.tests.libstest.lib2;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class MainActivity extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.lib2_main);
+
+ Lib2.handleTextView(this);
+ }
+}
diff --git a/build-system/tests/sameNamedLibs/lib2/libs/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/tests/sameNamedLibs/lib2/libs/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..8074c4c
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib2/libs/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/sameNamedLibs/lib2/libs/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/tests/sameNamedLibs/lib2/libs/src/main/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..1095584
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib2/libs/src/main/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/sameNamedLibs/lib2/libs/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/tests/sameNamedLibs/lib2/libs/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib2/libs/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/sameNamedLibs/lib2/libs/src/main/res/layout/lib2_main.xml b/build-system/tests/sameNamedLibs/lib2/libs/src/main/res/layout/lib2_main.xml
new file mode 100644
index 0000000..bb639d1
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib2/libs/src/main/res/layout/lib2_main.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/lib2_text1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/lib2_string" />
+
+ <TextView
+ android:id="@+id/lib2_text2"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/build-system/tests/sameNamedLibs/lib2/libs/src/main/res/values/strings.xml b/build-system/tests/sameNamedLibs/lib2/libs/src/main/res/values/strings.xml
new file mode 100644
index 0000000..215b8fa
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib2/libs/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="lib2_name">LibsTest-lib2</string>
+ <string name="lib2_string">SUCCESS-LIB2</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/sameNamedLibs/lib2/libs/src/main/resources/com/android/tests/libstest/lib2/Lib2.txt b/build-system/tests/sameNamedLibs/lib2/libs/src/main/resources/com/android/tests/libstest/lib2/Lib2.txt
new file mode 100644
index 0000000..94cabe4
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib2/libs/src/main/resources/com/android/tests/libstest/lib2/Lib2.txt
@@ -0,0 +1 @@
+SUCCESS-LIB2
\ No newline at end of file
diff --git a/build-system/tests/sameNamedLibs/lib2b/libs/build.gradle b/build-system/tests/sameNamedLibs/lib2b/libs/build.gradle
new file mode 100644
index 0000000..4b2a733
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib2b/libs/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'android-library'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
\ No newline at end of file
diff --git a/build-system/tests/sameNamedLibs/lib2b/libs/proguard-project.txt b/build-system/tests/sameNamedLibs/lib2b/libs/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib2b/libs/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/build-system/tests/sameNamedLibs/lib2b/libs/src/instrumentTest/java/com/android/tests/libstest/lib2/MainActivity2bTest.java b/build-system/tests/sameNamedLibs/lib2b/libs/src/instrumentTest/java/com/android/tests/libstest/lib2/MainActivity2bTest.java
new file mode 100644
index 0000000..35c56e3
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib2b/libs/src/instrumentTest/java/com/android/tests/libstest/lib2/MainActivity2bTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.libstest.lib2;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+import com.android.tests.libstest.lib2.R;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class MainActivity2bTest extends ActivityInstrumentationTestCase2<MainActivity2b> {
+
+ private TextView mTextView1;
+ private TextView mTextView2;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public MainActivity2bTest() {
+ super(MainActivity2b.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final MainActivity2b a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ mTextView1 = (TextView) a.findViewById(R.id.lib2b_text1);
+ mTextView2 = (TextView) a.findViewById(R.id.lib2b_text2);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView1);
+ assertNotNull(mTextView2);
+ }
+
+ @MediumTest
+ public void testAndroidStrings() {
+ assertEquals("SUCCESS-LIB2b", mTextView1.getText());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIB2b", mTextView2.getText());
+ }
+}
diff --git a/build-system/tests/sameNamedLibs/lib2b/libs/src/main/AndroidManifest.xml b/build-system/tests/sameNamedLibs/lib2b/libs/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..814af46
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib2b/libs/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.libstest.lib2"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/lib2b_name" >
+ <activity
+ android:name="MainActivity2b"
+ android:label="@string/lib2b_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/build-system/tests/sameNamedLibs/lib2b/libs/src/main/java/com/android/tests/libstest/lib2/Lib2b.java b/build-system/tests/sameNamedLibs/lib2b/libs/src/main/java/com/android/tests/libstest/lib2/Lib2b.java
new file mode 100644
index 0000000..e4329e5
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib2b/libs/src/main/java/com/android/tests/libstest/lib2/Lib2b.java
@@ -0,0 +1,43 @@
+package com.android.tests.libstest.lib2;
+
+import android.app.Activity;
+import android.widget.TextView;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+public class Lib2b {
+
+ public static void handleTextView(Activity a) {
+ TextView tv = (TextView) a.findViewById(R.id.lib2b_text2);
+ if (tv != null) {
+ tv.setText(getContent());
+ }
+ }
+
+ private static String getContent() {
+ InputStream input = Lib2b.class.getResourceAsStream("Lib2b.txt");
+ if (input == null) {
+ return "FAILED TO FIND Lib2b.txt";
+ }
+
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+
+ return reader.readLine();
+ } catch (IOException e) {
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ return "FAILED TO READ CONTENT";
+ }
+}
diff --git a/build-system/tests/sameNamedLibs/lib2b/libs/src/main/java/com/android/tests/libstest/lib2/MainActivity2b.java b/build-system/tests/sameNamedLibs/lib2b/libs/src/main/java/com/android/tests/libstest/lib2/MainActivity2b.java
new file mode 100644
index 0000000..2e09018
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib2b/libs/src/main/java/com/android/tests/libstest/lib2/MainActivity2b.java
@@ -0,0 +1,15 @@
+package com.android.tests.libstest.lib2;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class MainActivity2b extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.lib2b_main);
+
+ Lib2b.handleTextView(this);
+ }
+}
diff --git a/build-system/tests/sameNamedLibs/lib2b/libs/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/tests/sameNamedLibs/lib2b/libs/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..8074c4c
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib2b/libs/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/sameNamedLibs/lib2b/libs/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/tests/sameNamedLibs/lib2b/libs/src/main/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..1095584
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib2b/libs/src/main/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/sameNamedLibs/lib2b/libs/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/tests/sameNamedLibs/lib2b/libs/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib2b/libs/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/sameNamedLibs/lib2b/libs/src/main/res/layout/lib2b_main.xml b/build-system/tests/sameNamedLibs/lib2b/libs/src/main/res/layout/lib2b_main.xml
new file mode 100644
index 0000000..01e6a9f
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib2b/libs/src/main/res/layout/lib2b_main.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/lib2b_text1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/lib2b_string" />
+
+ <TextView
+ android:id="@+id/lib2b_text2"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/build-system/tests/sameNamedLibs/lib2b/libs/src/main/res/values/strings.xml b/build-system/tests/sameNamedLibs/lib2b/libs/src/main/res/values/strings.xml
new file mode 100644
index 0000000..d21e21f
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib2b/libs/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="lib2b_name">LibsTest-lib2b</string>
+ <string name="lib2b_string">SUCCESS-LIB2b</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/sameNamedLibs/lib2b/libs/src/main/resources/com/android/tests/libstest/lib2/Lib2b.txt b/build-system/tests/sameNamedLibs/lib2b/libs/src/main/resources/com/android/tests/libstest/lib2/Lib2b.txt
new file mode 100644
index 0000000..59e6b48
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/lib2b/libs/src/main/resources/com/android/tests/libstest/lib2/Lib2b.txt
@@ -0,0 +1 @@
+SUCCESS-LIB2b
\ No newline at end of file
diff --git a/build-system/tests/sameNamedLibs/libapp/libs/build.gradle b/build-system/tests/sameNamedLibs/libapp/libs/build.gradle
new file mode 100644
index 0000000..4b2a733
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/libapp/libs/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'android-library'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
\ No newline at end of file
diff --git a/build-system/tests/sameNamedLibs/libapp/libs/proguard-project.txt b/build-system/tests/sameNamedLibs/libapp/libs/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/libapp/libs/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/build-system/tests/sameNamedLibs/libapp/libs/src/instrumentTest/java/com/android/tests/libstest/libapp/MainActivityLibAppTest.java b/build-system/tests/sameNamedLibs/libapp/libs/src/instrumentTest/java/com/android/tests/libstest/libapp/MainActivityLibAppTest.java
new file mode 100644
index 0000000..e6087a7
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/libapp/libs/src/instrumentTest/java/com/android/tests/libstest/libapp/MainActivityLibAppTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.libstest.app;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+import com.android.tests.libstest.app.R;
+
+/**
+ * An example of an {@link ActivityInstrumentationTestCase2} of a specific activity {@link Focus2}.
+ * By virtue of extending {@link ActivityInstrumentationTestCase2}, the target activity is automatically
+ * launched and finished before and after each test. This also extends
+ * {@link android.test.InstrumentationTestCase}, which provides
+ * access to methods for sending events to the target activity, such as key and
+ * touch events. See {@link #sendKeys}.
+ *
+ * In general, {@link android.test.InstrumentationTestCase}s and {@link ActivityInstrumentationTestCase2}s
+ * are heavier weight functional tests available for end to end testing of your
+ * user interface. When run via a {@link android.test.InstrumentationTestRunner},
+ * the necessary {@link android.app.Instrumentation} will be injected for you to
+ * user via {@link #getInstrumentation} in your tests.
+ *
+ * See {@link com.example.android.apis.AllTests} for documentation on running
+ * all tests and individual tests in this application.
+ */
+public class MainActivityLibAppTest extends ActivityInstrumentationTestCase2<MainActivityLibApp> {
+
+ private TextView mTextView1;
+ private TextView mTextView2;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Focus2} activity.
+ */
+ public MainActivityLibAppTest() {
+ super(MainActivityLibApp.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final MainActivityLibApp a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ mTextView1 = (TextView) a.findViewById(R.id.libapp_text1);
+ mTextView2 = (TextView) a.findViewById(R.id.libapp_text2);
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView1);
+ assertNotNull(mTextView2);
+ }
+
+ @MediumTest
+ public void testAndroidStrings() {
+ assertEquals("SUCCESS-LIBAPP", mTextView1.getText());
+ }
+
+ @MediumTest
+ public void testJavaStrings() {
+ assertEquals("SUCCESS-LIBAPP", mTextView2.getText());
+ }
+}
diff --git a/build-system/tests/sameNamedLibs/libapp/libs/src/main/AndroidManifest.xml b/build-system/tests/sameNamedLibs/libapp/libs/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..0592d2d
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/libapp/libs/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.libstest.app"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/libapp_name" >
+ <activity
+ android:name="MainActivityLibApp"
+ android:label="@string/libapp_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/build-system/tests/sameNamedLibs/libapp/libs/src/main/java/com/android/tests/libstest/app/LibApp.java b/build-system/tests/sameNamedLibs/libapp/libs/src/main/java/com/android/tests/libstest/app/LibApp.java
new file mode 100644
index 0000000..9a25e9e
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/libapp/libs/src/main/java/com/android/tests/libstest/app/LibApp.java
@@ -0,0 +1,43 @@
+package com.android.tests.libstest.app;
+
+import android.app.Activity;
+import android.widget.TextView;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+public class LibApp {
+
+ public static void handleTextView(Activity a) {
+ TextView tv = (TextView) a.findViewById(R.id.libapp_text2);
+ if (tv != null) {
+ tv.setText(getContent());
+ }
+ }
+
+ private static String getContent() {
+ InputStream input = LibApp.class.getResourceAsStream("Libapp.txt");
+ if (input == null) {
+ return "FAILED TO FIND Libapp.txt";
+ }
+
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
+
+ return reader.readLine();
+ } catch (IOException e) {
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ return "FAILED TO READ CONTENT";
+ }
+}
diff --git a/build-system/tests/sameNamedLibs/libapp/libs/src/main/java/com/android/tests/libstest/app/MainActivityLibApp.java b/build-system/tests/sameNamedLibs/libapp/libs/src/main/java/com/android/tests/libstest/app/MainActivityLibApp.java
new file mode 100644
index 0000000..65460f7
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/libapp/libs/src/main/java/com/android/tests/libstest/app/MainActivityLibApp.java
@@ -0,0 +1,15 @@
+package com.android.tests.libstest.app;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class MainActivityLibApp extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.libapp_main);
+
+ LibApp.handleTextView(this);
+ }
+}
diff --git a/build-system/tests/sameNamedLibs/libapp/libs/src/main/res/drawable-hdpi/ic_launcher.png b/build-system/tests/sameNamedLibs/libapp/libs/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..8074c4c
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/libapp/libs/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/sameNamedLibs/libapp/libs/src/main/res/drawable-ldpi/ic_launcher.png b/build-system/tests/sameNamedLibs/libapp/libs/src/main/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..1095584
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/libapp/libs/src/main/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/sameNamedLibs/libapp/libs/src/main/res/drawable-mdpi/ic_launcher.png b/build-system/tests/sameNamedLibs/libapp/libs/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/libapp/libs/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/build-system/tests/sameNamedLibs/libapp/libs/src/main/res/layout/libapp_main.xml b/build-system/tests/sameNamedLibs/libapp/libs/src/main/res/layout/libapp_main.xml
new file mode 100644
index 0000000..6bf607b
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/libapp/libs/src/main/res/layout/libapp_main.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/libapp_text1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/libapp_string" />
+
+ <TextView
+ android:id="@+id/libapp_text2"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/build-system/tests/sameNamedLibs/libapp/libs/src/main/res/values/strings.xml b/build-system/tests/sameNamedLibs/libapp/libs/src/main/res/values/strings.xml
new file mode 100644
index 0000000..0a0a597
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/libapp/libs/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="libapp_name">LibsTest-libapp</string>
+ <string name="libapp_string">SUCCESS-LIBAPP</string>
+
+</resources>
\ No newline at end of file
diff --git a/build-system/tests/sameNamedLibs/libapp/libs/src/main/resources/com/android/tests/libstest/app/Libapp.txt b/build-system/tests/sameNamedLibs/libapp/libs/src/main/resources/com/android/tests/libstest/app/Libapp.txt
new file mode 100644
index 0000000..9d626ac
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/libapp/libs/src/main/resources/com/android/tests/libstest/app/Libapp.txt
@@ -0,0 +1 @@
+SUCCESS-LIBAPP
\ No newline at end of file
diff --git a/build-system/tests/sameNamedLibs/settings.gradle b/build-system/tests/sameNamedLibs/settings.gradle
new file mode 100644
index 0000000..c6f5d7b
--- /dev/null
+++ b/build-system/tests/sameNamedLibs/settings.gradle
@@ -0,0 +1,5 @@
+include 'app'
+include 'lib1:libs'
+include 'lib2:libs'
+include 'lib2b:libs'
+include 'libapp:libs'
diff --git a/build-system/tests/testWithDep/build.gradle b/build-system/tests/testWithDep/build.gradle
new file mode 100644
index 0000000..e7fb476
--- /dev/null
+++ b/build-system/tests/testWithDep/build.gradle
@@ -0,0 +1,22 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
+apply plugin: 'android'
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ instrumentTestCompile 'com.jayway.android.robotium:robotium-solo:4.3.1'
+}
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
\ No newline at end of file
diff --git a/build-system/tests/testWithDep/src/instrumentTest/java/com/android/tests/basic/MainTest.java b/build-system/tests/testWithDep/src/instrumentTest/java/com/android/tests/basic/MainTest.java
new file mode 100644
index 0000000..edc597f
--- /dev/null
+++ b/build-system/tests/testWithDep/src/instrumentTest/java/com/android/tests/basic/MainTest.java
@@ -0,0 +1,48 @@
+package com.android.tests.basic;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.TextView;
+
+import com.jayway.android.robotium.solo.Solo;
+
+public class MainTest extends ActivityInstrumentationTestCase2<Main> {
+
+ private Solo solo;
+
+ private TextView mTextView;
+
+ /**
+ * Creates an {@link ActivityInstrumentationTestCase2} that tests the {@link Main} activity.
+ */
+ public MainTest() {
+ super(Main.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final Main a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+ mTextView = (TextView) a.findViewById(R.id.text);
+ solo = new Solo(getInstrumentation(), getActivity());
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ solo.finishOpenedActivities();
+ }
+
+ /**
+ * The name 'test preconditions' is a convention to signal that if this
+ * test doesn't pass, the test case was not set up properly and it might
+ * explain any and all failures in other tests. This is not guaranteed
+ * to run before other tests, as junit uses reflection to find the tests.
+ */
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mTextView);
+ }
+}
+
diff --git a/build-system/tests/testWithDep/src/main/AndroidManifest.xml b/build-system/tests/testWithDep/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..4f8d570
--- /dev/null
+++ b/build-system/tests/testWithDep/src/main/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.basic">
+ <application android:label="@string/app_name" android:icon="@drawable/icon">
+ <activity android:name=".Main"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <uses-permission android:name="com.blah" />
+
+ <permission-group android:name="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+ <permission android:name="foo.permission.SEND_SMS"
+ android:permissionGroup="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+ <permission android:name="foo.blah.SEND_SMS"
+ android:permissionGroup="foo.permission-group.COST_MONEY"
+ android:label="@string/app_name"
+ android:description="@string/app_name" />
+
+</manifest>
diff --git a/build-system/tests/testWithDep/src/main/assets/notice.txt b/build-system/tests/testWithDep/src/main/assets/notice.txt
new file mode 100644
index 0000000..33ff961
--- /dev/null
+++ b/build-system/tests/testWithDep/src/main/assets/notice.txt
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2005-2012, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/build-system/tests/testWithDep/src/main/java/com/android/tests/basic/Main.java b/build-system/tests/testWithDep/src/main/java/com/android/tests/basic/Main.java
new file mode 100644
index 0000000..2b0e698
--- /dev/null
+++ b/build-system/tests/testWithDep/src/main/java/com/android/tests/basic/Main.java
@@ -0,0 +1,15 @@
+package com.android.tests.basic;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class Main extends Activity
+{
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ }
+}
diff --git a/build-system/tests/testWithDep/src/main/res/drawable/icon.png b/build-system/tests/testWithDep/src/main/res/drawable/icon.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/build-system/tests/testWithDep/src/main/res/drawable/icon.png
Binary files differ
diff --git a/build-system/tests/testWithDep/src/main/res/layout/main.xml b/build-system/tests/testWithDep/src/main/res/layout/main.xml
new file mode 100644
index 0000000..b199751
--- /dev/null
+++ b/build-system/tests/testWithDep/src/main/res/layout/main.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+<TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="Test App - Basic"
+ android:id="@+id/text"
+ />
+</LinearLayout>
+
diff --git a/build-system/tests/testWithDep/src/main/res/raw/notice.txt b/build-system/tests/testWithDep/src/main/res/raw/notice.txt
new file mode 100644
index 0000000..33ff961
--- /dev/null
+++ b/build-system/tests/testWithDep/src/main/res/raw/notice.txt
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2005-2012, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/build-system/tests/testWithDep/src/main/res/values/strings.xml b/build-system/tests/testWithDep/src/main/res/values/strings.xml
new file mode 100644
index 0000000..60ea2d0
--- /dev/null
+++ b/build-system/tests/testWithDep/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">_Test-Basic</string>
+</resources>
diff --git a/build-system/tests/testWithDep/src/release/res/values/strings.xml b/build-system/tests/testWithDep/src/release/res/values/strings.xml
new file mode 100644
index 0000000..532909c
--- /dev/null
+++ b/build-system/tests/testWithDep/src/release/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">_Test-Basic-Release</string>
+</resources>
diff --git a/build-system/tests/tictactoe/README.txt b/build-system/tests/tictactoe/README.txt
new file mode 100644
index 0000000..6a1ac65
--- /dev/null
+++ b/build-system/tests/tictactoe/README.txt
@@ -0,0 +1,21 @@
+Sample: tictactoe/lib and tictactoe/app
+
+--------
+Summary:
+--------
+
+These two projects work together. They demonstrate how to use the ability to
+split an APK into multiple projects.
+
+--------
+Details:
+--------
+
+'app' is the main project. It defines a main activity that is first
+displayed to the user. When one of the start buttons is selected, an
+activity defined in 'lib' is started.
+
+For more details on the purpose of this feature, its limitations and detailed usage,
+please read the SDK guide at
+ http://developer.android.com/guide/developing/eclipse-adt.html
+
diff --git a/build-system/tests/tictactoe/app/build.gradle b/build-system/tests/tictactoe/app/build.gradle
new file mode 100644
index 0000000..376c388
--- /dev/null
+++ b/build-system/tests/tictactoe/app/build.gradle
@@ -0,0 +1,10 @@
+apply plugin: 'android'
+
+dependencies {
+ compile project(':lib')
+}
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
diff --git a/build-system/tests/tictactoe/app/src/main/AndroidManifest.xml b/build-system/tests/tictactoe/app/src/main/AndroidManifest.xml
new file mode 100755
index 0000000..e62c9ed
--- /dev/null
+++ b/build-system/tests/tictactoe/app/src/main/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.tictactoe">
+
+ <uses-sdk android:minSdkVersion="8" />
+
+ <application android:icon="@drawable/icon" android:label="@string/app_name">
+ <activity android:name=".MainActivity"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/build-system/tests/tictactoe/app/src/main/java/com/example/android/tictactoe/MainActivity.java b/build-system/tests/tictactoe/app/src/main/java/com/example/android/tictactoe/MainActivity.java
new file mode 100755
index 0000000..14a9011
--- /dev/null
+++ b/build-system/tests/tictactoe/app/src/main/java/com/example/android/tictactoe/MainActivity.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.tictactoe;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+
+import com.example.android.tictactoe.library.GameActivity;
+import com.example.android.tictactoe.library.GameView.State;
+
+public class MainActivity extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ findViewById(R.id.start_player).setOnClickListener(
+ new OnClickListener() {
+ public void onClick(View v) {
+ startGame(true);
+ }
+ });
+
+ findViewById(R.id.start_comp).setOnClickListener(
+ new OnClickListener() {
+ public void onClick(View v) {
+ startGame(false);
+ }
+ });
+ }
+
+ private void startGame(boolean startWithHuman) {
+ Intent i = new Intent(this, GameActivity.class);
+ i.putExtra(GameActivity.EXTRA_START_PLAYER,
+ startWithHuman ? State.PLAYER1.getValue() : State.PLAYER2.getValue());
+ startActivity(i);
+ }
+}
\ No newline at end of file
diff --git a/build-system/tests/tictactoe/app/src/main/res/drawable/icon.png b/build-system/tests/tictactoe/app/src/main/res/drawable/icon.png
new file mode 100755
index 0000000..b8665ff
--- /dev/null
+++ b/build-system/tests/tictactoe/app/src/main/res/drawable/icon.png
Binary files differ
diff --git a/build-system/tests/tictactoe/app/src/main/res/layout/main.xml b/build-system/tests/tictactoe/app/src/main/res/layout/main.xml
new file mode 100755
index 0000000..1e75004
--- /dev/null
+++ b/build-system/tests/tictactoe/app/src/main/res/layout/main.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:gravity="center_horizontal"
+ >
+
+ <TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:layout_marginTop="20dip"
+ android:layout_marginBottom="5dip"
+ android:text="@string/welcome"
+ />
+ <TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:layout_marginBottom="5dip"
+ android:text="@string/explain2"
+ />
+ <TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:layout_marginBottom="20dip"
+ android:text="@string/explain1"
+ />
+
+ <Button
+ android:id="@+id/start_player"
+ android:text="@string/start_player"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ />
+ <Button
+ android:id="@+id/start_comp"
+ android:text="@string/start_comp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="10dip"
+ />
+
+ <ImageView
+ android:id="@+id/ImageView01"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/icon"
+ />
+
+</LinearLayout>
diff --git a/build-system/tests/tictactoe/app/src/main/res/values/strings.xml b/build-system/tests/tictactoe/app/src/main/res/values/strings.xml
new file mode 100755
index 0000000..436877f
--- /dev/null
+++ b/build-system/tests/tictactoe/app/src/main/res/values/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<resources>
+ <string name="start_comp">Start -- Computer goes first</string>
+ <string name="start_player">Start -- Player goes first</string>
+ <string name="welcome"><b>Welcome to the Tic-Tac-Toe Sample!</b></string>
+ <string name="explain1">This sample code demonstrates how to split an application in multiple projects by using the \'project library\' available in the Froyo SDK Tools.</string>
+ <string name="explain2">This activity is defined in one project. The second activity, launched by one of the buttons below, is located in another project which is a \"library\" to the main one and merged in the same APK.</string>
+ <string name="app_name">Tic-Tac-Toe Sample</string>
+</resources>
diff --git a/build-system/tests/tictactoe/build.gradle b/build-system/tests/tictactoe/build.gradle
new file mode 100644
index 0000000..83b3e0b
--- /dev/null
+++ b/build-system/tests/tictactoe/build.gradle
@@ -0,0 +1,8 @@
+buildscript {
+ repositories {
+ maven { url '../../../../../out/host/gradle/repo' }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.0-SNAPSHOT'
+ }
+}
diff --git a/build-system/tests/tictactoe/lib/build.gradle b/build-system/tests/tictactoe/lib/build.gradle
new file mode 100644
index 0000000..4b2a733
--- /dev/null
+++ b/build-system/tests/tictactoe/lib/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'android-library'
+
+android {
+ compileSdkVersion 15
+ buildToolsVersion "18.0.1"
+}
\ No newline at end of file
diff --git a/build-system/tests/tictactoe/lib/src/main/AndroidManifest.xml b/build-system/tests/tictactoe/lib/src/main/AndroidManifest.xml
new file mode 100755
index 0000000..ee934a5
--- /dev/null
+++ b/build-system/tests/tictactoe/lib/src/main/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.tictactoe.library">
+ <application>
+ <activity android:name="GameActivity" />
+ </application>
+</manifest>
diff --git a/build-system/tests/tictactoe/lib/src/main/java/com/example/android/tictactoe/library/GameActivity.java b/build-system/tests/tictactoe/lib/src/main/java/com/example/android/tictactoe/library/GameActivity.java
new file mode 100755
index 0000000..df1cac0
--- /dev/null
+++ b/build-system/tests/tictactoe/lib/src/main/java/com/example/android/tictactoe/library/GameActivity.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.tictactoe.library;
+
+import java.util.Random;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Handler.Callback;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.example.android.tictactoe.library.GameView.ICellListener;
+import com.example.android.tictactoe.library.GameView.State;
+
+
+public class GameActivity extends Activity {
+
+ /** Start player. Must be 1 or 2. Default is 1. */
+ public static final String EXTRA_START_PLAYER =
+ "com.example.android.tictactoe.library.GameActivity.EXTRA_START_PLAYER";
+
+ private static final int MSG_COMPUTER_TURN = 1;
+ private static final long COMPUTER_DELAY_MS = 500;
+
+ private Handler mHandler = new Handler(new MyHandlerCallback());
+ private Random mRnd = new Random();
+ private GameView mGameView;
+ private TextView mInfoView;
+ private Button mButtonNext;
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+
+ /*
+ * IMPORTANT: all resource IDs from this library will eventually be merged
+ * with the resources from the main project that will use the library.
+ *
+ * If the main project and the libraries define the same resource IDs,
+ * the application project will always have priority and override library resources
+ * and IDs defined in multiple libraries are resolved based on the libraries priority
+ * defined in the main project.
+ *
+ * An intentional consequence is that the main project can override some resources
+ * from the library.
+ * (TODO insert example).
+ *
+ * To avoid potential conflicts, it is suggested to add a prefix to the
+ * library resource names.
+ */
+ setContentView(R.layout.lib_game);
+
+ mGameView = (GameView) findViewById(R.id.game_view);
+ mInfoView = (TextView) findViewById(R.id.info_turn);
+ mButtonNext = (Button) findViewById(R.id.next_turn);
+
+ mGameView.setFocusable(true);
+ mGameView.setFocusableInTouchMode(true);
+ mGameView.setCellListener(new MyCellListener());
+
+ mButtonNext.setOnClickListener(new MyButtonListener());
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ State player = mGameView.getCurrentPlayer();
+ if (player == State.UNKNOWN) {
+ player = State.fromInt(getIntent().getIntExtra(EXTRA_START_PLAYER, 1));
+ if (!checkGameFinished(player)) {
+ selectTurn(player);
+ }
+ }
+ if (player == State.PLAYER2) {
+ mHandler.sendEmptyMessageDelayed(MSG_COMPUTER_TURN, COMPUTER_DELAY_MS);
+ }
+ if (player == State.WIN) {
+ setWinState(mGameView.getWinner());
+ }
+ }
+
+
+ private State selectTurn(State player) {
+ mGameView.setCurrentPlayer(player);
+ mButtonNext.setEnabled(false);
+
+ if (player == State.PLAYER1) {
+ mInfoView.setText(R.string.player1_turn);
+ mGameView.setEnabled(true);
+
+ } else if (player == State.PLAYER2) {
+ mInfoView.setText(R.string.player2_turn);
+ mGameView.setEnabled(false);
+ }
+
+ return player;
+ }
+
+ private class MyCellListener implements ICellListener {
+ public void onCellSelected() {
+ if (mGameView.getCurrentPlayer() == State.PLAYER1) {
+ int cell = mGameView.getSelection();
+ mButtonNext.setEnabled(cell >= 0);
+ }
+ }
+ }
+
+ private class MyButtonListener implements OnClickListener {
+
+ public void onClick(View v) {
+ State player = mGameView.getCurrentPlayer();
+
+ if (player == State.WIN) {
+ GameActivity.this.finish();
+
+ } else if (player == State.PLAYER1) {
+ int cell = mGameView.getSelection();
+ if (cell >= 0) {
+ mGameView.stopBlink();
+ mGameView.setCell(cell, player);
+ finishTurn();
+ }
+ }
+ }
+ }
+
+ private class MyHandlerCallback implements Callback {
+ public boolean handleMessage(Message msg) {
+ if (msg.what == MSG_COMPUTER_TURN) {
+
+ // Pick a non-used cell at random. That's about all the AI you need for this game.
+ State[] data = mGameView.getData();
+ int used = 0;
+ while (used != 0x1F) {
+ int index = mRnd.nextInt(9);
+ if (((used >> index) & 1) == 0) {
+ used |= 1 << index;
+ if (data[index] == State.EMPTY) {
+ mGameView.setCell(index, mGameView.getCurrentPlayer());
+ break;
+ }
+ }
+ }
+
+ finishTurn();
+ return true;
+ }
+ return false;
+ }
+ }
+
+ private State getOtherPlayer(State player) {
+ return player == State.PLAYER1 ? State.PLAYER2 : State.PLAYER1;
+ }
+
+ private void finishTurn() {
+ State player = mGameView.getCurrentPlayer();
+ if (!checkGameFinished(player)) {
+ player = selectTurn(getOtherPlayer(player));
+ if (player == State.PLAYER2) {
+ mHandler.sendEmptyMessageDelayed(MSG_COMPUTER_TURN, COMPUTER_DELAY_MS);
+ }
+ }
+ }
+
+ public boolean checkGameFinished(State player) {
+ State[] data = mGameView.getData();
+ boolean full = true;
+
+ int col = -1;
+ int row = -1;
+ int diag = -1;
+
+ // check rows
+ for (int j = 0, k = 0; j < 3; j++, k += 3) {
+ if (data[k] != State.EMPTY && data[k] == data[k+1] && data[k] == data[k+2]) {
+ row = j;
+ }
+ if (full && (data[k] == State.EMPTY ||
+ data[k+1] == State.EMPTY ||
+ data[k+2] == State.EMPTY)) {
+ full = false;
+ }
+ }
+
+ // check columns
+ for (int i = 0; i < 3; i++) {
+ if (data[i] != State.EMPTY && data[i] == data[i+3] && data[i] == data[i+6]) {
+ col = i;
+ }
+ }
+
+ // check diagonals
+ if (data[0] != State.EMPTY && data[0] == data[1+3] && data[0] == data[2+6]) {
+ diag = 0;
+ } else if (data[2] != State.EMPTY && data[2] == data[1+3] && data[2] == data[0+6]) {
+ diag = 1;
+ }
+
+ if (col != -1 || row != -1 || diag != -1) {
+ setFinished(player, col, row, diag);
+ return true;
+ }
+
+ // if we get here, there's no winner but the board is full.
+ if (full) {
+ setFinished(State.EMPTY, -1, -1, -1);
+ return true;
+ }
+ return false;
+ }
+
+ private void setFinished(State player, int col, int row, int diagonal) {
+
+ mGameView.setCurrentPlayer(State.WIN);
+ mGameView.setWinner(player);
+ mGameView.setEnabled(false);
+ mGameView.setFinished(col, row, diagonal);
+
+ setWinState(player);
+ }
+
+ private void setWinState(State player) {
+ mButtonNext.setEnabled(true);
+ mButtonNext.setText("Back");
+
+ String text;
+
+ if (player == State.EMPTY) {
+ text = getString(R.string.tie);
+ } else if (player == State.PLAYER1) {
+ text = getString(R.string.player1_win);
+ } else {
+ text = getString(R.string.player2_win);
+ }
+ mInfoView.setText(text);
+ }
+}
diff --git a/build-system/tests/tictactoe/lib/src/main/java/com/example/android/tictactoe/library/GameView.java b/build-system/tests/tictactoe/lib/src/main/java/com/example/android/tictactoe/library/GameView.java
new file mode 100755
index 0000000..3af516a
--- /dev/null
+++ b/build-system/tests/tictactoe/lib/src/main/java/com/example/android/tictactoe/library/GameView.java
@@ -0,0 +1,468 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.tictactoe.library;
+
+import java.util.Random;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Bitmap.Config;
+import android.graphics.BitmapFactory.Options;
+import android.graphics.Paint.Style;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Parcelable;
+import android.os.Handler.Callback;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+//-----------------------------------------------
+
+public class GameView extends View {
+
+ public static final long FPS_MS = 1000/2;
+
+ public enum State {
+ UNKNOWN(-3),
+ WIN(-2),
+ EMPTY(0),
+ PLAYER1(1),
+ PLAYER2(2);
+
+ private int mValue;
+
+ private State(int value) {
+ mValue = value;
+ }
+
+ public int getValue() {
+ return mValue;
+ }
+
+ public static State fromInt(int i) {
+ for (State s : values()) {
+ if (s.getValue() == i) {
+ return s;
+ }
+ }
+ return EMPTY;
+ }
+ }
+
+ private static final int MARGIN = 4;
+ private static final int MSG_BLINK = 1;
+
+ private final Handler mHandler = new Handler(new MyHandler());
+
+ private final Rect mSrcRect = new Rect();
+ private final Rect mDstRect = new Rect();
+
+ private int mSxy;
+ private int mOffetX;
+ private int mOffetY;
+ private Paint mWinPaint;
+ private Paint mLinePaint;
+ private Paint mBmpPaint;
+ private Bitmap mBmpPlayer1;
+ private Bitmap mBmpPlayer2;
+ private Drawable mDrawableBg;
+
+ private ICellListener mCellListener;
+
+ /** Contains one of {@link State#EMPTY}, {@link State#PLAYER1} or {@link State#PLAYER2}. */
+ private final State[] mData = new State[9];
+
+ private int mSelectedCell = -1;
+ private State mSelectedValue = State.EMPTY;
+ private State mCurrentPlayer = State.UNKNOWN;
+ private State mWinner = State.EMPTY;
+
+ private int mWinCol = -1;
+ private int mWinRow = -1;
+ private int mWinDiag = -1;
+
+ private boolean mBlinkDisplayOff;
+ private final Rect mBlinkRect = new Rect();
+
+
+
+ public interface ICellListener {
+ abstract void onCellSelected();
+ }
+
+ public GameView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ requestFocus();
+
+ mDrawableBg = getResources().getDrawable(R.drawable.lib_bg);
+ setBackgroundDrawable(mDrawableBg);
+
+ mBmpPlayer1 = getResBitmap(R.drawable.lib_cross);
+ mBmpPlayer2 = getResBitmap(R.drawable.lib_circle);
+
+ if (mBmpPlayer1 != null) {
+ mSrcRect.set(0, 0, mBmpPlayer1.getWidth() -1, mBmpPlayer1.getHeight() - 1);
+ }
+
+ mBmpPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+ mLinePaint = new Paint();
+ mLinePaint.setColor(0xFFFFFFFF);
+ mLinePaint.setStrokeWidth(5);
+ mLinePaint.setStyle(Style.STROKE);
+
+ mWinPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mWinPaint.setColor(0xFFFF0000);
+ mWinPaint.setStrokeWidth(10);
+ mWinPaint.setStyle(Style.STROKE);
+
+ for (int i = 0; i < mData.length; i++) {
+ mData[i] = State.EMPTY;
+ }
+
+ if (isInEditMode()) {
+ // In edit mode (e.g. in the Eclipse ADT graphical layout editor)
+ // we'll use some random data to display the state.
+ Random rnd = new Random();
+ for (int i = 0; i < mData.length; i++) {
+ mData[i] = State.fromInt(rnd.nextInt(3));
+ }
+ }
+ }
+
+ public State[] getData() {
+ return mData;
+ }
+
+ public void setCell(int cellIndex, State value) {
+ mData[cellIndex] = value;
+ invalidate();
+ }
+
+ public void setCellListener(ICellListener cellListener) {
+ mCellListener = cellListener;
+ }
+
+ public int getSelection() {
+ if (mSelectedValue == mCurrentPlayer) {
+ return mSelectedCell;
+ }
+
+ return -1;
+ }
+
+ public State getCurrentPlayer() {
+ return mCurrentPlayer;
+ }
+
+ public void setCurrentPlayer(State player) {
+ mCurrentPlayer = player;
+ mSelectedCell = -1;
+ }
+
+ public State getWinner() {
+ return mWinner;
+ }
+
+ public void setWinner(State winner) {
+ mWinner = winner;
+ }
+
+ /** Sets winning mark on specified column or row (0..2) or diagonal (0..1). */
+ public void setFinished(int col, int row, int diagonal) {
+ mWinCol = col;
+ mWinRow = row;
+ mWinDiag = diagonal;
+ }
+
+ //-----------------------------------------
+
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ int sxy = mSxy;
+ int s3 = sxy * 3;
+ int x7 = mOffetX;
+ int y7 = mOffetY;
+
+ for (int i = 0, k = sxy; i < 2; i++, k += sxy) {
+ canvas.drawLine(x7 , y7 + k, x7 + s3 - 1, y7 + k , mLinePaint);
+ canvas.drawLine(x7 + k, y7 , x7 + k , y7 + s3 - 1, mLinePaint);
+ }
+
+ for (int j = 0, k = 0, y = y7; j < 3; j++, y += sxy) {
+ for (int i = 0, x = x7; i < 3; i++, k++, x += sxy) {
+ mDstRect.offsetTo(MARGIN+x, MARGIN+y);
+
+ State v;
+ if (mSelectedCell == k) {
+ if (mBlinkDisplayOff) {
+ continue;
+ }
+ v = mSelectedValue;
+ } else {
+ v = mData[k];
+ }
+
+ switch(v) {
+ case PLAYER1:
+ if (mBmpPlayer1 != null) {
+ canvas.drawBitmap(mBmpPlayer1, mSrcRect, mDstRect, mBmpPaint);
+ }
+ break;
+ case PLAYER2:
+ if (mBmpPlayer2 != null) {
+ canvas.drawBitmap(mBmpPlayer2, mSrcRect, mDstRect, mBmpPaint);
+ }
+ break;
+ }
+ }
+ }
+
+ if (mWinRow >= 0) {
+ int y = y7 + mWinRow * sxy + sxy / 2;
+ canvas.drawLine(x7 + MARGIN, y, x7 + s3 - 1 - MARGIN, y, mWinPaint);
+
+ } else if (mWinCol >= 0) {
+ int x = x7 + mWinCol * sxy + sxy / 2;
+ canvas.drawLine(x, y7 + MARGIN, x, y7 + s3 - 1 - MARGIN, mWinPaint);
+
+ } else if (mWinDiag == 0) {
+ // diagonal 0 is from (0,0) to (2,2)
+
+ canvas.drawLine(x7 + MARGIN, y7 + MARGIN,
+ x7 + s3 - 1 - MARGIN, y7 + s3 - 1 - MARGIN, mWinPaint);
+
+ } else if (mWinDiag == 1) {
+ // diagonal 1 is from (0,2) to (2,0)
+
+ canvas.drawLine(x7 + MARGIN, y7 + s3 - 1 - MARGIN,
+ x7 + s3 - 1 - MARGIN, y7 + MARGIN, mWinPaint);
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // Keep the view squared
+ int w = MeasureSpec.getSize(widthMeasureSpec);
+ int h = MeasureSpec.getSize(heightMeasureSpec);
+ int d = w == 0 ? h : h == 0 ? w : w < h ? w : h;
+ setMeasuredDimension(d, d);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+
+ int sx = (w - 2 * MARGIN) / 3;
+ int sy = (h - 2 * MARGIN) / 3;
+
+ int size = sx < sy ? sx : sy;
+
+ mSxy = size;
+ mOffetX = (w - 3 * size) / 2;
+ mOffetY = (h - 3 * size) / 2;
+
+ mDstRect.set(MARGIN, MARGIN, size - MARGIN, size - MARGIN);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ int action = event.getAction();
+
+ if (action == MotionEvent.ACTION_DOWN) {
+ return true;
+
+ } else if (action == MotionEvent.ACTION_UP) {
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+
+ int sxy = mSxy;
+ x = (x - MARGIN) / sxy;
+ y = (y - MARGIN) / sxy;
+
+ if (isEnabled() && x >= 0 && x < 3 && y >= 0 & y < 3) {
+ int cell = x + 3 * y;
+
+ State state = cell == mSelectedCell ? mSelectedValue : mData[cell];
+ state = state == State.EMPTY ? mCurrentPlayer : State.EMPTY;
+
+ stopBlink();
+
+ mSelectedCell = cell;
+ mSelectedValue = state;
+ mBlinkDisplayOff = false;
+ mBlinkRect.set(MARGIN + x * sxy, MARGIN + y * sxy,
+ MARGIN + (x + 1) * sxy, MARGIN + (y + 1) * sxy);
+
+ if (state != State.EMPTY) {
+ // Start the blinker
+ mHandler.sendEmptyMessageDelayed(MSG_BLINK, FPS_MS);
+ }
+
+ if (mCellListener != null) {
+ mCellListener.onCellSelected();
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ public void stopBlink() {
+ boolean hadSelection = mSelectedCell != -1 && mSelectedValue != State.EMPTY;
+ mSelectedCell = -1;
+ mSelectedValue = State.EMPTY;
+ if (!mBlinkRect.isEmpty()) {
+ invalidate(mBlinkRect);
+ }
+ mBlinkDisplayOff = false;
+ mBlinkRect.setEmpty();
+ mHandler.removeMessages(MSG_BLINK);
+ if (hadSelection && mCellListener != null) {
+ mCellListener.onCellSelected();
+ }
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ Bundle b = new Bundle();
+
+ Parcelable s = super.onSaveInstanceState();
+ b.putParcelable("gv_super_state", s);
+
+ b.putBoolean("gv_en", isEnabled());
+
+ int[] data = new int[mData.length];
+ for (int i = 0; i < data.length; i++) {
+ data[i] = mData[i].getValue();
+ }
+ b.putIntArray("gv_data", data);
+
+ b.putInt("gv_sel_cell", mSelectedCell);
+ b.putInt("gv_sel_val", mSelectedValue.getValue());
+ b.putInt("gv_curr_play", mCurrentPlayer.getValue());
+ b.putInt("gv_winner", mWinner.getValue());
+
+ b.putInt("gv_win_col", mWinCol);
+ b.putInt("gv_win_row", mWinRow);
+ b.putInt("gv_win_diag", mWinDiag);
+
+ b.putBoolean("gv_blink_off", mBlinkDisplayOff);
+ b.putParcelable("gv_blink_rect", mBlinkRect);
+
+ return b;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+
+ if (!(state instanceof Bundle)) {
+ // Not supposed to happen.
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
+ Bundle b = (Bundle) state;
+ Parcelable superState = b.getParcelable("gv_super_state");
+
+ setEnabled(b.getBoolean("gv_en", true));
+
+ int[] data = b.getIntArray("gv_data");
+ if (data != null && data.length == mData.length) {
+ for (int i = 0; i < data.length; i++) {
+ mData[i] = State.fromInt(data[i]);
+ }
+ }
+
+ mSelectedCell = b.getInt("gv_sel_cell", -1);
+ mSelectedValue = State.fromInt(b.getInt("gv_sel_val", State.EMPTY.getValue()));
+ mCurrentPlayer = State.fromInt(b.getInt("gv_curr_play", State.EMPTY.getValue()));
+ mWinner = State.fromInt(b.getInt("gv_winner", State.EMPTY.getValue()));
+
+ mWinCol = b.getInt("gv_win_col", -1);
+ mWinRow = b.getInt("gv_win_row", -1);
+ mWinDiag = b.getInt("gv_win_diag", -1);
+
+ mBlinkDisplayOff = b.getBoolean("gv_blink_off", false);
+ Rect r = b.getParcelable("gv_blink_rect");
+ if (r != null) {
+ mBlinkRect.set(r);
+ }
+
+ // let the blink handler decide if it should blink or not
+ mHandler.sendEmptyMessage(MSG_BLINK);
+
+ super.onRestoreInstanceState(superState);
+ }
+
+ //-----
+
+ private class MyHandler implements Callback {
+ public boolean handleMessage(Message msg) {
+ if (msg.what == MSG_BLINK) {
+ if (mSelectedCell >= 0 && mSelectedValue != State.EMPTY && mBlinkRect.top != 0) {
+ mBlinkDisplayOff = !mBlinkDisplayOff;
+ invalidate(mBlinkRect);
+
+ if (!mHandler.hasMessages(MSG_BLINK)) {
+ mHandler.sendEmptyMessageDelayed(MSG_BLINK, FPS_MS);
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+ }
+
+ private Bitmap getResBitmap(int bmpResId) {
+ Options opts = new Options();
+ opts.inDither = false;
+
+ Resources res = getResources();
+ Bitmap bmp = BitmapFactory.decodeResource(res, bmpResId, opts);
+
+ if (bmp == null && isInEditMode()) {
+ // BitmapFactory.decodeResource doesn't work from the rendering
+ // library in Eclipse's Graphical Layout Editor. Use this workaround instead.
+
+ Drawable d = res.getDrawable(bmpResId);
+ int w = d.getIntrinsicWidth();
+ int h = d.getIntrinsicHeight();
+ bmp = Bitmap.createBitmap(w, h, Config.ARGB_8888);
+ Canvas c = new Canvas(bmp);
+ d.setBounds(0, 0, w - 1, h - 1);
+ d.draw(c);
+ }
+
+ return bmp;
+ }
+}
+
+
diff --git a/build-system/tests/tictactoe/lib/src/main/res/drawable/lib_bg.9.png b/build-system/tests/tictactoe/lib/src/main/res/drawable/lib_bg.9.png
new file mode 100755
index 0000000..38c06c0
--- /dev/null
+++ b/build-system/tests/tictactoe/lib/src/main/res/drawable/lib_bg.9.png
Binary files differ
diff --git a/build-system/tests/tictactoe/lib/src/main/res/drawable/lib_circle.png b/build-system/tests/tictactoe/lib/src/main/res/drawable/lib_circle.png
new file mode 100755
index 0000000..55adffe
--- /dev/null
+++ b/build-system/tests/tictactoe/lib/src/main/res/drawable/lib_circle.png
Binary files differ
diff --git a/build-system/tests/tictactoe/lib/src/main/res/drawable/lib_cross.png b/build-system/tests/tictactoe/lib/src/main/res/drawable/lib_cross.png
new file mode 100755
index 0000000..9189ebb
--- /dev/null
+++ b/build-system/tests/tictactoe/lib/src/main/res/drawable/lib_cross.png
Binary files differ
diff --git a/build-system/tests/tictactoe/lib/src/main/res/layout-land/lib_game.xml b/build-system/tests/tictactoe/lib/src/main/res/layout-land/lib_game.xml
new file mode 100755
index 0000000..9777e02
--- /dev/null
+++ b/build-system/tests/tictactoe/lib/src/main/res/layout-land/lib_game.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:gravity="center_vertical|center_horizontal"
+ >
+
+ <com.example.android.tictactoe.library.GameView
+ android:id="@+id/game_view"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_margin="20dip"
+ />
+
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ >
+
+ <TextView
+ android:id="@+id/info_turn"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:layout_marginBottom="10dip"
+ />
+
+ <Button
+ android:id="@+id/next_turn"
+ android:text="I'm done"
+ android:minEms="10"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="20dip"
+ android:layout_marginRight="20dip"
+ />
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/build-system/tests/tictactoe/lib/src/main/res/layout/lib_game.xml b/build-system/tests/tictactoe/lib/src/main/res/layout/lib_game.xml
new file mode 100755
index 0000000..6735983
--- /dev/null
+++ b/build-system/tests/tictactoe/lib/src/main/res/layout/lib_game.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:gravity="center_horizontal"
+ >
+
+ <com.example.android.tictactoe.library.GameView
+ android:id="@+id/game_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="20dip"
+ android:layout_weight="1"
+ />
+
+ <TextView
+ android:id="@+id/info_turn"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:layout_marginBottom="10dip"
+ />
+
+ <Button
+ android:id="@+id/next_turn"
+ android:text="I'm done"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="20dip"
+ android:layout_marginRight="20dip"
+ />
+
+</LinearLayout>
diff --git a/build-system/tests/tictactoe/lib/src/main/res/values/strings.xml b/build-system/tests/tictactoe/lib/src/main/res/values/strings.xml
new file mode 100755
index 0000000..468975a
--- /dev/null
+++ b/build-system/tests/tictactoe/lib/src/main/res/values/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<resources>
+ <string name="player2_win">Player 2 (computer) wins!</string>
+ <string name="player1_win">Player 1 (you) wins!</string>
+ <string name="tie">This is a tie! No one wins!</string>
+ <string name="player2_turn">Player 2\'s turn (that\'s the computer)</string>
+ <string name="player1_turn">Player 1\'s turn -- that\'s you!</string>
+</resources>
diff --git a/build-system/tests/tictactoe/settings.gradle b/build-system/tests/tictactoe/settings.gradle
new file mode 100644
index 0000000..5ed7972
--- /dev/null
+++ b/build-system/tests/tictactoe/settings.gradle
@@ -0,0 +1,2 @@
+include 'app'
+include 'lib'
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 94a8527..57c79cc 100644
--- a/build.gradle
+++ b/build.gradle
@@ -15,6 +15,7 @@
mainRepo = "$rootDir/../../prebuilts/tools/common/m2/repository"
secondaryRepo = "$rootDir/../../prebuilts/tools/common/m2/internal"
}
+
// set up the distribution destination
distribution {
destinationPath = "$rootDir/../../prebuilts/devtools"
@@ -23,27 +24,18 @@
// ext.androidHostOut is shared by all tools/{base,build,swt} gradle projects/
ext.androidHostOut = file("$rootDir/../../out/host/gradle")
-ext.androidRootDir = file(new File(ext.androidHostOut, "../../../"))
// rootProject.buildDir is specific to this gradle build.
buildDir = new File(file(ext.androidHostOut), "tools/base/build")
-def getVersion(Project p, String baseVersion) {
- if (p.has("release")) {
- return baseVersion
- }
+ext.localRepo = project.hasProperty('localRepo') ? localRepo : "$ext.androidHostOut/repo"
- return baseVersion + '-SNAPSHOT'
-}
+project.ext.baseVersion = '22.4.0'
+project.ext.buildVersion = '0.7.0'
subprojects { Project project ->
// Change buildDir first so that all plugins pick up the new value.
project.buildDir = project.file("$project.parent.buildDir/../$project.name")
- apply plugin: 'java'
- apply plugin: 'maven'
- apply plugin: 'signing'
- apply plugin: 'findbugs'
- apply plugin: 'distrib'
apply plugin: 'clone-artifacts'
repositories {
@@ -51,79 +43,7 @@
maven { url = uri(rootProject.cloneArtifacts.secondaryRepo) }
}
- // find bug dependencies is added dynamically so it's hard for the
- // clone artifact plugin to find it. This custom config lets us manually
- // add such dependencies.
- configurations {
- hidden
- }
- dependencies {
- hidden "com.google.code.findbugs:findbugs:2.0.1"
- }
-
- version = getVersion(project, '22.2.0')
-
- project.ext.sonatypeUsername = project.hasProperty('sonatypeUsername') ? sonatypeUsername : ""
- project.ext.sonatypePassword = project.hasProperty('sonatypePassword') ? sonatypePassword : ""
-
- // set all java compilation to use UTF-8 encoding.
- tasks.withType(JavaCompile) {
- options.encoding = 'UTF-8'
- }
-
- task disableTestFailures << {
- tasks.withType(Test) {
- ignoreFailures = true
- }
- }
-
- // custom tasks for creating source/javadoc jars
- task sourcesJar(type: Jar, dependsOn:classes) {
- classifier = 'sources'
- from sourceSets.main.allSource
- }
-
- task javadocJar(type: Jar, dependsOn:javadoc) {
- classifier = 'javadoc'
- from javadoc.destinationDir
- }
-
- // add javadoc/source jar tasks as artifacts
- artifacts {
- archives jar
-
- archives sourcesJar
- archives javadocJar
- }
-
- task publishLocal(type: Upload) {
- configuration = configurations.archives
- repositories {
- mavenDeployer {
- repository(url: uri("$rootProject.ext.androidHostOut/repo"))
- }
- }
- }
-
- def userHome = System.getProperty("user.home")
- publishLocal.doFirst {
- System.setProperty("user.home", file("$buildDir/fakem2home").absolutePath)
- }
- publishLocal.doLast {
- System.setProperty("user.home", userHome)
- }
-
- findbugs {
- ignoreFailures = true
- effort = "max"
- reportLevel = "high"
- }
-
- signing {
- required { project.has("release") && gradle.taskGraph.hasTask("uploadArchives") }
- sign configurations.archives
- }
-
+ apply from: "$project.rootDir/base.gradle"
}
// delay evaluation of this project before all subprojects have been evaluated.
@@ -135,3 +55,14 @@
from { testTasks*.testResultsDir }
into { file("$buildDir/results") }
}
+
+task prepareRepo(type: Copy) {
+ from { rootProject.cloneArtifacts.mainRepo }
+ from { rootProject.cloneArtifacts.secondaryRepo }
+ into { "$rootProject.ext.androidHostOut/repo" }
+}
+
+task copyGradleProperty(type: Copy) {
+ from { "${System.env.HOME}/.gradle/gradle.properties" }
+ into { gradle.gradleUserHomeDir }
+}
diff --git a/buildVersion.gradle b/buildVersion.gradle
new file mode 100644
index 0000000..ae0918f
--- /dev/null
+++ b/buildVersion.gradle
@@ -0,0 +1,9 @@
+def getVersion() {
+ if (project.has("release")) {
+ return rootProject.ext.buildVersion
+ }
+
+ return rootProject.ext.buildVersion + '-SNAPSHOT'
+}
+
+version = getVersion()
diff --git a/common/.classpath b/common/.classpath
index 39a3f56..c98c4c7 100644
--- a/common/.classpath
+++ b/common/.classpath
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src/main/java"/>
+ <classpathentry kind="src" path="src/test/java"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/>
<classpathentry exported="true" kind="var" path="ANDROID_SRC/prebuilts/tools/common/m2/repository/com/google/guava/guava/13.0.1/guava-13.0.1.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/m2/repository/com/google/guava/guava/13.0.1/guava-13.0.1-sources.jar"/>
diff --git a/common/build.gradle b/common/build.gradle
index ecaeb42..11b520d 100644
--- a/common/build.gradle
+++ b/common/build.gradle
@@ -1,3 +1,6 @@
+apply plugin: 'java'
+apply plugin: 'distrib'
+
dependencies {
compile 'com.google.guava:guava:13.0.1'
@@ -6,50 +9,13 @@
group = 'com.android.tools'
archivesBaseName = 'common'
+project.ext.pomName = 'Android Tools common library'
+project.ext.pomDesc = 'common library used by other Android tools libraries.'
jar {
from 'NOTICE'
}
-uploadArchives {
- repositories {
- mavenDeployer {
- beforeDeployment { MavenDeployment deployment ->
- if (!project.has("release")) {
- throw new StopExecutionException("uploadArchives must be called with the release.gradle init script")
- }
-
- signing.signPom(deployment)
- }
-
- repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
- authentication(userName: project.ext.sonatypeUsername, password: project.ext.sonatypePassword)
- }
-
- pom.project {
- name 'Android Tools common library'
- description 'common library used by other Android tools libraries.'
- url 'http://tools.android.com'
- inceptionYear '2007'
-
- licenses {
- license {
- name 'The Apache Software License, Version 2.0'
- url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
- distribution 'repo'
- }
- }
-
- scm {
- url "https://android.googlesource.com/platform/tools/base"
- connection "git://android.googlesource.com/platform/tools/base.git"
- }
- developers {
- developer {
- name 'The Android Open Source Project'
- }
- }
- }
- }
- }
-}
+apply from: '../baseVersion.gradle'
+apply from: '../publish.gradle'
+apply from: '../javadoc.gradle'
diff --git a/common/src/main/java/com/android/SdkConstants.java b/common/src/main/java/com/android/SdkConstants.java
index 6c50340..37510ce 100644
--- a/common/src/main/java/com/android/SdkConstants.java
+++ b/common/src/main/java/com/android/SdkConstants.java
@@ -47,6 +47,12 @@
*/
public static final int CURRENT_PLATFORM = currentPlatform();
+ /** Environment variable that specifies the path of an Android SDK. */
+ public static final String ANDROID_HOME_ENV = "ANDROID_HOME";
+
+ /** Property in local.properties file that specifies the path of the Android SDK. */
+ public static final String SDK_DIR_PROPERTY = "sdk.dir";
+
/**
* Charset for the ini file handled by the SDK.
*/
@@ -147,6 +153,18 @@
public static final String FN_BCC_COMPAT =
"bcc_compat" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ /** renderscript support linker for ARM (with extension for the current OS) */
+ public static final String FN_LD_ARM =
+ "arm-linux-androideabi-ld" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+
+ /** renderscript support linker for X86 (with extension for the current OS) */
+ public static final String FN_LD_X86 =
+ "i686-linux-android-ld" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+
+ /** renderscript support linker for MIPS (with extension for the current OS) */
+ public static final String FN_LD_MIPS =
+ "mipsel-linux-android-ld" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+
/** adb executable (with extension for the current OS) */
public static final String FN_ADB =
"adb" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
@@ -178,10 +196,14 @@
/** properties file for the SDK */
public static final String FN_SDK_PROP = "sdk.properties"; //$NON-NLS-1$
+
+ public static final String FN_RENDERSCRIPT_V8_JAR = "renderscript-v8.jar"; //$NON-NLS-1$
+
/**
* filename for gdbserver.
*/
public static final String FN_GDBSERVER = "gdbserver"; //$NON-NLS-1$
+ public static final String FN_GDB_SETUP = "gdb.setup"; //$NON-NLS-1$
/** global Android proguard config file */
public static final String FN_ANDROID_PROGUARD_FILE = "proguard-android.txt"; //$NON-NLS-1$
@@ -225,6 +247,11 @@
/** aidl output folder for copied aidl files */
public static final String FD_AIDL = "aidl"; //$NON-NLS-1$
+ /** rs Libs output folder for support mode */
+ public static final String FD_RS_LIBS = "rsLibs"; //$NON-NLS-1$
+ /** rs Libs output folder for support mode */
+ public static final String FD_RS_OBJ = "rsObj"; //$NON-NLS-1$
+
/* Folder Names for the Android SDK */
/** Name of the SDK platforms folder. */
@@ -293,7 +320,7 @@
public static final String FD_RES = "res"; //$NON-NLS-1$
/** Name of the SDK font folder, i.e. "fonts" */
public static final String FD_FONTS = "fonts"; //$NON-NLS-1$
- /** Name of the android sources directory */
+ /** Name of the android sources directory and the root of the SDK sources package folder. */
public static final String FD_ANDROID_SOURCES = "sources"; //$NON-NLS-1$
/** Name of the addon libs folder. */
public static final String FD_ADDON_LIBS = "libs"; //$NON-NLS-1$
@@ -669,6 +696,7 @@
public static final String TAG_FLAG = "flag"; //$NON-NLS-1$
public static final String TAG_ATTR = "attr"; //$NON-NLS-1$
public static final String TAG_DECLARE_STYLEABLE = "declare-styleable"; //$NON-NLS-1$
+ public static final String TAG_EAT_COMMENT = "eat-comment"; //$NON-NLS-1$
// Tags: XML
public static final String TAG_HEADER = "header"; //$NON-NLS-1$
@@ -752,6 +780,7 @@
public static final String ATTR_PARENT = "parent"; //$NON-NLS-1$
public static final String ATTR_TRANSLATABLE = "translatable"; //$NON-NLS-1$
public static final String ATTR_COLOR = "color"; //$NON-NLS-1$
+ public static final String ATTR_DRAWABLE = "drawable"; //$NON-NLS-1$
public static final String ATTR_VALUE = "value"; //$NON-NLS-1$
public static final String ATTR_QUANTITY = "quantity"; //$NON-NLS-1$
public static final String ATTR_FORMAT = "format"; //$NON-NLS-1$
@@ -929,6 +958,7 @@
public static final String DOT_JAVA = ".java"; //$NON-NLS-1$
public static final String DOT_CLASS = ".class"; //$NON-NLS-1$
public static final String DOT_JAR = ".jar"; //$NON-NLS-1$
+ public static final String DOT_GRADLE = ".gradle"; //$NON-NLS-1$
/** Extension of the Application package Files, i.e. "apk". */
@@ -939,14 +969,20 @@
public static final String EXT_CLASS = "class"; //$NON-NLS-1$
/** Extension of xml files, i.e. "xml" */
public static final String EXT_XML = "xml"; //$NON-NLS-1$
+ /** Extension of gradle files, i.e. "gradle" */
+ public static final String EXT_GRADLE = "gradle"; //$NON-NLS-1$
/** Extension of jar files, i.e. "jar" */
public static final String EXT_JAR = "jar"; //$NON-NLS-1$
/** Extension of aidl files, i.e. "aidl" */
public static final String EXT_AIDL = "aidl"; //$NON-NLS-1$
/** Extension of Renderscript files, i.e. "rs" */
public static final String EXT_RS = "rs"; //$NON-NLS-1$
+ /** Extension of Renderscript files, i.e. "rsh" */
+ public static final String EXT_RSH = "rsh"; //$NON-NLS-1$
/** Extension of FilterScript files, i.e. "fs" */
public static final String EXT_FS = "fs"; //$NON-NLS-1$
+ /** Extension of Renderscript bitcode files, i.e. "bc" */
+ public static final String EXT_BC = "bc"; //$NON-NLS-1$
/** Extension of dependency files, i.e. "d" */
public static final String EXT_DEP = "d"; //$NON-NLS-1$
/** Extension of native libraries, i.e. "so" */
@@ -968,8 +1004,12 @@
public static final String DOT_AIDL = DOT + EXT_AIDL;
/** Dot-Extension of renderscript files, i.e. ".rs" */
public static final String DOT_RS = DOT + EXT_RS;
+ /** Dot-Extension of renderscript header files, i.e. ".rsh" */
+ public static final String DOT_RSH = DOT + EXT_RSH;
/** Dot-Extension of FilterScript files, i.e. ".fs" */
public static final String DOT_FS = DOT + EXT_FS;
+ /** Dot-Extension of renderscript bitcode files, i.e. ".bc" */
+ public static final String DOT_BC = DOT + EXT_BC;
/** Dot-Extension of dependency files, i.e. ".d" */
public static final String DOT_DEP = DOT + EXT_DEP;
/** Dot-Extension of dex files, i.e. ".dex" */
diff --git a/common/src/main/java/com/android/utils/HtmlBuilder.java b/common/src/main/java/com/android/utils/HtmlBuilder.java
new file mode 100644
index 0000000..f2e0d20
--- /dev/null
+++ b/common/src/main/java/com/android/utils/HtmlBuilder.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.utils;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.net.URL;
+
+public class HtmlBuilder {
+ @NonNull private final StringBuilder mStringBuilder;
+ private String mTableDataExtra;
+
+ public HtmlBuilder(@NonNull StringBuilder stringBuilder) {
+ mStringBuilder = stringBuilder;
+ }
+
+ public HtmlBuilder() {
+ mStringBuilder = new StringBuilder(100);
+ }
+
+ public HtmlBuilder openHtmlBody() {
+ addHtml("<html><body>");
+
+ return this;
+ }
+
+ public HtmlBuilder closeHtmlBody() {
+ addHtml("</body></html>");
+
+ return this;
+ }
+
+ public HtmlBuilder addHtml(@NonNull String html) {
+ mStringBuilder.append(html);
+
+ return this;
+ }
+
+ public HtmlBuilder addNbsp() {
+ mStringBuilder.append(" ");
+
+ return this;
+ }
+
+ public HtmlBuilder addNbsps(int count) {
+ for (int i = 0; i < count; i++) {
+ addNbsp();
+ }
+
+ return this;
+ }
+
+ public HtmlBuilder newline() {
+ mStringBuilder.append("<BR/>\n");
+
+ return this;
+ }
+
+ public HtmlBuilder newlineIfNecessary() {
+ if (!SdkUtils.endsWith(mStringBuilder, "<BR/>\n")) {
+ mStringBuilder.append("<BR/>\n");
+ }
+
+ return this;
+ }
+
+ public HtmlBuilder addLink(@Nullable String textBefore,
+ @NonNull String linkText,
+ @Nullable String textAfter,
+ @NonNull String url) {
+ if (textBefore != null) {
+ add(textBefore);
+ }
+
+ addLink(linkText, url);
+
+ if (textAfter != null) {
+ add(textAfter);
+ }
+
+ return this;
+ }
+
+ public HtmlBuilder addLink(@NonNull String text, @NonNull String url) {
+ int begin = 0;
+ int length = text.length();
+ for (; begin < length; begin++) {
+ char c = text.charAt(begin);
+ if (Character.isWhitespace(c)) {
+ mStringBuilder.append(c);
+ } else {
+ break;
+ }
+ }
+ mStringBuilder.append("<A HREF=\"");
+ mStringBuilder.append(url);
+ mStringBuilder.append("\">");
+
+ XmlUtils.appendXmlTextValue(mStringBuilder, text.trim());
+ mStringBuilder.append("</A>");
+
+ int end = length - 1;
+ for (; end > begin; end--) {
+ char c = text.charAt(begin);
+ if (Character.isWhitespace(c)) {
+ mStringBuilder.append(c);
+ }
+ }
+
+ return this;
+ }
+
+ public HtmlBuilder add(@NonNull String text) {
+ XmlUtils.appendXmlTextValue(mStringBuilder, text);
+
+ return this;
+ }
+
+ @NonNull
+ public String getHtml() {
+ return mStringBuilder.toString();
+ }
+
+ public HtmlBuilder beginBold() {
+ mStringBuilder.append("<B>");
+
+ return this;
+ }
+
+ public HtmlBuilder endBold() {
+ mStringBuilder.append("</B>");
+
+ return this;
+ }
+
+ public HtmlBuilder addBold(String text) {
+ beginBold();
+ add(text);
+ endBold();
+
+ return this;
+ }
+
+ public HtmlBuilder beginDiv() {
+ return beginDiv(null);
+ }
+
+ public HtmlBuilder beginDiv(@Nullable String cssStyle) {
+ mStringBuilder.append("<div");
+ if (cssStyle != null) {
+ mStringBuilder.append(" style=\"");
+ mStringBuilder.append(cssStyle);
+ mStringBuilder.append("\"");
+ }
+ mStringBuilder.append('>');
+ return this;
+ }
+
+ public HtmlBuilder endDiv() {
+ mStringBuilder.append("</div>");
+ return this;
+ }
+
+ public HtmlBuilder addHeading(@NonNull String text, @NonNull String fontColor) {
+ mStringBuilder.append("<font style=\"font-weight:bold; color:").append(fontColor)
+ .append(";\">");
+ add(text);
+ mStringBuilder.append("</font>");
+
+ return this;
+ }
+
+ /**
+ * The JEditorPane HTML renderer creates really ugly bulleted lists; the
+ * size is hardcoded to use a giant heavy bullet. So, use a definition
+ * list instead.
+ */
+ private static final boolean USE_DD_LISTS = true;
+
+ public HtmlBuilder beginList() {
+ if (USE_DD_LISTS) {
+ mStringBuilder.append("<DL>");
+ } else {
+ mStringBuilder.append("<UL>");
+ }
+
+ return this;
+ }
+
+ public HtmlBuilder endList() {
+ if (USE_DD_LISTS) {
+ mStringBuilder.append("\n</DL>");
+ } else {
+ mStringBuilder.append("\n</UL>");
+ }
+
+ return this;
+ }
+
+ public HtmlBuilder listItem() {
+ if (USE_DD_LISTS) {
+ mStringBuilder.append("\n<DD>");
+ mStringBuilder.append("-&NBSP;");
+ } else {
+ mStringBuilder.append("\n<LI>");
+ }
+
+ return this;
+ }
+
+ public HtmlBuilder addImage(URL url, @Nullable String altText) {
+ String link = "";
+ try {
+ link = url.toURI().toURL().toExternalForm();
+ }
+ catch (Throwable t) {
+ // pass
+ }
+ mStringBuilder.append("<img src='");
+ mStringBuilder.append(link);
+
+ if (altText != null) {
+ mStringBuilder.append("' alt=\"");
+ mStringBuilder.append(altText);
+ mStringBuilder.append("\"");
+ }
+ mStringBuilder.append(" />");
+
+ return this;
+ }
+
+ public HtmlBuilder addIcon(@Nullable String src) {
+ if (src != null) {
+ mStringBuilder.append("<img src='");
+ mStringBuilder.append(src);
+ mStringBuilder.append("' width=16 height=16 border=0 />");
+ }
+
+ return this;
+ }
+
+ public HtmlBuilder beginTable(@Nullable String tdExtra) {
+ mStringBuilder.append("<table>");
+ mTableDataExtra = tdExtra;
+ return this;
+ }
+
+ public HtmlBuilder beginTable() {
+ return beginTable(null);
+ }
+
+ public HtmlBuilder endTable() {
+ mStringBuilder.append("</table>");
+ return this;
+ }
+
+ public HtmlBuilder beginTableRow() {
+ mStringBuilder.append("<tr>");
+ return this;
+ }
+
+ public HtmlBuilder endTableRow() {
+ mStringBuilder.append("</tr>");
+ return this;
+ }
+
+ public HtmlBuilder addTableRow(boolean isHeader, String... columns) {
+ if (columns == null || columns.length == 0) {
+ return this;
+ }
+
+ String tag = "t" + (isHeader ? 'h' : 'd');
+
+ beginTableRow();
+ for (String c : columns) {
+ mStringBuilder.append('<');
+ mStringBuilder.append(tag);
+ if (mTableDataExtra != null) {
+ mStringBuilder.append(' ');
+ mStringBuilder.append(mTableDataExtra);
+ }
+ mStringBuilder.append('>');
+
+ mStringBuilder.append(c);
+
+ mStringBuilder.append("</");
+ mStringBuilder.append(tag);
+ mStringBuilder.append('>');
+ }
+ endTableRow();
+
+ return this;
+ }
+
+ public HtmlBuilder addTableRow(String... columns) {
+ return addTableRow(false, columns);
+ }
+
+ @NonNull
+ public StringBuilder getStringBuilder() {
+ return mStringBuilder;
+ }
+}
diff --git a/common/src/main/java/com/android/utils/SdkUtils.java b/common/src/main/java/com/android/utils/SdkUtils.java
index d610527..b12caac 100644
--- a/common/src/main/java/com/android/utils/SdkUtils.java
+++ b/common/src/main/java/com/android/utils/SdkUtils.java
@@ -16,9 +16,24 @@
package com.android.utils;
+import static com.android.SdkConstants.DOT_XML;
+
+import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.google.common.base.Charsets;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Closeables;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
import java.text.NumberFormat;
import java.text.ParseException;
@@ -32,7 +47,7 @@
* @param suffix the suffix to be checked for
* @return true if the string case-insensitively ends with the given suffix
*/
- public static boolean endsWithIgnoreCase(String string, String suffix) {
+ public static boolean endsWithIgnoreCase(@NonNull String string, @NonNull String suffix) {
return string.regionMatches(true /* ignoreCase */, string.length() - suffix.length(),
suffix, 0, suffix.length());
}
@@ -45,7 +60,7 @@
* @param suffix the suffix to look for
* @return true if the given sequence ends with the given suffix
*/
- public static boolean endsWith(CharSequence sequence, CharSequence suffix) {
+ public static boolean endsWith(@NonNull CharSequence sequence, @NonNull CharSequence suffix) {
return endsWith(sequence, sequence.length(), suffix);
}
@@ -58,7 +73,8 @@
* @param suffix the suffix to look for
* @return true if the given sequence ends with the given suffix
*/
- public static boolean endsWith(CharSequence sequence, int endOffset, CharSequence suffix) {
+ public static boolean endsWith(@NonNull CharSequence sequence, int endOffset,
+ @NonNull CharSequence suffix) {
if (endOffset < suffix.length()) {
return false;
}
@@ -80,7 +96,7 @@
* @param prefix the prefix to be checked for
* @return true if the string case-insensitively starts with the given prefix
*/
- public static boolean startsWithIgnoreCase(String string, String prefix) {
+ public static boolean startsWithIgnoreCase(@NonNull String string, @NonNull String prefix) {
return string.regionMatches(true /* ignoreCase */, 0, prefix, 0, prefix.length());
}
@@ -94,7 +110,7 @@
* @return true if the string case-insensitively starts at the given offset
* with the given prefix
*/
- public static boolean startsWith(String string, int offset, String prefix) {
+ public static boolean startsWith(@NonNull String string, int offset, @NonNull String prefix) {
return string.regionMatches(true /* ignoreCase */, offset, prefix, 0, prefix.length());
}
@@ -104,7 +120,7 @@
* @param string the string to be cleaned up
* @return the string, without whitespace
*/
- public static String stripWhitespace(String string) {
+ public static String stripWhitespace(@NonNull String string) {
StringBuilder sb = new StringBuilder(string.length());
for (int i = 0, n = string.length(); i < n; i++) {
char c = string.charAt(i);
@@ -122,7 +138,7 @@
* @param s the string to check
* @return true if it contains uppercase characters
*/
- public static boolean hasUpperCaseCharacter(String s) {
+ public static boolean hasUpperCaseCharacter(@NonNull String s) {
for (int i = 0; i < s.length(); i++) {
if (Character.isUpperCase(s.charAt(i))) {
return true;
@@ -289,4 +305,122 @@
return defaultValue;
}
}
+
+ /**
+ * Returns the corresponding {@link File} for the given file:// url
+ *
+ * @param url the URL string, e.g. file://foo/bar
+ * @return the corresponding {@link File} (which may or may not exist)
+ * @throws MalformedURLException if the URL string is malformed or is not a file: URL
+ */
+ @NonNull
+ public static File urlToFile(@NonNull String url) throws MalformedURLException {
+ return urlToFile(new URL(url));
+ }
+
+ @NonNull
+ public static File urlToFile(@NonNull URL url) throws MalformedURLException {
+ try {
+ return new File(url.toURI());
+ }
+ catch (IllegalArgumentException e) {
+ MalformedURLException ex = new MalformedURLException(e.getLocalizedMessage());
+ ex.initCause(e);
+ throw ex;
+ }
+ catch (URISyntaxException e) {
+ return new File(url.getPath());
+ }
+ }
+
+ /**
+ * Returns the corresponding URL string for the given {@link File}
+ *
+ * @param file the file to look up the URL for
+ * @return the corresponding URL
+ * @throws MalformedURLException in very unexpected cases
+ */
+ public static String fileToUrlString(@NonNull File file) throws MalformedURLException {
+ return fileToUrl(file).toExternalForm();
+ }
+
+ /**
+ * Returns the corresponding URL for the given {@link File}
+ *
+ * @param file the file to look up the URL for
+ * @return the corresponding URL
+ * @throws MalformedURLException in very unexpected cases
+ */
+ public static URL fileToUrl(@NonNull File file) throws MalformedURLException {
+ return file.toURI().toURL();
+ }
+
+ /** Prefix in comments which mark the source locations for merge results */
+ public static final String FILENAME_PREFIX = "From: ";
+
+ /**
+ * Creates the path comment XML string. Note that it does not escape characters
+ * such as & and <; those are expected to be escaped by the caller (for
+ * example, handled by a call to {@link org.w3c.dom.Document#createComment(String)})
+ *
+ *
+ * @param file the file to create a path comment for
+ * @param includePadding whether to include padding. The final comment recognized by
+ * error recognizers expect padding between the {@code <!--} and
+ * the start marker (From:); you can disable padding if the caller
+ * already is in a context where the padding has been added.
+ * @return the corresponding XML contents of the string
+ */
+ public static String createPathComment(@NonNull File file, boolean includePadding)
+ throws MalformedURLException {
+ String url = fileToUrlString(file);
+ int dashes = url.indexOf("--");
+ if (dashes != -1) { // Not allowed inside XML comments - for SGML compatibility. Sigh.
+ url = url.replace("--", "%2D%2D");
+ }
+
+ if (includePadding) {
+ return ' ' + FILENAME_PREFIX + url + ' ';
+ } else {
+ return FILENAME_PREFIX + url;
+ }
+ }
+
+ /**
+ * Copies the given XML file to the given new path. It also inserts a comment at
+ * the end of the file which points to the original source location. This is intended
+ * for use with error parsers which can rewrite for example AAPT error messages in
+ * say layout or manifest files, which occur in the merged (copied) output, and present
+ * it as an error pointing to one of the user's original source files.
+ */
+ public static void copyXmlWithSourceReference(@NonNull File from, @NonNull File to)
+ throws IOException {
+ copyXmlWithComment(from, to, createPathComment(from, true));
+ }
+
+ /** Copies a given XML file, and appends a given comment to the end */
+ private static void copyXmlWithComment(@NonNull File from, @NonNull File to,
+ @Nullable String comment) throws IOException {
+ assert endsWithIgnoreCase(from.getPath(), DOT_XML) : from;
+
+ int successfulOps = 0;
+ InputStream in = new FileInputStream(from);
+ try {
+ FileOutputStream out = new FileOutputStream(to, false);
+ try {
+ ByteStreams.copy(in, out);
+ successfulOps++;
+ if (comment != null) {
+ String commentText = "<!--" + XmlUtils.toXmlTextValue(comment) + "-->";
+ byte[] suffix = commentText.getBytes(Charsets.UTF_8);
+ out.write(suffix);
+ }
+ } finally {
+ Closeables.close(out, successfulOps < 1);
+ successfulOps++;
+ }
+ } finally {
+ Closeables.close(in, successfulOps < 2);
+ }
+ }
}
diff --git a/common/src/main/java/com/android/utils/XmlUtils.java b/common/src/main/java/com/android/utils/XmlUtils.java
index 999375f..bd99287 100644
--- a/common/src/main/java/com/android/utils/XmlUtils.java
+++ b/common/src/main/java/com/android/utils/XmlUtils.java
@@ -359,15 +359,12 @@
break;
}
case Node.COMMENT_NODE:
+ sb.append(XML_COMMENT_BEGIN);
+ sb.append(node.getNodeValue());
+ sb.append(XML_COMMENT_END);
+ break;
case Node.TEXT_NODE: {
- if (nodeType == Node.COMMENT_NODE) {
- sb.append(XML_COMMENT_BEGIN);
- }
- String text = node.getNodeValue();
- sb.append(toXmlTextValue(text));
- if (nodeType == Node.COMMENT_NODE) {
- sb.append(XML_COMMENT_END);
- }
+ sb.append(toXmlTextValue(node.getNodeValue()));
break;
}
case Node.ELEMENT_NODE: {
diff --git a/common/src/test/java/com/android/utils/HtmlBuilderTest.java b/common/src/test/java/com/android/utils/HtmlBuilderTest.java
new file mode 100644
index 0000000..ab80bdd
--- /dev/null
+++ b/common/src/test/java/com/android/utils/HtmlBuilderTest.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.utils;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.IOException;
+
+public class HtmlBuilderTest extends TestCase {
+ public void test1() {
+ HtmlBuilder builder = new HtmlBuilder();
+ builder.add("Plain.");
+ builder.addLink(" (link) ", "runnable:0");
+ builder.add("Plain.");
+ // Check that the spaces surrounding the link text are not included in the link range
+ assertEquals("Plain. <A HREF=\"runnable:0\">(link)</A>Plain.", builder.getHtml());
+ }
+
+ public void test2() {
+ HtmlBuilder builder = new HtmlBuilder();
+ builder.add("Plain").newline().addLink("mylink", "runnable:0").newline();
+ builder.beginList().listItem().add("item 1").listItem().add("item 2").endList();
+
+ assertEquals("Plain<BR/>\n" +
+ "<A HREF=\"runnable:0\">mylink</A><BR/>\n" +
+ "<DL>\n" +
+ "<DD>-&NBSP;item 1\n" +
+ "<DD>-&NBSP;item 2\n" +
+ "</DL>", builder.getHtml());
+ }
+
+ public void test3() {
+ HtmlBuilder builder1 = new HtmlBuilder();
+ builder1.addBold("This is bold");
+ assertEquals("<B>This is bold</B>", builder1.getHtml());
+
+ HtmlBuilder builder2 = new HtmlBuilder();
+ builder2.add("Plain. ");
+ builder2.beginBold();
+ builder2.add("Bold. ");
+ builder2.addLink("mylink", "runnable:0");
+ builder2.endBold();
+ assertEquals("Plain. <B>Bold. <A HREF=\"runnable:0\">mylink</A></B>", builder2.getHtml());
+ }
+
+ public void test4() {
+ HtmlBuilder builder = new HtmlBuilder();
+ builder.add("Plain. ");
+ builder.beginBold();
+ builder.add("Bold. ");
+ builder.addLink("mylink", "foo://bar:123");
+ builder.endBold();
+ assertEquals("Plain. <B>Bold. <A HREF=\"foo://bar:123\">mylink</A></B>",
+ builder.getHtml());
+ }
+
+ public void test5() {
+ HtmlBuilder builder = new HtmlBuilder();
+ builder.addLink("This is the ", "linked text", "!", "foo://bar");
+ assertEquals("This is the <A HREF=\"foo://bar\">linked text</A>!", builder.getHtml());
+ }
+
+ public void testTable1() {
+ HtmlBuilder builder = new HtmlBuilder();
+ builder.beginTable().addTableRow(true, "Header1", "Header2")
+ .addTableRow("Data1", "Data2")
+ .endTable();
+ assertEquals(
+ "<table><tr><th>Header1</th><th>Header2</th></tr><tr><td>Data1</td><td>Data2</td></tr></table>",
+ builder.getHtml());
+ }
+
+ public void testTable2() {
+ HtmlBuilder builder = new HtmlBuilder();
+ builder.beginTable("valign=\"top\"").addTableRow("Data1", "Data2").endTable();
+ assertEquals(
+ "<table><tr><td valign=\"top\">Data1</td><td valign=\"top\">Data2</td></tr></table>",
+ builder.getHtml());
+ }
+
+ public void testDiv1() {
+ HtmlBuilder builder = new HtmlBuilder();
+ assertEquals("<div>Hello</div>", builder.beginDiv().add("Hello").endDiv().getHtml());
+ }
+
+ public void testDiv2() {
+ HtmlBuilder builder = new HtmlBuilder();
+ assertEquals("<div style=\"padding: 10px; text-color: gray\">Hello</div>",
+ builder.beginDiv("padding: 10px; text-color: gray").add("Hello").endDiv()
+ .getHtml());
+ }
+
+ public void testImage() throws IOException {
+ File f = File.createTempFile("img", "png");
+ f.deleteOnExit();
+
+ String actual = new HtmlBuilder().addImage(SdkUtils.fileToUrl(f), "preview").getHtml();
+ String path = f.getAbsolutePath();
+
+ if (!path.startsWith("/")) {
+ path = '/' + path;
+ }
+ String expected = String.format("<img src='file:%1$s' alt=\"preview\" />", path);
+ if (File.separatorChar != '/') {
+ // SdkUtil.fileToUrl always returns / as a separator so adjust
+ // Windows path accordingly.
+ expected = expected.replace(File.separatorChar, '/');
+ }
+ assertEquals(expected, actual);
+ }
+
+ public void testNewlineIfNecessary() {
+ HtmlBuilder builder = new HtmlBuilder();
+ builder.newlineIfNecessary();
+ assertEquals("<BR/>\n", builder.getHtml());
+ builder.newlineIfNecessary();
+ assertEquals("<BR/>\n", builder.getHtml());
+ builder.add("a");
+ builder.newlineIfNecessary();
+ assertEquals("<BR/>\na<BR/>\n", builder.getHtml());
+ builder.newline();
+ builder.newlineIfNecessary();
+ builder.newlineIfNecessary();
+ builder.newlineIfNecessary();
+ assertEquals("<BR/>\na<BR/>\n<BR/>\n", builder.getHtml());
+ }
+}
diff --git a/common/src/test/java/com/android/utils/SdkUtilsTest.java b/common/src/test/java/com/android/utils/SdkUtilsTest.java
index b250972..e893d91 100644
--- a/common/src/test/java/com/android/utils/SdkUtilsTest.java
+++ b/common/src/test/java/com/android/utils/SdkUtilsTest.java
@@ -16,8 +16,23 @@
package com.android.utils;
+import static com.android.utils.SdkUtils.FILENAME_PREFIX;
+import static com.android.utils.SdkUtils.createPathComment;
+import static com.android.utils.SdkUtils.fileToUrlString;
+import static com.android.utils.SdkUtils.urlToFile;
+
+import com.android.SdkConstants;
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+
import junit.framework.TestCase;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
import java.text.ParseException;
import java.util.Locale;
@@ -216,4 +231,105 @@
assertEquals(1000.0, SdkUtils.parseLocalizedDouble("1000", -1)); // Valid
assertEquals(0.0, SdkUtils.parseLocalizedDouble("", 8));
}
-}
+
+ public void testFileToUrl() throws Exception {
+ if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
+ assertEquals("file:/D:/tmp/foo/bar",
+ fileToUrlString(new File("D:\\tmp\\foo\\bar")));
+ // File path normalization adds a missing drive letter on Windows's File
+ // implementation which defaults to C:
+ assertEquals("file:/C:/tmp/foo/bar",
+ fileToUrlString(new File("/tmp/foo/bar")));
+ assertEquals("file:/C:/tmp/$&+,:;=%3F@/foo%20bar%25",
+ fileToUrlString(new File("/tmp/$&+,:;=?@/foo bar%")));
+ } else {
+ assertEquals("file:/tmp/foo/bar",
+ fileToUrlString(new File("/tmp/foo/bar")));
+ assertEquals("file:/tmp/$&+,:;=%3F@/foo%20bar%25",
+ fileToUrlString(new File("/tmp/$&+,:;=?@/foo bar%")));
+ }
+ }
+
+ public void testUrlToFile() throws Exception {
+ assertEquals(new File("/tmp/foo/bar"), urlToFile("file:/tmp/foo/bar"));
+ assertEquals(new File("/tmp/$&+,:;=?@/foo bar%"),
+ urlToFile("file:/tmp/$&+,:;=%3F@/foo%20bar%25"));
+
+ assertEquals(new File("/tmp/foo/bar"),
+ urlToFile(new URL("file:/tmp/foo/bar")));
+ assertEquals(new File("/tmp/$&+,:;=?@/foo bar%"),
+ urlToFile(new URL("file:/tmp/$&+,:;=%3F@/foo%20bar%25")));
+ }
+
+ public void testCreatePathComment() throws Exception {
+ assertEquals("From: file:/tmp/foo", createPathComment(new File("/tmp/foo"), false));
+ assertEquals(" From: file:/tmp/foo ", createPathComment(new File("/tmp/foo"), true));
+ assertEquals("From: file:/tmp-/%2D%2D/a%2D%2Da/foo",
+ createPathComment(new File("/tmp-/--/a--a/foo"), false));
+
+ String path = "/tmp/foo";
+ String urlString =
+ createPathComment(new File(path), false).substring(5); // 5: "From:".length()
+ assertEquals(path, urlToFile(new URL(urlString)).getPath());
+
+ path = "/tmp-/--/a--a/foo";
+ urlString = createPathComment(new File(path), false).substring(5);
+ assertEquals(path, urlToFile(new URL(urlString)).getPath());
+
+ // Make sure we handle file://path too, not just file:path
+ urlString = "file:///tmp-/%2D%2D/a%2D%2Da/foo";
+ assertEquals(path, urlToFile(new URL(urlString)).getPath());
+ }
+
+ public void testFormattedComment() throws Exception {
+ Document document = XmlUtils.parseDocumentSilently("<root/>", true);
+ assertNotNull(document);
+ // Many invalid characters in XML, such as -- and <, and characters invalid in URLs, such
+ // as spaces
+ String path = "/My Program Files/--/Q&A/X<Y/foo";
+ String comment = createPathComment(new File(path), true);
+ Element root = document.getDocumentElement();
+ assertNotNull(root);
+ root.appendChild(document.createComment(comment));
+ String xml = XmlUtils.toXml(document, false);
+ assertEquals(""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<root><!-- From: file:/My%20Program%20Files/%2D%2D/Q&A/X%3CY/foo --></root>",
+ xml);
+ int index = xml.indexOf(FILENAME_PREFIX);
+ assertTrue(index != -1);
+ String urlString = xml.substring(index + FILENAME_PREFIX.length(),
+ xml.indexOf("-->")).trim();
+ assertEquals(path, urlToFile(new URL(urlString)).getPath());
+ }
+
+ public void testCopyXmlWithSourceReference() throws IOException {
+ File source = File.createTempFile("source", SdkConstants.DOT_XML);
+ File dest = File.createTempFile("dest", SdkConstants.DOT_XML);
+ Files.write(""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <string name=\"description_search\">Search</string>\n"
+ + " <string name=\"description_map\">Map</string>\n"
+ + " <string name=\"description_refresh\">Refresh</string>\n"
+ + " <string name=\"description_share\">Share</string>\n"
+ + "</resources>",
+ source, Charsets.UTF_8);
+ SdkUtils.copyXmlWithSourceReference(source, dest);
+
+ String sourceUrl = SdkUtils.fileToUrlString(source);
+ assertEquals(""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <string name=\"description_search\">Search</string>\n"
+ + " <string name=\"description_map\">Map</string>\n"
+ + " <string name=\"description_refresh\">Refresh</string>\n"
+ + " <string name=\"description_share\">Share</string>\n"
+ + "</resources><!-- From: " + sourceUrl + " -->",
+ Files.toString(dest, Charsets.UTF_8));
+ boolean deleted = source.delete();
+ assertTrue(deleted);
+ deleted = dest.delete();
+ assertTrue(deleted);
+ }
+}
\ No newline at end of file
diff --git a/common/src/test/java/com/android/utils/XmlUtilsTest.java b/common/src/test/java/com/android/utils/XmlUtilsTest.java
index b76d0b7..95adb4b 100644
--- a/common/src/test/java/com/android/utils/XmlUtilsTest.java
+++ b/common/src/test/java/com/android/utils/XmlUtilsTest.java
@@ -245,6 +245,18 @@
xml);
}
+ public void testToXml5() throws Exception {
+ String xml = ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<root>\n"
+ + " <!-- <&'>\" -->\n"
+ + "</root>";
+ Document doc = parse(xml);
+
+ String formatted = XmlUtils.toXml(doc, true);
+ assertEquals(xml, formatted);
+ }
+
@Nullable
private static Document createEmptyPlainDocument() throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
diff --git a/ddmlib/build.gradle b/ddmlib/build.gradle
index 4aad6b6..3dc7c8e 100644
--- a/ddmlib/build.gradle
+++ b/ddmlib/build.gradle
@@ -1,3 +1,6 @@
+apply plugin: 'java'
+apply plugin: 'distrib'
+
group = 'com.android.tools.ddms'
archivesBaseName = 'ddmlib'
@@ -19,45 +22,9 @@
from 'NOTICE'
}
-uploadArchives {
- repositories {
- mavenDeployer {
- beforeDeployment { MavenDeployment deployment ->
- if (!project.has("release")) {
- throw new StopExecutionException("uploadArchives must be called with the release.gradle init script")
- }
+project.ext.pomName = 'Android Tools ddmlib'
+project.ext.pomDesc = 'Library providing APIs to talk to Android devices'
- signing.signPom(deployment)
- }
-
- repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
- authentication(userName: project.ext.sonatypeUsername, password: project.ext.sonatypePassword)
- }
-
- pom.project {
- name 'Android Tools ddmlib'
- description 'Library providing APIs to talk to Android devices'
- url 'http://tools.android.com'
- inceptionYear '2007'
-
- licenses {
- license {
- name 'The Apache Software License, Version 2.0'
- url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
- distribution 'repo'
- }
- }
-
- scm {
- url "https://android.googlesource.com/platform/tools/base"
- connection "git://android.googlesource.com/platform/tools/base.git"
- }
- developers {
- developer {
- name 'The Android Open Source Project'
- }
- }
- }
- }
- }
-}
+apply from: '../baseVersion.gradle'
+apply from: '../publish.gradle'
+apply from: '../javadoc.gradle'
diff --git a/ddmlib/src/main/java/com/android/ddmlib/AndroidDebugBridge.java b/ddmlib/src/main/java/com/android/ddmlib/AndroidDebugBridge.java
index 1edc383..f807a79 100644
--- a/ddmlib/src/main/java/com/android/ddmlib/AndroidDebugBridge.java
+++ b/ddmlib/src/main/java/com/android/ddmlib/AndroidDebugBridge.java
@@ -729,8 +729,10 @@
}
// kill the monitoring services
- mDeviceMonitor.stop();
- mDeviceMonitor = null;
+ if (mDeviceMonitor != null) {
+ mDeviceMonitor.stop();
+ mDeviceMonitor = null;
+ }
if (!stopAdb()) {
return false;
diff --git a/ddmlib/src/main/java/com/android/ddmlib/Client.java b/ddmlib/src/main/java/com/android/ddmlib/Client.java
index b2261fd..82fea87 100644
--- a/ddmlib/src/main/java/com/android/ddmlib/Client.java
+++ b/ddmlib/src/main/java/com/android/ddmlib/Client.java
@@ -17,7 +17,6 @@
package com.android.ddmlib;
import com.android.annotations.NonNull;
-import com.android.ddmlib.ClientData.MethodProfilingStatus;
import com.android.ddmlib.DebugPortManager.IDebugPortProvider;
import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener;
@@ -28,6 +27,7 @@
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
+import java.util.concurrent.TimeUnit;
/**
* This represents a single client, usually a Dalvik VM process.
@@ -247,25 +247,24 @@
}
}
+ /**
+ * Toggles method profiling state.
+ * @deprecated Use {@link #startMethodTracer()}, {@link #stopMethodTracer()},
+ * {@link #startSamplingProfiler(int, java.util.concurrent.TimeUnit)} or
+ * {@link #stopSamplingProfiler()} instead.
+ */
public void toggleMethodProfiling() {
- boolean canStream = mClientData.hasFeature(ClientData.FEATURE_PROFILING_STREAMING);
try {
- if (mClientData.getMethodProfilingStatus() == MethodProfilingStatus.ON) {
- if (canStream) {
- HandleProfiling.sendMPSE(this);
- } else {
- HandleProfiling.sendMPRE(this);
- }
- } else {
- int bufferSize = DdmPreferences.getProfilerBufferSizeMb() * 1024 * 1024;
- if (canStream) {
- HandleProfiling.sendMPSS(this, bufferSize, 0 /*flags*/);
- } else {
- String file = "/sdcard/" +
- mClientData.getClientDescription().replaceAll("\\:.*", "") +
- DdmConstants.DOT_TRACE;
- HandleProfiling.sendMPRS(this, file, bufferSize, 0 /*flags*/);
- }
+ switch (mClientData.getMethodProfilingStatus()) {
+ case TRACER_ON:
+ stopMethodTracer();
+ break;
+ case SAMPLER_ON:
+ stopSamplingProfiler();
+ break;
+ case OFF:
+ startMethodTracer();
+ break;
}
} catch (IOException e) {
Log.w("ddms", "Toggle method profiling failed");
@@ -273,6 +272,42 @@
}
}
+ private int getProfileBufferSize() {
+ return DdmPreferences.getProfilerBufferSizeMb() * 1024 * 1024;
+ }
+
+ public void startMethodTracer() throws IOException {
+ boolean canStream = mClientData.hasFeature(ClientData.FEATURE_PROFILING_STREAMING);
+ int bufferSize = getProfileBufferSize();
+ if (canStream) {
+ HandleProfiling.sendMPSS(this, bufferSize, 0 /*flags*/);
+ } else {
+ String file = "/sdcard/" +
+ mClientData.getClientDescription().replaceAll("\\:.*", "") +
+ DdmConstants.DOT_TRACE;
+ HandleProfiling.sendMPRS(this, file, bufferSize, 0 /*flags*/);
+ }
+ }
+
+ public void stopMethodTracer() throws IOException {
+ boolean canStream = mClientData.hasFeature(ClientData.FEATURE_PROFILING_STREAMING);
+
+ if (canStream) {
+ HandleProfiling.sendMPSE(this);
+ } else {
+ HandleProfiling.sendMPRE(this);
+ }
+ }
+
+ public void startSamplingProfiler(int samplingInterval, TimeUnit timeUnit) throws IOException {
+ int bufferSize = getProfileBufferSize();
+ HandleProfiling.sendSPSS(this, bufferSize, samplingInterval, timeUnit);
+ }
+
+ public void stopSamplingProfiler() throws IOException {
+ HandleProfiling.sendSPSE(this);
+ }
+
public boolean startOpenGlTracing() {
boolean canTraceOpenGl = mClientData.hasFeature(ClientData.FEATURE_OPENGL_TRACING);
if (!canTraceOpenGl) {
diff --git a/ddmlib/src/main/java/com/android/ddmlib/ClientData.java b/ddmlib/src/main/java/com/android/ddmlib/ClientData.java
index 1e72523..76f5f97 100644
--- a/ddmlib/src/main/java/com/android/ddmlib/ClientData.java
+++ b/ddmlib/src/main/java/com/android/ddmlib/ClientData.java
@@ -92,8 +92,10 @@
UNKNOWN,
/** Method profiling status: the {@link Client} is not profiling method calls. */
OFF,
- /** Method profiling status: the {@link Client} is profiling method calls. */
- ON
+ /** Method profiling status: the {@link Client} is tracing method calls. */
+ TRACER_ON,
+ /** Method profiling status: the {@link Client} is being profiled via sampling. */
+ SAMPLER_ON
}
/**
@@ -130,6 +132,12 @@
public static final String FEATURE_PROFILING_STREAMING = "method-trace-profiling-streaming"; //$NON-NLS-1$
/**
+ * String for feature enabling sampling profiler.
+ * @see #hasFeature(String)
+ */
+ public static final String FEATURE_SAMPLING_PROFILER = "method-sample-profiling"; //$NON-NLS-1$
+
+ /**
* String for feature indicating support for tracing OpenGL calls.
* @see #hasFeature(String)
*/
diff --git a/ddmlib/src/main/java/com/android/ddmlib/CollectingOutputReceiver.java b/ddmlib/src/main/java/com/android/ddmlib/CollectingOutputReceiver.java
index e262cf3..efe092c 100644
--- a/ddmlib/src/main/java/com/android/ddmlib/CollectingOutputReceiver.java
+++ b/ddmlib/src/main/java/com/android/ddmlib/CollectingOutputReceiver.java
@@ -18,6 +18,7 @@
import java.io.UnsupportedEncodingException;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* A {@link IShellOutputReceiver} which collects the whole shell output into one
@@ -26,7 +27,7 @@
public class CollectingOutputReceiver implements IShellOutputReceiver {
private CountDownLatch mCompletionLatch;
private StringBuffer mOutputBuffer = new StringBuffer();
- private boolean mIsCanceled = false;
+ private AtomicBoolean mIsCanceled = new AtomicBoolean(false);
public CollectingOutputReceiver() {
}
@@ -41,20 +42,20 @@
@Override
public boolean isCancelled() {
- return mIsCanceled;
+ return mIsCanceled.get();
}
/**
* Cancel the output collection
*/
public void cancel() {
- mIsCanceled = true;
+ mIsCanceled.set(true);
}
@Override
public void addOutput(byte[] data, int offset, int length) {
if (!isCancelled()) {
- String s = null;
+ String s;
try {
s = new String(data, offset, length, "UTF-8"); //$NON-NLS-1$
} catch (UnsupportedEncodingException e) {
diff --git a/ddmlib/src/main/java/com/android/ddmlib/Device.java b/ddmlib/src/main/java/com/android/ddmlib/Device.java
index 22c462b..8e9bece 100644
--- a/ddmlib/src/main/java/com/android/ddmlib/Device.java
+++ b/ddmlib/src/main/java/com/android/ddmlib/Device.java
@@ -16,6 +16,9 @@
package com.android.ddmlib;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
import com.android.annotations.concurrency.GuardedBy;
import com.android.ddmlib.log.LogReceiver;
@@ -38,7 +41,6 @@
* A Device. It can be a physical device or an emulator.
*/
final class Device implements IDevice {
-
private static final int INSTALL_TIMEOUT = 2*60*1000; //2min
private static final int BATTERY_TIMEOUT = 2*1000; //2 seconds
private static final int GETPROP_TIMEOUT = 2*1000; //2 seconds
@@ -81,6 +83,13 @@
private Integer mLastBatteryLevel = null;
private long mLastBatteryCheckTime = 0;
+ /** Path to the screen recorder binary on the device. */
+ private static final String SCREEN_RECORDER_DEVICE_PATH = "/system/bin/screenrecord";
+
+ /** Flag indicating whether the device has the screen recorder binary. */
+ private Boolean mHasScreenRecorder;
+
+ private int mApiLevel;
private String mName;
/**
@@ -106,6 +115,8 @@
Matcher m = FAILURE_PATTERN.matcher(line);
if (m.matches()) {
mErrorMessage = m.group(1);
+ } else {
+ mErrorMessage = "Unknown failure";
}
}
}
@@ -173,6 +184,50 @@
}
}
+ /**
+ * Output receiver for "cat /sys/class/power_supply/.../capacity" command line.
+ */
+ static final class SysFsBatteryLevelReceiver extends MultiLineReceiver {
+
+ private static final Pattern BATTERY_LEVEL = Pattern.compile("^(\\d+)[.\\s]*");
+ private Integer mBatteryLevel = null;
+
+ /**
+ * Get the parsed battery level.
+ * @return battery level or <code>null</code> if it cannot be determined
+ */
+ @Nullable
+ public Integer getBatteryLevel() {
+ return mBatteryLevel;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+
+ @Override
+ public void processNewLines(String[] lines) {
+ for (String line : lines) {
+ Matcher batteryMatch = BATTERY_LEVEL.matcher(line);
+ if (batteryMatch.matches()) {
+ if (mBatteryLevel == null) {
+ mBatteryLevel = Integer.parseInt(batteryMatch.group(1));
+ } else {
+ // multiple matches, check if they are different
+ Integer tmpLevel = Integer.parseInt(batteryMatch.group(1));
+ if (!mBatteryLevel.equals(tmpLevel)) {
+ Log.w(LOG_TAG, String.format(
+ "Multiple lines matched with different value; " +
+ "Original: %s, Current: %s (keeping original)",
+ mBatteryLevel.toString(), tmpLevel.toString()));
+ }
+ }
+ }
+ }
+ }
+ }
+
/*
* (non-Javadoc)
* @see com.android.ddmlib.IDevice#getSerialNumber()
@@ -352,6 +407,56 @@
}
@Override
+ public boolean supportsFeature(Feature feature) {
+ switch (feature) {
+ case SCREEN_RECORD:
+ if (getApiLevel() < 19) {
+ return false;
+ }
+ if (mHasScreenRecorder == null) {
+ mHasScreenRecorder = hasBinary(SCREEN_RECORDER_DEVICE_PATH);
+ }
+ return mHasScreenRecorder;
+ case PROCSTATS:
+ return getApiLevel() >= 19;
+ default:
+ return false;
+ }
+ }
+
+ private int getApiLevel() {
+ if (mApiLevel > 0) {
+ return mApiLevel;
+ }
+
+ try {
+ mApiLevel = Integer.parseInt(getPropertyCacheOrSync(PROP_BUILD_API_LEVEL));
+ return mApiLevel;
+ } catch (Exception e) {
+ return -1;
+ }
+ }
+
+ private boolean hasBinary(String path) {
+ CountDownLatch latch = new CountDownLatch(1);
+ CollectingOutputReceiver receiver = new CollectingOutputReceiver(latch);
+ try {
+ executeShellCommand("ls " + path, receiver);
+ } catch (Exception e) {
+ return false;
+ }
+
+ try {
+ latch.await(GETPROP_TIMEOUT, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ return false;
+ }
+
+ String value = receiver.getOutput().trim();
+ return !value.endsWith("No such file or directory");
+ }
+
+ @Override
public String getMountPoint(String name) {
return mMountPoints.get(name);
}
@@ -429,6 +534,50 @@
}
@Override
+ public void startScreenRecorder(String remoteFilePath, ScreenRecorderOptions options,
+ IShellOutputReceiver receiver) throws TimeoutException, AdbCommandRejectedException,
+ IOException, ShellCommandUnresponsiveException {
+ executeShellCommand(getScreenRecorderCommand(remoteFilePath, options), receiver, 0, null);
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ static String getScreenRecorderCommand(@NonNull String remoteFilePath,
+ @NonNull ScreenRecorderOptions options) {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("screenrecord");
+ sb.append(' ');
+
+ if (options.width > 0 && options.height > 0) {
+ sb.append("--size ");
+ sb.append(options.width);
+ sb.append('x');
+ sb.append(options.height);
+ sb.append(' ');
+ }
+
+ if (options.bitrateMbps > 0) {
+ sb.append("--bit-rate ");
+ sb.append(options.bitrateMbps * 1000000);
+ sb.append(' ');
+ }
+
+ if (options.timeLimit > 0) {
+ sb.append("--time-limit ");
+ long seconds = TimeUnit.SECONDS.convert(options.timeLimit, options.timeLimitUnits);
+ if (seconds > 180) {
+ seconds = 180;
+ }
+ sb.append(seconds);
+ sb.append(' ');
+ }
+
+ sb.append(remoteFilePath);
+
+ return sb.toString();
+ }
+
+ @Override
public void executeShellCommand(String command, IShellOutputReceiver receiver)
throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException,
IOException {
@@ -872,6 +1021,16 @@
&& mLastBatteryCheckTime > (System.currentTimeMillis() - freshnessMs)) {
return mLastBatteryLevel;
}
+ // first try to get it from sysfs
+ SysFsBatteryLevelReceiver sysBattReceiver = new SysFsBatteryLevelReceiver();
+ executeShellCommand("cat /sys/class/power_supply/*/capacity",
+ sysBattReceiver, BATTERY_TIMEOUT);
+ mLastBatteryLevel = sysBattReceiver.getBatteryLevel();
+ if (mLastBatteryLevel != null) {
+ mLastBatteryCheckTime = System.currentTimeMillis();
+ return mLastBatteryLevel;
+ }
+ // now try dumpsys
BatteryReceiver receiver = new BatteryReceiver();
executeShellCommand("dumpsys battery", receiver, BATTERY_TIMEOUT);
mLastBatteryLevel = receiver.getBatteryLevel();
diff --git a/ddmlib/src/main/java/com/android/ddmlib/HandleProfiling.java b/ddmlib/src/main/java/com/android/ddmlib/HandleProfiling.java
index 9d01fdf..cdefd2c 100644
--- a/ddmlib/src/main/java/com/android/ddmlib/HandleProfiling.java
+++ b/ddmlib/src/main/java/com/android/ddmlib/HandleProfiling.java
@@ -21,6 +21,7 @@
import java.io.IOException;
import java.nio.ByteBuffer;
+import java.util.concurrent.TimeUnit;
/**
* Handle heap status updates.
@@ -31,6 +32,8 @@
public static final int CHUNK_MPRE = type("MPRE");
public static final int CHUNK_MPSS = type("MPSS");
public static final int CHUNK_MPSE = type("MPSE");
+ public static final int CHUNK_SPSS = type("SPSS");
+ public static final int CHUNK_SPSE = type("SPSE");
public static final int CHUNK_MPRQ = type("MPRQ");
public static final int CHUNK_FAIL = type("FAIL");
@@ -194,6 +197,35 @@
}
/**
+ * Send a SPSS (Sampling Profiling Streaming Start) request to the client.
+ *
+ * @param bufferSize is the desired buffer size in bytes (8MB is good)
+ * @param samplingInterval sampling interval
+ * @param samplingIntervalTimeUnits units for sampling interval
+ */
+ public static void sendSPSS(Client client, int bufferSize, int samplingInterval,
+ TimeUnit samplingIntervalTimeUnits) throws IOException {
+ int interval = (int) samplingIntervalTimeUnits.toMicros(samplingInterval);
+
+ ByteBuffer rawBuf = allocBuffer(3*4);
+ JdwpPacket packet = new JdwpPacket(rawBuf);
+ ByteBuffer buf = getChunkDataBuf(rawBuf);
+
+ buf.putInt(bufferSize);
+ buf.putInt(0); // flags
+ buf.putInt(interval);
+
+ finishChunkPacket(packet, CHUNK_SPSS, buf.position());
+ Log.d("ddm-prof", "Sending " + name(CHUNK_SPSS)
+ + "', size=" + bufferSize + ", flags=0, samplingInterval=" + interval);
+ client.sendAndConsume(packet, mInst);
+
+ // send a status query. this ensure that the status is properly updated if for some
+ // reason starting the tracing failed.
+ sendMPRQ(client);
+ }
+
+ /**
* Send a MPSE (Method Profiling Streaming End) request to the client.
*/
public static void sendMPSE(Client client) throws IOException {
@@ -209,6 +241,21 @@
}
/**
+ * Send a SPSE (Sampling Profiling Streaming End) request to the client.
+ */
+ public static void sendSPSE(Client client) throws IOException {
+ ByteBuffer rawBuf = allocBuffer(0);
+ JdwpPacket packet = new JdwpPacket(rawBuf);
+ ByteBuffer buf = getChunkDataBuf(rawBuf);
+
+ // no data
+
+ finishChunkPacket(packet, CHUNK_SPSE, buf.position());
+ Log.d("ddm-prof", "Sending " + name(CHUNK_SPSE));
+ client.sendAndConsume(packet, mInst);
+ }
+
+ /**
* Handle incoming profiling data. The MPSE packet includes the
* complete .trace file.
*/
@@ -253,9 +300,12 @@
if (result == 0) {
client.getClientData().setMethodProfilingStatus(MethodProfilingStatus.OFF);
Log.d("ddm-prof", "Method profiling is not running");
- } else {
- client.getClientData().setMethodProfilingStatus(MethodProfilingStatus.ON);
- Log.d("ddm-prof", "Method profiling is running");
+ } else if (result == 1) {
+ client.getClientData().setMethodProfilingStatus(MethodProfilingStatus.TRACER_ON);
+ Log.d("ddm-prof", "Method tracing is active");
+ } else if (result == 2) {
+ client.getClientData().setMethodProfilingStatus(MethodProfilingStatus.SAMPLER_ON);
+ Log.d("ddm-prof", "Sampler based profiling is active");
}
client.update(Client.CHANGE_METHOD_PROFILING_STATUS);
}
diff --git a/ddmlib/src/main/java/com/android/ddmlib/IDevice.java b/ddmlib/src/main/java/com/android/ddmlib/IDevice.java
index 7b70acb..c8d72ae 100644
--- a/ddmlib/src/main/java/com/android/ddmlib/IDevice.java
+++ b/ddmlib/src/main/java/com/android/ddmlib/IDevice.java
@@ -16,6 +16,7 @@
package com.android.ddmlib;
+import com.android.annotations.NonNull;
import com.android.ddmlib.log.LogReceiver;
import java.io.IOException;
@@ -31,6 +32,8 @@
public static final String PROP_BUILD_CODENAME = "ro.build.version.codename";
public static final String PROP_DEVICE_MODEL = "ro.product.model";
public static final String PROP_DEVICE_MANUFACTURER = "ro.product.manufacturer";
+ public static final String PROP_DEVICE_CPU_ABI = "ro.product.cpu.abi";
+ public static final String PROP_DEVICE_CPU_ABI2 = "ro.product.cpu.abi2";
public static final String PROP_DEBUGGABLE = "ro.debuggable";
@@ -43,6 +46,12 @@
/** Device change bit mask: build info change. */
public static final int CHANGE_BUILD_INFO = 0x0004;
+ /** List of device level features. */
+ public enum Feature {
+ SCREEN_RECORD, // screen recorder available?
+ PROCSTATS, // procstats service (dumpsys procstats) available
+ };
+
/** @deprecated Use {@link #PROP_BUILD_API_LEVEL}. */
@Deprecated
public static final String PROP_BUILD_VERSION_NUMBER = PROP_BUILD_API_LEVEL;
@@ -176,6 +185,9 @@
public String getPropertyCacheOrSync(String name) throws TimeoutException,
AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException;
+ /** Returns whether this device supports the given feature. */
+ boolean supportsFeature(@NonNull Feature feature);
+
/**
* Returns a mount point.
*
@@ -262,6 +274,14 @@
IOException;
/**
+ * Initiates screen recording on the device if the device supports {@link Feature#SCREEN_RECORD}.
+ */
+ public void startScreenRecorder(@NonNull String remoteFilePath,
+ @NonNull ScreenRecorderOptions options, @NonNull IShellOutputReceiver receiver) throws
+ TimeoutException, AdbCommandRejectedException, IOException,
+ ShellCommandUnresponsiveException;
+
+ /**
* @deprecated Use {@link #executeShellCommand(String, IShellOutputReceiver, long, java.util.concurrent.TimeUnit)}.
*/
@Deprecated
diff --git a/ddmlib/src/main/java/com/android/ddmlib/ScreenRecorderOptions.java b/ddmlib/src/main/java/com/android/ddmlib/ScreenRecorderOptions.java
new file mode 100644
index 0000000..af8952a
--- /dev/null
+++ b/ddmlib/src/main/java/com/android/ddmlib/ScreenRecorderOptions.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ddmlib;
+
+import java.util.concurrent.TimeUnit;
+
+public class ScreenRecorderOptions {
+ // video size is given by width x height, defaults to device's main display resolution
+ // or 1280x720.
+ public final int width;
+ public final int height;
+
+ // bit rate in Mbps. Defaults to 4Mbps
+ public final int bitrateMbps;
+
+ // time limit, maximum of 3 seconds
+ public final long timeLimit;
+ public final TimeUnit timeLimitUnits;
+
+ private ScreenRecorderOptions(Builder builder) {
+ width = builder.mWidth;
+ height = builder.mHeight;
+
+ bitrateMbps = builder.mBitRate;
+
+ timeLimit = builder.mTime;
+ timeLimitUnits = builder.mTimeUnits;
+ }
+
+ public static class Builder {
+ private int mWidth;
+ private int mHeight;
+ private int mBitRate;
+ private long mTime;
+ private TimeUnit mTimeUnits;
+
+ public Builder setSize(int w, int h) {
+ mWidth = w;
+ mHeight = h;
+ return this;
+ }
+
+ public Builder setBitRate(int bitRateMbps) {
+ mBitRate = bitRateMbps;
+ return this;
+ }
+
+ public Builder setTimeLimit(long time, TimeUnit units) {
+ mTime = time;
+ mTimeUnits = units;
+ return this;
+ }
+
+ public ScreenRecorderOptions build() {
+ return new ScreenRecorderOptions(this);
+ }
+ }
+}
diff --git a/ddmlib/src/test/java/com/android/ddmlib/AndroidDebugBridgeTest.java b/ddmlib/src/test/java/com/android/ddmlib/AndroidDebugBridgeTest.java
new file mode 100644
index 0000000..f9aa8a3
--- /dev/null
+++ b/ddmlib/src/test/java/com/android/ddmlib/AndroidDebugBridgeTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ddmlib;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.IOException;
+
+public class AndroidDebugBridgeTest extends TestCase {
+ private String mAndroidHome;
+
+ @Override
+ protected void setUp() throws Exception {
+ mAndroidHome = System.getenv("ANDROID_HOME");
+ assertNotNull(
+ "This test requires ANDROID_HOME environment variable to point to a valid SDK",
+ mAndroidHome);
+
+ AndroidDebugBridge.init(false);
+ }
+
+ // https://code.google.com/p/android/issues/detail?id=63170
+ public void testCanRecreateAdb() throws IOException {
+ File adbPath = new File(mAndroidHome, "platform-tools" + File.separator + "adb");
+
+ AndroidDebugBridge adb = AndroidDebugBridge.createBridge(adbPath.getCanonicalPath(), true);
+ assertNotNull(adb);
+ AndroidDebugBridge.terminate();
+
+ adb = AndroidDebugBridge.createBridge(adbPath.getCanonicalPath(), true);
+ assertNotNull(adb);
+ AndroidDebugBridge.terminate();
+ }
+}
diff --git a/ddmlib/src/test/java/com/android/ddmlib/DeviceTest.java b/ddmlib/src/test/java/com/android/ddmlib/DeviceTest.java
new file mode 100644
index 0000000..fb55987
--- /dev/null
+++ b/ddmlib/src/test/java/com/android/ddmlib/DeviceTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ddmlib;
+
+import junit.framework.TestCase;
+
+import java.util.concurrent.TimeUnit;
+
+public class DeviceTest extends TestCase {
+ public void testScreenRecorderOptions() {
+ ScreenRecorderOptions options =
+ new ScreenRecorderOptions.Builder()
+ .setBitRate(6)
+ .setSize(600,400)
+ .build();
+ assertEquals("screenrecord --size 600x400 --bit-rate 6000000 /sdcard/1.mp4",
+ Device.getScreenRecorderCommand("/sdcard/1.mp4", options));
+
+ options = new ScreenRecorderOptions.Builder().setTimeLimit(100, TimeUnit.SECONDS).build();
+ assertEquals("screenrecord --time-limit 100 /sdcard/1.mp4",
+ Device.getScreenRecorderCommand("/sdcard/1.mp4", options));
+
+ options = new ScreenRecorderOptions.Builder().setTimeLimit(4, TimeUnit.MINUTES).build();
+ assertEquals("screenrecord --time-limit 180 /sdcard/1.mp4",
+ Device.getScreenRecorderCommand("/sdcard/1.mp4", options));
+ }
+}
diff --git a/ddmlib/src/test/java/com/android/ddmlib/SysFsBatteryLevelReceiverTest.java b/ddmlib/src/test/java/com/android/ddmlib/SysFsBatteryLevelReceiverTest.java
new file mode 100644
index 0000000..70970c3
--- /dev/null
+++ b/ddmlib/src/test/java/com/android/ddmlib/SysFsBatteryLevelReceiverTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ddmlib;
+
+import com.android.ddmlib.Device.SysFsBatteryLevelReceiver;
+
+import junit.framework.TestCase;
+
+import java.util.Random;
+
+public class SysFsBatteryLevelReceiverTest extends TestCase {
+
+ private SysFsBatteryLevelReceiver mReceiver;
+ private Integer mExpected1, mExpected2;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mReceiver = new SysFsBatteryLevelReceiver();
+ Random r = new Random(System.currentTimeMillis());
+ mExpected1 = r.nextInt(101);
+ mExpected2 = r.nextInt(101);
+ }
+
+ public void testSingleLine() {
+ String[] lines = {mExpected1.toString()};
+ mReceiver.processNewLines(lines);
+ assertEquals(mExpected1, mReceiver.getBatteryLevel());
+ }
+
+ public void testWithTrailingWhitespace1() {
+ String[] lines = {mExpected1 + " "};
+ mReceiver.processNewLines(lines);
+ assertEquals(mExpected1, mReceiver.getBatteryLevel());
+ }
+
+ public void testWithTrailingWhitespace2() {
+ String[] lines = {mExpected1 + "\n"};
+ mReceiver.processNewLines(lines);
+ assertEquals(mExpected1, mReceiver.getBatteryLevel());
+ }
+
+ public void testWithTrailingWhitespace3() {
+ String[] lines = {mExpected1 + "\r"};
+ mReceiver.processNewLines(lines);
+ assertEquals(mExpected1, mReceiver.getBatteryLevel());
+ }
+
+ public void testWithTrailingWhitespace4() {
+ String[] lines = {mExpected1 + "\r\n"};
+ mReceiver.processNewLines(lines);
+ assertEquals(mExpected1, mReceiver.getBatteryLevel());
+ }
+
+ public void testMultipleLinesSame() {
+ String[] lines = {mExpected1 + "\n", mExpected2.toString()};
+ mReceiver.processNewLines(lines);
+ assertEquals(mExpected1, mReceiver.getBatteryLevel());
+ }
+
+ public void testMultipleLinesDifferent() {
+ String[] lines = {mExpected1 + "\n", mExpected2.toString()};
+ mReceiver.processNewLines(lines);
+ assertEquals(mExpected1, mReceiver.getBatteryLevel());
+ }
+
+ public void testInvalid() {
+ String[] lines = {"foo\n", "bar", "yadda"};
+ mReceiver.processNewLines(lines);
+ assertNull(mReceiver.getBatteryLevel());
+ }
+}
diff --git a/device_validator/dvlib/build.gradle b/device_validator/dvlib/build.gradle
index d31d5b5..8a0e5ee 100644
--- a/device_validator/dvlib/build.gradle
+++ b/device_validator/dvlib/build.gradle
@@ -1,3 +1,6 @@
+apply plugin: 'java'
+apply plugin: 'distrib'
+
dependencies {
compile project(':common')
testCompile 'junit:junit:3.8.1'
@@ -13,45 +16,9 @@
from 'NOTICE'
}
-uploadArchives {
- repositories {
- mavenDeployer {
- beforeDeployment { MavenDeployment deployment ->
- if (!project.has("release")) {
- throw new StopExecutionException("uploadArchives must be called with the release.gradle init script")
- }
+project.ext.pomName = 'Android Tools dvlib'
+project.ext.pomDesc = 'A Library to manage the Android device database XML files.'
- signing.signPom(deployment)
- }
-
- repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
- authentication(userName: project.ext.sonatypeUsername, password: project.ext.sonatypePassword)
- }
-
- pom.project {
- name 'Android Tools dvlib'
- description 'A Library to manage the Android device database XML files.'
- url 'http://tools.android.com'
- inceptionYear '2007'
-
- licenses {
- license {
- name 'The Apache Software License, Version 2.0'
- url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
- distribution 'repo'
- }
- }
-
- scm {
- url "https://android.googlesource.com/platform/tools/base"
- connection "git://android.googlesource.com/platform/tools/base.git"
- }
- developers {
- developer {
- name 'The Android Open Source Project'
- }
- }
- }
- }
- }
-}
+apply from: '../../baseVersion.gradle'
+apply from: '../../publish.gradle'
+apply from: '../../javadoc.gradle'
diff --git a/device_validator/dvlib/src/main/java/com/android/dvlib/DeviceSchema.java b/device_validator/dvlib/src/main/java/com/android/dvlib/DeviceSchema.java
index 58f3026..965c368 100644
--- a/device_validator/dvlib/src/main/java/com/android/dvlib/DeviceSchema.java
+++ b/device_validator/dvlib/src/main/java/com/android/dvlib/DeviceSchema.java
@@ -180,6 +180,8 @@
public static final String NODE_NAME = "name";
+ public static final String NODE_ID = "id";
+
public static final String NODE_API_LEVEL = "api-level";
public static final String NODE_MANUFACTURER = "manufacturer";
diff --git a/device_validator/dvlib/src/main/resources/com/android/dvlib/devices.xsd b/device_validator/dvlib/src/main/resources/com/android/dvlib/devices.xsd
index bfa915f..34d197a 100644
--- a/device_validator/dvlib/src/main/resources/com/android/dvlib/devices.xsd
+++ b/device_validator/dvlib/src/main/resources/com/android/dvlib/devices.xsd
@@ -49,6 +49,7 @@
<xsd:complexType>
<xsd:sequence>
<xsd:element name="name" type= "xsd:token" />
+ <xsd:element name="id" type= "xsd:token" minOccurs="0" />
<xsd:element name="manufacturer" type= "xsd:token" />
<xsd:element name="meta" type= "c:metaType" minOccurs="0" />
<xsd:element name="hardware" type= "c:hardwareType" />
@@ -464,6 +465,7 @@
<xsd:enumeration value="hdpi" />
<xsd:enumeration value="xhdpi" />
<xsd:enumeration value="xxhdpi" />
+ <xsd:enumeration value="xxxhdpi" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
diff --git a/device_validator/dvlib/src/test/resources/com/android/dvlib/devices.xml b/device_validator/dvlib/src/test/resources/com/android/dvlib/devices.xml
index 6662099..142d426 100644
--- a/device_validator/dvlib/src/test/resources/com/android/dvlib/devices.xml
+++ b/device_validator/dvlib/src/test/resources/com/android/dvlib/devices.xml
@@ -7,6 +7,9 @@
<d:name>
Galaxy Nexus
</d:name>
+ <d:id>
+ galaxy_nexus
+ </d:id>
<d:manufacturer>
Samsung
</d:manufacturer>
diff --git a/draw9patch/build.gradle b/draw9patch/build.gradle
index e8dcc9f..d4e04b1 100644
--- a/draw9patch/build.gradle
+++ b/draw9patch/build.gradle
@@ -1,3 +1,6 @@
+apply plugin: 'java'
+apply plugin: 'distrib'
+
group = 'com.android.tools'
archivesBaseName = 'draw9patch'
@@ -16,3 +19,4 @@
buildDistributionJar.manifest.attributes("Main-Class": "com.android.draw9patch.Application")
+apply from: '../baseVersion.gradle'
\ No newline at end of file
diff --git a/draw9patch/etc/draw9patch.bat b/draw9patch/etc/draw9patch.bat
index b6826fc..6965a36 100755
--- a/draw9patch/etc/draw9patch.bat
+++ b/draw9patch/etc/draw9patch.bat
@@ -30,17 +30,17 @@
if not defined java_exe goto :EOF
set jarfile=draw9patch.jar
-set frameworkdir=
+set frameworkdir=.
set libdir=
-if exist %frameworkdir%%jarfile% goto JarFileOk
- set frameworkdir=lib\
+if exist %frameworkdir%\%jarfile% goto JarFileOk
+ set frameworkdir=lib
-if exist %frameworkdir%%jarfile% goto JarFileOk
- set frameworkdir=..\framework\
+if exist %frameworkdir%\%jarfile% goto JarFileOk
+ set frameworkdir=..\framework
:JarFileOk
-set jarpath=%frameworkdir%%jarfile%
+set jarpath=%frameworkdir%\%jarfile%
-call %java_exe% -Djava.ext.dirs=%frameworkdir% -jar %jarpath% %*
+call "%java_exe%" "-Djava.ext.dirs=%frameworkdir%" -jar %jarpath% %*
diff --git a/draw9patch/src/main/java/com/android/draw9patch/graphics/GraphicsUtilities.java b/draw9patch/src/main/java/com/android/draw9patch/graphics/GraphicsUtilities.java
index c6c182c..1e65907 100644
--- a/draw9patch/src/main/java/com/android/draw9patch/graphics/GraphicsUtilities.java
+++ b/draw9patch/src/main/java/com/android/draw9patch/graphics/GraphicsUtilities.java
@@ -18,6 +18,7 @@
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
import java.awt.image.Raster;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
@@ -29,6 +30,9 @@
public class GraphicsUtilities {
public static BufferedImage loadCompatibleImage(URL resource) throws IOException {
BufferedImage image = ImageIO.read(resource);
+ if (image == null) {
+ return null;
+ }
return toCompatibleImage(image);
}
@@ -41,7 +45,8 @@
return image;
}
- if (image.getColorModel().equals(getGraphicsConfiguration().getColorModel())) {
+ ColorModel colorModel = image.getColorModel();
+ if (colorModel != null && colorModel.equals(getGraphicsConfiguration().getColorModel())) {
return image;
}
diff --git a/draw9patch/src/main/java/com/android/draw9patch/ui/ImageEditorPanel.java b/draw9patch/src/main/java/com/android/draw9patch/ui/ImageEditorPanel.java
index d37727d..60e5671 100644
--- a/draw9patch/src/main/java/com/android/draw9patch/ui/ImageEditorPanel.java
+++ b/draw9patch/src/main/java/com/android/draw9patch/ui/ImageEditorPanel.java
@@ -375,4 +375,10 @@
RenderedImage getImage() {
return image;
}
+
+ public void dispose() {
+ if (viewer != null) {
+ viewer.dispose();
+ }
+ }
}
diff --git a/draw9patch/src/main/java/com/android/draw9patch/ui/ImageViewer.java b/draw9patch/src/main/java/com/android/draw9patch/ui/ImageViewer.java
index 76fec3e..7db3892 100644
--- a/draw9patch/src/main/java/com/android/draw9patch/ui/ImageViewer.java
+++ b/draw9patch/src/main/java/com/android/draw9patch/ui/ImageViewer.java
@@ -89,6 +89,8 @@
/** Maximum zoom level for the 9patch image. */
public static final int MAX_ZOOM = 16;
+ private final AWTEventListener mAwtKeyEventListener;
+
/** Current 9patch zoom level, {@link #MIN_ZOOM} <= zoom <= {@link #MAX_ZOOM} */
private int zoom = DEFAULT_ZOOM;
private boolean showPatches;
@@ -176,6 +178,12 @@
helpLabel = new JLabel("Press Shift to erase pixels."
+ " Press Control to draw layout bounds");
helpLabel.putClientProperty("JComponent.sizeVariant", "small");
+ // Labels are not opaque by default, as a result, if there is not enough space,
+ // the label will be painted over the button we add below. However, we still want the same
+ // background as the panel, so we explicitly set that background as well
+ // https://code.google.com/p/android/issues/detail?id=62576
+ helpLabel.setOpaque(true);
+ helpLabel.setBackground(HELP_COLOR);
helpPanel.add(helpLabel, BorderLayout.WEST);
checkButton = new JButton("Show bad patches");
checkButton.putClientProperty("JComponent.sizeVariant", "small");
@@ -277,11 +285,14 @@
}
});
- Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
+ mAwtKeyEventListener = new AWTEventListener() {
+ @Override
public void eventDispatched(AWTEvent event) {
enableEraseMode((KeyEvent) event);
}
- }, AWTEvent.KEY_EVENT_MASK);
+ };
+ Toolkit.getDefaultToolkit()
+ .addAWTEventListener(mAwtKeyEventListener, AWTEvent.KEY_EVENT_MASK);
checkButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
@@ -1189,4 +1200,8 @@
p.patchesUpdated();
}
}
+
+ public void dispose() {
+ Toolkit.getDefaultToolkit().removeAWTEventListener(mAwtKeyEventListener);
+ }
}
\ No newline at end of file
diff --git a/draw9patch/src/main/java/com/android/draw9patch/ui/MainFrame.java b/draw9patch/src/main/java/com/android/draw9patch/ui/MainFrame.java
index 3f02085..91bc612 100644
--- a/draw9patch/src/main/java/com/android/draw9patch/ui/MainFrame.java
+++ b/draw9patch/src/main/java/com/android/draw9patch/ui/MainFrame.java
@@ -119,6 +119,9 @@
}
void showImageEditor(BufferedImage image, String name) {
+ if (imageEditor != null) {
+ imageEditor.dispose();
+ }
getContentPane().removeAll();
imageEditor = new ImageEditorPanel(this, image, name);
add(imageEditor);
diff --git a/draw9patch/src/main/java/com/android/draw9patch/ui/PatchInfo.java b/draw9patch/src/main/java/com/android/draw9patch/ui/PatchInfo.java
index 0cca67b..2eb824f 100644
--- a/draw9patch/src/main/java/com/android/draw9patch/ui/PatchInfo.java
+++ b/draw9patch/src/main/java/com/android/draw9patch/ui/PatchInfo.java
@@ -214,7 +214,7 @@
fixed.add(new Pair<Integer>(lastIndex, pixels.length - 1));
}
- if (patches.size() == 0) {
+ if (patches.isEmpty()) {
patches.add(new Pair<Integer>(1, pixels.length - 1));
startWithPatch = true;
fixed.clear();
diff --git a/draw9patch/src/main/java/com/android/draw9patch/ui/StretchesViewer.java b/draw9patch/src/main/java/com/android/draw9patch/ui/StretchesViewer.java
index d63fd97..f799a18 100644
--- a/draw9patch/src/main/java/com/android/draw9patch/ui/StretchesViewer.java
+++ b/draw9patch/src/main/java/com/android/draw9patch/ui/StretchesViewer.java
@@ -237,7 +237,7 @@
x = 0;
y = 0;
- if (patchInfo.patches.size() == 0) {
+ if (patchInfo.patches.isEmpty()) {
g.drawImage(image, 0, 0, scaledWidth, scaledHeight, null);
g2.dispose();
return;
diff --git a/files/devices.xml b/files/devices.xml
deleted file mode 100644
index e729986..0000000
--- a/files/devices.xml
+++ /dev/null
@@ -1,595 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<d:devices xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:d="http://schemas.android.com/sdk/devices/1">
-
- <d:device>
- <d:name>Nexus One</d:name>
- <d:manufacturer>Google</d:manufacturer>
- <d:hardware>
- <d:screen>
- <d:screen-size>normal</d:screen-size>
- <d:diagonal-length>3.7</d:diagonal-length>
- <d:pixel-density>hdpi</d:pixel-density>
- <d:screen-ratio>long</d:screen-ratio>
- <d:dimensions>
- <d:x-dimension>480</d:x-dimension>
- <d:y-dimension>800</d:y-dimension>
- </d:dimensions>
- <d:xdpi>254</d:xdpi>
- <d:ydpi>254</d:ydpi>
- <d:touch>
- <d:multitouch>basic</d:multitouch>
- <d:mechanism>finger</d:mechanism>
- <d:screen-type>capacitive</d:screen-type>
- </d:touch>
- </d:screen>
- <d:networking>
- Wifi
- Bluetooth
- </d:networking>
- <d:sensors>
- Accelerometer
- Compass
- GPS
- LightSensor
- ProximitySensor
- </d:sensors>
- <d:mic>true</d:mic>
- <d:camera>
- <d:location>back</d:location>
- <d:autofocus>true</d:autofocus>
- <d:flash>true</d:flash>
- </d:camera>
- <d:keyboard>nokeys</d:keyboard>
- <d:nav>trackball</d:nav>
- <d:ram unit="MiB">512</d:ram>
- <d:buttons>hard</d:buttons>
- <d:internal-storage unit="MiB">503</d:internal-storage>
- <d:removable-storage unit="MiB">0</d:removable-storage>
- <d:cpu>Qualcomm Scorpion</d:cpu>
- <d:gpu>Qualcomm Adreno 200</d:gpu>
- <d:abi>
- armeabi-v7a
- armeabi
- </d:abi>
- <d:dock> </d:dock>
- <d:power-type>plugged-in</d:power-type>
- </d:hardware>
- <d:software>
- <d:api-level>7-10</d:api-level>
- <d:live-wallpaper-support>true</d:live-wallpaper-support>
- <d:bluetooth-profiles> </d:bluetooth-profiles>
- <d:gl-version>2.0</d:gl-version>
- <d:gl-extensions>
- </d:gl-extensions>
- <d:status-bar>true</d:status-bar>
- </d:software>
- <d:state name="Portrait" default="true">
- <d:description>The phone in portrait view</d:description>
- <d:screen-orientation>port</d:screen-orientation>
- <d:keyboard-state>keyssoft</d:keyboard-state>
- <d:nav-state>nonav</d:nav-state>
- </d:state>
- <d:state name="Landscape">
- <d:description>The phone in landscape view</d:description>
- <d:screen-orientation>land</d:screen-orientation>
- <d:keyboard-state>keyssoft</d:keyboard-state>
- <d:nav-state>nonav</d:nav-state>
- </d:state>
- </d:device>
- <d:device>
- <d:name>Nexus S</d:name>
- <d:manufacturer>Google</d:manufacturer>
- <d:hardware>
- <d:screen>
- <d:screen-size>normal</d:screen-size>
- <d:diagonal-length>4</d:diagonal-length>
- <d:pixel-density>hdpi</d:pixel-density>
- <d:screen-ratio>long</d:screen-ratio>
- <d:dimensions>
- <d:x-dimension>480</d:x-dimension>
- <d:y-dimension>800</d:y-dimension>
- </d:dimensions>
- <d:xdpi>235</d:xdpi>
- <d:ydpi>235</d:ydpi>
- <d:touch>
- <d:multitouch>jazz-hands</d:multitouch>
- <d:mechanism>finger</d:mechanism>
- <d:screen-type>capacitive</d:screen-type>
- </d:touch>
- </d:screen>
- <d:networking>
- Wifi
- Bluetooth
- NFC
- </d:networking>
- <d:sensors>
- Accelerometer
- Compass
- GPS
- Gyroscope
- LightSensor
- ProximitySensor
- </d:sensors>
- <d:mic>true</d:mic>
- <d:camera>
- <d:location>back</d:location>
- <d:autofocus>true</d:autofocus>
- <d:flash>true</d:flash>
- </d:camera>
- <d:camera>
- <d:location>front</d:location>
- <d:autofocus>false</d:autofocus>
- <d:flash>false</d:flash>
- </d:camera>
- <d:keyboard>nokeys</d:keyboard>
- <d:nav>nonav</d:nav>
- <d:ram unit="KiB">351428</d:ram>
- <d:buttons>hard</d:buttons>
- <d:internal-storage unit="MiB">503</d:internal-storage>
- <d:removable-storage unit="MiB">0</d:removable-storage>
- <d:cpu>Samsung Exynos 3110</d:cpu>
- <d:gpu>PowerVR SGX 540</d:gpu>
- <d:abi>
- armeabi-v7a
- armeabi
- </d:abi>
- <d:dock> </d:dock>
- <d:power-type>plugged-in</d:power-type>
- </d:hardware>
- <d:software>
- <d:api-level>9-16</d:api-level>
- <d:live-wallpaper-support>true</d:live-wallpaper-support>
- <d:bluetooth-profiles> </d:bluetooth-profiles>
- <d:gl-version>2.0</d:gl-version>
- <d:gl-extensions>
- GL_EXT_debug_marker
- GL_OES_rgb8_rgba8
- GL_OES_depth24
- GL_OES_vertex_half_float
- GL_OES_texture_float
- GL_OES_texture_half_float
- GL_OES_element_index_uint
- GL_OES_mapbuffer
- GL_OES_fragment_precision_high
- GL_OES_compressed_ETC1_RGB8_texture
- GL_OES_EGL_image
- GL_OES_EGL_image_external
- GL_OES_required_internalformat
- GL_OES_depth_texture
- GL_OES_get_program_binary
- GL_OES_packed_depth_stencil
- GL_OES_standard_derivatives
- GL_OES_vertex_array_object
- GL_OES_egl_sync
- GL_EXT_multi_draw_arrays
- GL_EXT_texture_format_BGRA8888
- GL_EXT_discard_framebuffer
- GL_EXT_shader_texture_lod
- GL_IMG_shader_binary
- GL_IMG_texture_compression_pvrtc
- GL_IMG_texture_npot
- GL_IMG_texture_format_BGRA8888
- GL_IMG_read_format
- GL_IMG_program_binary
- GL_IMG_multisampled_render_to_texture
- </d:gl-extensions>
- <d:status-bar>true</d:status-bar>
- </d:software>
- <d:state name="Portrait" default="true">
- <d:description>The phone in portrait view</d:description>
- <d:screen-orientation>port</d:screen-orientation>
- <d:keyboard-state>keyssoft</d:keyboard-state>
- <d:nav-state>nonav</d:nav-state>
- </d:state>
- <d:state name="Landscape">
- <d:description>The phone in landscape view</d:description>
- <d:screen-orientation>land</d:screen-orientation>
- <d:keyboard-state>keyssoft</d:keyboard-state>
- <d:nav-state>nonav</d:nav-state>
- </d:state>
- </d:device>
-
- <d:device>
- <d:name>Galaxy Nexus</d:name>
- <d:manufacturer>Google</d:manufacturer>
- <d:hardware>
- <d:screen>
- <d:screen-size>normal</d:screen-size>
- <d:diagonal-length>4.65</d:diagonal-length> <!-- In inches -->
- <d:pixel-density>xhdpi</d:pixel-density>
- <d:screen-ratio>long</d:screen-ratio>
- <d:dimensions>
- <d:x-dimension>720</d:x-dimension>
- <d:y-dimension>1280</d:y-dimension>
- </d:dimensions>
- <d:xdpi>316</d:xdpi>
- <d:ydpi>316</d:ydpi>
- <d:touch>
- <d:multitouch>jazz-hands</d:multitouch>
- <d:mechanism>finger</d:mechanism>
- <d:screen-type>capacitive</d:screen-type>
- </d:touch>
- </d:screen>
- <d:networking>
- Bluetooth
- Wifi
- NFC
- </d:networking>
- <d:sensors>
- Accelerometer
- Barometer
- Gyroscope
- Compass
- GPS
- ProximitySensor
- </d:sensors>
- <d:mic>true</d:mic>
- <d:camera>
- <d:location>front</d:location>
- <d:autofocus>true</d:autofocus>
- <d:flash>false</d:flash>
- </d:camera>
- <d:camera>
- <d:location>back</d:location>
- <d:autofocus>true</d:autofocus>
- <d:flash>true</d:flash>
- </d:camera>
- <d:keyboard>nokeys</d:keyboard>
- <d:nav>nonav</d:nav>
- <d:ram unit="GiB">1</d:ram>
- <d:buttons>soft</d:buttons>
- <d:internal-storage unit="GiB">16</d:internal-storage>
- <d:removable-storage unit="KiB"></d:removable-storage>
- <d:cpu>OMAP 4460</d:cpu> <!-- cpu type (Tegra3) freeform -->
- <d:gpu>PowerVR SGX540</d:gpu>
- <d:abi>
- armeabi
- armeabi-v7a
- </d:abi>
- <!--dock (car, desk, tv, none)-->
- <d:dock>
- </d:dock>
- <!-- plugged in (never, charge, always) -->
- <d:power-type>battery</d:power-type>
- </d:hardware>
- <d:software>
- <d:api-level>14-</d:api-level>
- <d:live-wallpaper-support>true</d:live-wallpaper-support>
- <d:bluetooth-profiles>
- HSP
- HFP
- SPP
- A2DP
- AVRCP
- OPP
- PBAP
- GAVDP
- AVDTP
- HID
- HDP
- PAN
- </d:bluetooth-profiles>
- <d:gl-version>2.0</d:gl-version>
- <!--
- These can be gotten via
- javax.microedition.khronos.opengles.GL10.glGetString(GL10.GL_EXTENSIONS);
- -->
- <d:gl-extensions>
- GL_EXT_discard_framebuffer
- GL_EXT_multi_draw_arrays
- GL_EXT_shader_texture_lod
- GL_EXT_texture_format_BGRA8888
- GL_IMG_multisampled_render_to_texture
- GL_IMG_program_binary
- GL_IMG_read_format
- GL_IMG_shader_binary
- GL_IMG_texture_compression_pvrtc
- GL_IMG_texture_format_BGRA8888
- GL_IMG_texture_npot
- GL_OES_compressed_ETC1_RGB8_texture
- GL_OES_depth_texture
- GL_OES_depth24
- GL_OES_EGL_image
- GL_OES_EGL_image_external
- GL_OES_egl_sync
- GL_OES_element_index_uint
- GL_OES_fragment_precision_high
- GL_OES_get_program_binary
- GL_OES_mapbuffer
- GL_OES_packed_depth_stencil
- GL_OES_required_internalformat
- GL_OES_rgb8_rgba8
- GL_OES_standard_derivatives
- GL_OES_texture_float
- GL_OES_texture_half_float
- GL_OES_vertex_array_object
- GL_OES_vertex_half_float
- </d:gl-extensions>
- <d:status-bar>true</d:status-bar>
- </d:software>
- <d:state name="Portrait" default="true">
- <d:description>The phone in portrait view</d:description>
- <d:screen-orientation>port</d:screen-orientation>
- <d:keyboard-state>keyssoft</d:keyboard-state>
- <d:nav-state>nonav</d:nav-state>
- </d:state>
- <d:state name="Landscape">
- <d:description>The phone in landscape view</d:description>
- <d:screen-orientation>land</d:screen-orientation>
- <d:keyboard-state>keyssoft</d:keyboard-state>
- <d:nav-state>nonav</d:nav-state>
- </d:state>
- </d:device>
- <d:device>
- <d:name>Nexus 7</d:name>
- <d:manufacturer>Google</d:manufacturer>
- <d:hardware>
- <d:screen>
- <d:screen-size>large</d:screen-size>
- <d:diagonal-length>7.27</d:diagonal-length>
- <d:pixel-density>tvdpi</d:pixel-density>
- <d:screen-ratio>notlong</d:screen-ratio>
- <d:dimensions>
- <d:x-dimension>800</d:x-dimension>
- <d:y-dimension>1280</d:y-dimension>
- </d:dimensions>
- <d:xdpi>195</d:xdpi>
- <d:ydpi>200</d:ydpi>
- <d:touch>
- <d:multitouch>jazz-hands</d:multitouch>
- <d:mechanism>finger</d:mechanism>
- <d:screen-type>capacitive</d:screen-type>
- </d:touch>
- </d:screen>
- <d:networking>
- Wifi
- Bluetooth
- NFC
- </d:networking>
- <d:sensors>
- Accelerometer
- Compass
- GPS
- Gyroscope
- LightSensor
- </d:sensors>
- <d:mic>true</d:mic>
- <d:camera>
- <d:location>front</d:location>
- <d:autofocus>false</d:autofocus>
- <d:flash>false</d:flash>
- </d:camera>
- <d:keyboard>nokeys</d:keyboard>
- <d:nav>nonav</d:nav>
- <d:ram unit="GiB">1</d:ram>
- <d:buttons>soft</d:buttons>
- <d:internal-storage unit="GiB">8</d:internal-storage>
- <d:removable-storage unit="MiB"> </d:removable-storage>
- <d:cpu> Tegra3 </d:cpu>
- <d:gpu> Tegra3 </d:gpu>
- <d:abi>
- armeabi-v7a
- armeabi
- </d:abi>
- <d:dock> </d:dock>
- <d:power-type>battery</d:power-type>
- </d:hardware>
-
- <d:software>
- <d:api-level>16</d:api-level>
- <d:live-wallpaper-support>true</d:live-wallpaper-support>
- <d:bluetooth-profiles> </d:bluetooth-profiles>
- <d:gl-version>2.0</d:gl-version>
- <d:gl-extensions> </d:gl-extensions>
- <d:status-bar>true</d:status-bar>
- </d:software>
-
- <d:state name="Portrait" default="true">
- <d:description>The phone in portrait view</d:description>
- <d:screen-orientation>port</d:screen-orientation>
- <d:keyboard-state>keyssoft</d:keyboard-state>
- <d:nav-state>nonav</d:nav-state>
- </d:state>
- <d:state name="Landscape">
- <d:description>The phone in landscape view</d:description>
- <d:screen-orientation>land</d:screen-orientation>
- <d:keyboard-state>keyssoft</d:keyboard-state>
- <d:nav-state>nonav</d:nav-state>
- </d:state>
-
- </d:device>
-
- <d:device>
- <d:name>Nexus 4</d:name>
- <d:manufacturer>Google</d:manufacturer>
- <d:hardware>
- <d:screen>
- <d:screen-size>normal</d:screen-size>
- <d:diagonal-length>4.7</d:diagonal-length>
- <d:pixel-density>xhdpi</d:pixel-density>
- <d:screen-ratio>notlong</d:screen-ratio>
- <d:dimensions>
- <d:x-dimension>768</d:x-dimension>
- <d:y-dimension>1280</d:y-dimension>
- </d:dimensions>
- <d:xdpi>320</d:xdpi>
- <d:ydpi>320</d:ydpi>
- <d:touch>
- <d:multitouch>jazz-hands</d:multitouch>
- <d:mechanism>finger</d:mechanism>
- <d:screen-type>capacitive</d:screen-type>
- </d:touch>
- </d:screen>
- <d:networking>
- Wifi
- Bluetooth
- NFC
- </d:networking>
- <d:sensors>
- Accelerometer
- Barometer
- Compass
- GPS
- Gyroscope
- LightSensor
- ProximitySensor
- </d:sensors>
- <d:mic>true</d:mic>
- <d:camera>
- <d:location>back</d:location>
- <d:autofocus>true</d:autofocus>
- <d:flash>true</d:flash>
- </d:camera>
- <d:camera>
- <d:location>front</d:location>
- <d:autofocus>false</d:autofocus>
- <d:flash>false</d:flash>
- </d:camera>
- <d:keyboard>nokeys</d:keyboard>
- <d:nav>nonav</d:nav>
- <d:ram unit="KiB">1953125</d:ram>
- <d:buttons>soft</d:buttons>
- <d:internal-storage unit="KiB">7811891</d:internal-storage>
- <d:removable-storage unit="MiB"></d:removable-storage>
- <d:cpu>Qualcomm Snapdragon S4 Pro</d:cpu>
- <d:gpu>Adreno 320</d:gpu>
- <d:abi>
- armeabi-v7a
- armeabi
- </d:abi>
- <d:dock></d:dock>
- <d:power-type>battery</d:power-type>
- </d:hardware>
- <d:software>
- <d:api-level>16</d:api-level>
- <d:live-wallpaper-support>true</d:live-wallpaper-support>
- <d:bluetooth-profiles></d:bluetooth-profiles>
- <d:gl-version>2.0</d:gl-version>
- <d:gl-extensions>GL_EXT_debug_marker GL_AMD_compressed_ATC_texture
- GL_AMD_performance_monitor GL_AMD_program_binary_Z400 GL_EXT_robustness
- GL_EXT_texture_format_BGRA8888 GL_EXT_texture_type_2_10_10_10_REV GL_NV_fence
- GL_OES_compressed_ETC1_RGB8_texture GL_OES_depth_texture GL_OES_depth24
- GL_OES_EGL_image GL_OES_EGL_image_external GL_OES_element_index_uint
- GL_OES_fbo_render_mipmap GL_OES_fragment_precision_high GL_OES_get_program_binary
- GL_OES_packed_depth_stencil GL_OES_rgb8_rgba8 GL_OES_standard_derivatives
- GL_OES_texture_3D GL_OES_texture_float GL_OES_texture_half_float
- GL_OES_texture_half_float_linear GL_OES_texture_npot GL_OES_vertex_half_float
- GL_OES_vertex_type_10_10_10_2 GL_OES_vertex_array_object GL_QCOM_alpha_test
- GL_QCOM_binning_control GL_QCOM_driver_control GL_QCOM_perfmon_global_mode
- GL_QCOM_extended_get GL_QCOM_extended_get2 GL_QCOM_tiled_rendering
- GL_QCOM_writeonly_rendering GL_EXT_sRGB
- </d:gl-extensions>
- <d:status-bar>true</d:status-bar>
- </d:software>
- <d:state name="Portrait" default="true">
- <d:description>The phone in portrait view</d:description>
- <d:screen-orientation>port</d:screen-orientation>
- <d:keyboard-state>keyssoft</d:keyboard-state>
- <d:nav-state>nonav</d:nav-state>
- </d:state>
- <d:state name="Landscape">
- <d:description>The phone in landscape view</d:description>
- <d:screen-orientation>land</d:screen-orientation>
- <d:keyboard-state>keyssoft</d:keyboard-state>
- <d:nav-state>nonav</d:nav-state>
- </d:state>
- </d:device>
-
- <d:device>
- <d:name>Nexus 10</d:name>
- <d:manufacturer>Google</d:manufacturer>
- <d:hardware>
- <d:screen>
- <d:screen-size>xlarge</d:screen-size>
- <d:diagonal-length>10.055</d:diagonal-length>
- <d:pixel-density>xhdpi</d:pixel-density>
- <d:screen-ratio>notlong</d:screen-ratio>
- <d:dimensions>
- <d:x-dimension>2560</d:x-dimension>
- <d:y-dimension>1600</d:y-dimension>
- </d:dimensions>
- <d:xdpi>300</d:xdpi>
- <d:ydpi>300</d:ydpi>
- <d:touch>
- <d:multitouch>jazz-hands</d:multitouch>
- <d:mechanism>finger</d:mechanism>
- <d:screen-type>capacitive</d:screen-type>
- </d:touch>
- </d:screen>
- <d:networking>
- Wifi
- Bluetooth
- NFC
- </d:networking>
- <d:sensors>
- Accelerometer
- Barometer
- Compass
- GPS
- Gyroscope
- LightSensor
- </d:sensors>
- <d:mic>true</d:mic>
- <d:camera>
- <d:location>back</d:location>
- <d:autofocus>true</d:autofocus>
- <d:flash>true</d:flash>
- </d:camera>
- <d:camera>
- <d:location>front</d:location>
- <d:autofocus>false</d:autofocus>
- <d:flash>false</d:flash>
- </d:camera>
- <d:keyboard>nokeys</d:keyboard>
- <d:nav>nonav</d:nav>
- <d:ram unit="KiB">1953125</d:ram>
- <d:buttons>soft</d:buttons>
- <d:internal-storage unit="KiB">15623782</d:internal-storage>
- <d:removable-storage unit="MiB"></d:removable-storage>
- <d:cpu>Dual-core A15</d:cpu>
- <d:gpu>Quad-core Mali T604</d:gpu>
- <d:abi>
- armeabi-v7a
- armeabi
- </d:abi>
- <d:dock></d:dock>
- <d:power-type>battery</d:power-type>
- </d:hardware>
- <d:software>
- <d:api-level>16</d:api-level>
- <d:live-wallpaper-support>true</d:live-wallpaper-support>
- <d:bluetooth-profiles></d:bluetooth-profiles>
- <d:gl-version>2.0</d:gl-version>
- <d:gl-extensions>GL_EXT_debug_marker GL_ARM_rgba8 GL_ARM_mali_shader_binary
- GL_OES_depth24 GL_OES_depth_texture GL_OES_depth_texture_cube_map
- GL_OES_packed_depth_stencil GL_OES_rgb8_rgba8 GL_EXT_read_format_bgra
- GL_OES_compressed_paletted_texture GL_OES_compressed_ETC1_RGB8_texture
- GL_OES_standard_derivatives GL_OES_EGL_image GL_OES_EGL_image_external
- GL_OES_EGL_sync GL_OES_texture_npot GL_OES_vertex_half_float
- GL_OES_required_internalformat GL_OES_vertex_array_object GL_OES_mapbuffer
- GL_EXT_texture_format_BGRA8888 GL_EXT_texture_rg GL_EXT_texture_type_2_10_10_10_REV
- GL_OES_fbo_render_mipmap GL_OES_element_index_uint GL_EXT_shadow_samplers
- GL_EXT_occlusion_query_boolean GL_EXT_blend_minmax GL_EXT_discard_framebuffer
- GL_OES_get_program_binary GL_OES_texture_3D GL_EXT_texture_storage
- GL_EXT_multisampled_render_to_texture GL_OES_surfaceless_context
- GL_ARM_mali_program_binary
- </d:gl-extensions>
- <d:status-bar>true</d:status-bar>
- </d:software>
- <d:state name="Portrait">
- <d:description>The phone in portrait view</d:description>
- <d:screen-orientation>port</d:screen-orientation>
- <d:keyboard-state>keyssoft</d:keyboard-state>
- <d:nav-state>nonav</d:nav-state>
- </d:state>
- <d:state name="Landscape" default="true">
- <d:description>The phone in landscape view</d:description>
- <d:screen-orientation>land</d:screen-orientation>
- <d:keyboard-state>keyssoft</d:keyboard-state>
- <d:nav-state>nonav</d:nav-state>
- </d:state>
- </d:device>
-
-</d:devices>
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..f66ad4d
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1 @@
+org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=1024m
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 169f873..71afaf0 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=../../../external/gradle/gradle-1.6-all.zip
+distributionUrl=../../../external/gradle/gradle-1.9-bin.zip
diff --git a/javadoc.gradle b/javadoc.gradle
new file mode 100644
index 0000000..720844a
--- /dev/null
+++ b/javadoc.gradle
@@ -0,0 +1,16 @@
+javadoc {
+ exclude "**/internal/**"
+ options.memberLevel = org.gradle.external.javadoc.JavadocMemberLevel.PROTECTED
+
+ title project.ext.pomName
+}
+
+task javadocJar(type: Jar, dependsOn:javadoc) {
+ classifier 'javadoc'
+ from javadoc.destinationDir
+}
+
+// add javadoc jar tasks as artifacts
+artifacts {
+ archives javadocJar
+}
diff --git a/jobb/build.gradle b/jobb/build.gradle
index 1e150ed..7541707 100644
--- a/jobb/build.gradle
+++ b/jobb/build.gradle
@@ -1,3 +1,6 @@
+apply plugin: 'java'
+apply plugin: 'distrib'
+
dependencies {
compile project(':fat32lib')
}
@@ -11,3 +14,5 @@
shipping {
launcherScripts = ['etc/jobb', 'etc/jobb.bat']
}
+
+apply from: '../baseVersion.gradle'
\ No newline at end of file
diff --git a/jobb/etc/jobb.bat b/jobb/etc/jobb.bat
index a5043c1..0547153 100755
--- a/jobb/etc/jobb.bat
+++ b/jobb/etc/jobb.bat
@@ -33,19 +33,19 @@
if not defined java_exe goto :EOF
set jarfile=jobb.jar
-set frameworkdir=
+set frameworkdir=.
set libdir=
-if exist %frameworkdir%%jarfile% goto JarFileOk
- set frameworkdir=lib\
+if exist %frameworkdir%\%jarfile% goto JarFileOk
+ set frameworkdir=lib
-if exist %frameworkdir%%jarfile% goto JarFileOk
- set frameworkdir=..\framework\
+if exist %frameworkdir%\%jarfile% goto JarFileOk
+ set frameworkdir=..\framework
:JarFileOk
-set jarpath=%frameworkdir%%jarfile%;%frameworkdir%libfat32.jar
+set jarpath=%frameworkdir%\%jarfile%;%frameworkdir%\libfat32.jar
-call %java_exe% %java_debug% -classpath "%jarpath%" com.android.jobb.Main %*
+call "%java_exe%" %java_debug% -classpath "%jarpath%" com.android.jobb.Main %*
diff --git a/layoutlib-api/build.gradle b/layoutlib-api/build.gradle
index 638beeb..9dc4779 100644
--- a/layoutlib-api/build.gradle
+++ b/layoutlib-api/build.gradle
@@ -1,3 +1,6 @@
+apply plugin: 'java'
+apply plugin: 'distrib'
+
group = 'com.android.tools.layoutlib'
archivesBaseName = 'layoutlib-api'
@@ -12,45 +15,10 @@
from 'NOTICE'
}
-uploadArchives {
- repositories {
- mavenDeployer {
- beforeDeployment { MavenDeployment deployment ->
- if (!project.has("release")) {
- throw new StopExecutionException("uploadArchives must be called with the release.gradle init script")
- }
+project.ext.pomName = 'Android Tools layoutlib-api'
+project.ext.pomDesc = 'Library to use the rendering library for Android layouts: layoutlib'
- signing.signPom(deployment)
- }
+apply from: '../baseVersion.gradle'
+apply from: '../publish.gradle'
+apply from: '../javadoc.gradle'
- repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
- authentication(userName: project.ext.sonatypeUsername, password: project.ext.sonatypePassword)
- }
-
- pom.project {
- name 'Android Tools layoutlib-api'
- description 'Library to use the rendering library for Android layouts: layoutlib'
- url 'http://tools.android.com'
- inceptionYear '2007'
-
- licenses {
- license {
- name 'The Apache Software License, Version 2.0'
- url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
- distribution 'repo'
- }
- }
-
- scm {
- url "https://android.googlesource.com/platform/tools/base"
- connection "git://android.googlesource.com/platform/tools/base.git"
- }
- developers {
- developer {
- name 'The Android Open Source Project'
- }
- }
- }
- }
- }
-}
diff --git a/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/Bridge.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/Bridge.java
index 2b77112..0f35b96 100644
--- a/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/Bridge.java
+++ b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/Bridge.java
@@ -32,7 +32,7 @@
*/
public abstract class Bridge {
- public static final int API_CURRENT = 9;
+ public static final int API_CURRENT = 10;
/**
* Returns the API level of the layout library.
@@ -144,6 +144,15 @@
}
/**
+ * Returns true if the character orientation of the locale is right to left.
+ * @param locale The locale formatted as language-region
+ * @return true if the locale is right to left.
+ */
+ public boolean isRtl(String locale) {
+ return false;
+ }
+
+ /**
* Utility method returning the baseline value for a given view object. This basically returns
* View.getBaseline().
*
diff --git a/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/Capability.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/Capability.java
index 66b11ce..f1c0113 100644
--- a/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/Capability.java
+++ b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/Capability.java
@@ -65,5 +65,9 @@
/**
* Ability to properly resize nine-patch assets.
*/
- FIXED_SCALABLE_NINE_PATCH
+ FIXED_SCALABLE_NINE_PATCH,
+ /**
+ * Ability to render RTL layouts.
+ */
+ RTL
}
diff --git a/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/DeclareStyleableResourceValue.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/DeclareStyleableResourceValue.java
index a8f269f..2b42908 100644
--- a/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/DeclareStyleableResourceValue.java
+++ b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/DeclareStyleableResourceValue.java
@@ -18,7 +18,7 @@
import com.android.resources.ResourceType;
-import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.Map;
/**
@@ -63,7 +63,8 @@
public void addValue(AttrResourceValue attr) {
if (mAttrMap == null) {
- mAttrMap = new HashMap<String, AttrResourceValue>();
+ // Preserve insertion order. This order affects the int[] indices for styleables.
+ mAttrMap = new LinkedHashMap<String, AttrResourceValue>();
}
mAttrMap.put(attr.getName(), attr);
diff --git a/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/LayoutLog.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/LayoutLog.java
index 17244e9..0cf9753 100644
--- a/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/LayoutLog.java
+++ b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/LayoutLog.java
@@ -114,6 +114,11 @@
public static final String TAG_SHADER = "shader";
/**
+ * Fidelity Tag used when an unrecognized format is found for strftime.
+ */
+ public static final String TAG_STRFTIME = "strftime";
+
+ /**
* Fidelity Tag used when a xfermode type is used but is not supported.
*/
public static final String TAG_XFERMODE = "xfermode";
diff --git a/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/RenderParams.java b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/RenderParams.java
index 50ed766..8592128 100644
--- a/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/RenderParams.java
+++ b/layoutlib-api/src/main/java/com/android/ide/common/rendering/api/RenderParams.java
@@ -46,6 +46,7 @@
private String mAppLabel = null;
private String mLocale = null;
private boolean mForceNoDecor;
+ private boolean mSupportsRtl;
/**
*
@@ -95,6 +96,7 @@
mAppLabel = params.mAppLabel;
mLocale = params.mLocale;
mForceNoDecor = params.mForceNoDecor;
+ mSupportsRtl = params.mSupportsRtl;
}
public void setOverrideBgColor(int color) {
@@ -126,6 +128,10 @@
mForceNoDecor = true;
}
+ public void setRtlSupport(boolean supportsRtl) {
+ mSupportsRtl = supportsRtl;
+ }
+
public Object getProjectKey() {
return mProjectKey;
}
@@ -233,4 +239,8 @@
public boolean isForceNoDecor() {
return mForceNoDecor;
}
+
+ public boolean isRtlSupported() {
+ return mSupportsRtl;
+ }
}
diff --git a/layoutlib-api/src/main/java/com/android/resources/Density.java b/layoutlib-api/src/main/java/com/android/resources/Density.java
index 1a49c25..e6f3cb3 100644
--- a/layoutlib-api/src/main/java/com/android/resources/Density.java
+++ b/layoutlib-api/src/main/java/com/android/resources/Density.java
@@ -23,6 +23,7 @@
* as well as other places needing to know the density values.
*/
public enum Density implements ResourceEnum {
+ XXXHIGH("xxxhdpi", "XXX-High Density", 640, 18), //$NON-NLS-1$
XXHIGH("xxhdpi", "XX-High Density", 480, 16), //$NON-NLS-1$
XHIGH("xhdpi", "X-High Density", 320, 8), //$NON-NLS-1$
HIGH("hdpi", "High Density", 240, 4), //$NON-NLS-1$
diff --git a/layoutlib-api/src/main/java/com/android/resources/FolderTypeRelationship.java b/layoutlib-api/src/main/java/com/android/resources/FolderTypeRelationship.java
index d174940..a951056 100644
--- a/layoutlib-api/src/main/java/com/android/resources/FolderTypeRelationship.java
+++ b/layoutlib-api/src/main/java/com/android/resources/FolderTypeRelationship.java
@@ -63,6 +63,7 @@
add(ResourceType.STRING, ResourceFolderType.VALUES);
add(ResourceType.STYLE, ResourceFolderType.VALUES);
add(ResourceType.STYLEABLE, ResourceFolderType.VALUES);
+ add(ResourceType.TRANSITION, ResourceFolderType.TRANSITION);
add(ResourceType.XML, ResourceFolderType.XML);
makeSafe();
diff --git a/layoutlib-api/src/main/java/com/android/resources/ResourceConstants.java b/layoutlib-api/src/main/java/com/android/resources/ResourceConstants.java
index 4e92e3c..7d73f70 100644
--- a/layoutlib-api/src/main/java/com/android/resources/ResourceConstants.java
+++ b/layoutlib-api/src/main/java/com/android/resources/ResourceConstants.java
@@ -43,6 +43,8 @@
public static final String FD_RES_XML = "xml"; //$NON-NLS-1$
/** Default raw resource folder name, i.e. "raw" */
public static final String FD_RES_RAW = "raw"; //$NON-NLS-1$
+ /** Default transition resource folder name, i.e. "transition" */
+ public static final String FD_RES_TRANSITION = "transition"; //$NON-NLS-1$
/** Separator between the resource folder qualifier. */
public static final String RES_QUALIFIER_SEP = "-"; //$NON-NLS-1$
diff --git a/layoutlib-api/src/main/java/com/android/resources/ResourceFolderType.java b/layoutlib-api/src/main/java/com/android/resources/ResourceFolderType.java
index 5a271cf..0e78697 100644
--- a/layoutlib-api/src/main/java/com/android/resources/ResourceFolderType.java
+++ b/layoutlib-api/src/main/java/com/android/resources/ResourceFolderType.java
@@ -29,6 +29,7 @@
MENU(ResourceConstants.FD_RES_MENU),
MIPMAP(ResourceConstants.FD_RES_MIPMAP),
RAW(ResourceConstants.FD_RES_RAW),
+ TRANSITION(ResourceConstants.FD_RES_TRANSITION),
VALUES(ResourceConstants.FD_RES_VALUES),
XML(ResourceConstants.FD_RES_XML);
diff --git a/layoutlib-api/src/main/java/com/android/resources/ResourceType.java b/layoutlib-api/src/main/java/com/android/resources/ResourceType.java
index f02152a..891c65b 100644
--- a/layoutlib-api/src/main/java/com/android/resources/ResourceType.java
+++ b/layoutlib-api/src/main/java/com/android/resources/ResourceType.java
@@ -42,6 +42,7 @@
STRING("string", "String"), //$NON-NLS-1$
STYLE("style", "Style"), //$NON-NLS-1$
STYLEABLE("styleable", "Styleable"), //$NON-NLS-1$
+ TRANSITION("transition", "Transition"), //$NON-NLS-1$
XML("xml", "XML"), //$NON-NLS-1$
// this is not actually used. Only there because they get parsed and since we want to
// detect new resource type, we need to have this one exist.
diff --git a/legacy/ant-tasks/build.gradle b/legacy/ant-tasks/build.gradle
index 47340cb..595aa7a 100644
--- a/legacy/ant-tasks/build.gradle
+++ b/legacy/ant-tasks/build.gradle
@@ -1,3 +1,6 @@
+apply plugin: 'java'
+apply plugin: 'distrib'
+
group = 'com.android.tools.build'
archivesBaseName = 'ant-tasks'
@@ -21,3 +24,5 @@
sourceSets.main.compileClasspath += configurations.provided
javadoc.classpath += configurations.provided
+
+apply from: '../../baseVersion.gradle'
\ No newline at end of file
diff --git a/legacy/ant-tasks/src/main/java/com/android/ant/AidlExecTask.java b/legacy/ant-tasks/src/main/java/com/android/ant/AidlExecTask.java
index 87ca599..2058232 100644
--- a/legacy/ant-tasks/src/main/java/com/android/ant/AidlExecTask.java
+++ b/legacy/ant-tasks/src/main/java/com/android/ant/AidlExecTask.java
@@ -17,6 +17,7 @@
package com.android.ant;
import com.android.SdkConstants;
+import com.android.annotations.NonNull;
import com.android.sdklib.io.FileOp;
import org.apache.tools.ant.BuildException;
@@ -59,13 +60,17 @@
private class AidlProcessor implements SourceProcessor {
@Override
+ @NonNull
public Set<String> getSourceFileExtensions() {
return Collections.singleton(SdkConstants.EXT_AIDL);
}
@Override
- public void process(String filePath, String sourceFolder,
- List<String> sourceFolders, Project taskProject) {
+ public void process(
+ @NonNull String filePath,
+ @NonNull String sourceFolder,
+ @NonNull List<String> sourceFolders,
+ @NonNull Project taskProject) {
ExecTask task = new ExecTask();
task.setProject(taskProject);
task.setOwningTarget(getOwningTarget());
@@ -116,7 +121,7 @@
}
@Override
- public void displayMessage(DisplayType type, int count) {
+ public void displayMessage(@NonNull DisplayType type, int count) {
switch (type) {
case FOUND:
System.out.println(String.format("Found %1$d AIDL files.", count));
@@ -140,6 +145,11 @@
break;
}
}
+
+ @Override
+ public void removedOutput(@NonNull File file) {
+ // nothing to do.
+ }
}
/**
diff --git a/legacy/ant-tasks/src/main/java/com/android/ant/BuildConfigTask.java b/legacy/ant-tasks/src/main/java/com/android/ant/BuildConfigTask.java
index 4fee80c..e7154ff 100644
--- a/legacy/ant-tasks/src/main/java/com/android/ant/BuildConfigTask.java
+++ b/legacy/ant-tasks/src/main/java/com/android/ant/BuildConfigTask.java
@@ -53,7 +53,7 @@
// first check if the file is missing.
File buildConfigFile = generator.getBuildConfigFile();
- boolean missingFile = buildConfigFile.exists() == false;
+ boolean missingFile = !buildConfigFile.exists();
if (missingFile || hasBuildTypeChanged()) {
if (isNewBuild()) {
diff --git a/legacy/ant-tasks/src/main/java/com/android/ant/BuildTypedTask.java b/legacy/ant-tasks/src/main/java/com/android/ant/BuildTypedTask.java
index 3f4b64a..1656486 100644
--- a/legacy/ant-tasks/src/main/java/com/android/ant/BuildTypedTask.java
+++ b/legacy/ant-tasks/src/main/java/com/android/ant/BuildTypedTask.java
@@ -46,7 +46,7 @@
* A build type is defined by having an empty previousBuildType.
*/
protected boolean isNewBuild() {
- return mBuildType == null || mPreviousBuildType.length() == 0;
+ return mBuildType == null || mPreviousBuildType.isEmpty();
}
/**
@@ -58,6 +58,6 @@
return false;
}
- return mBuildType.equals(mPreviousBuildType) == false;
+ return !(mBuildType != null && mBuildType.equals(mPreviousBuildType));
}
}
diff --git a/legacy/ant-tasks/src/main/java/com/android/ant/ComputeDependencyTask.java b/legacy/ant-tasks/src/main/java/com/android/ant/ComputeDependencyTask.java
index 7d96c70..7b974d4 100644
--- a/legacy/ant-tasks/src/main/java/com/android/ant/ComputeDependencyTask.java
+++ b/legacy/ant-tasks/src/main/java/com/android/ant/ComputeDependencyTask.java
@@ -19,6 +19,7 @@
import com.android.SdkConstants;
import com.android.ant.DependencyHelper.JarProcessor;
import com.android.io.FileWrapper;
+import com.android.sdklib.build.RenderScriptProcessor;
import com.android.sdklib.internal.project.IPropertySource;
import com.android.xml.AndroidManifest;
@@ -28,6 +29,7 @@
import org.apache.tools.ant.types.Path.PathElement;
import java.io.File;
+import java.util.Collections;
import java.util.List;
/**
@@ -62,6 +64,9 @@
private String mLibraryRFilePathOut;
private int mTargetApi = -1;
private boolean mVerbose = false;
+ private boolean mRenderscriptSupportMode;
+ private String mBuildToolsFolder;
+ private String mRenderscriptSupportLibsOut;
public void setLibraryManifestFilePathOut(String libraryManifestFilePathOut) {
mLibraryManifestFilePathOut = libraryManifestFilePathOut;
@@ -95,6 +100,18 @@
mTargetApi = targetApi;
}
+ public void setRenderscriptSupportMode(boolean renderscriptSupportMode) {
+ mRenderscriptSupportMode = renderscriptSupportMode;
+ }
+
+ public void setBuildToolsFolder(String folder) {
+ mBuildToolsFolder = folder;
+ }
+
+ public void setRenderscriptSupportLibsOut(String renderscriptSupportLibsOut) {
+ mRenderscriptSupportLibsOut = renderscriptSupportLibsOut;
+ }
+
@Override
public void execute() throws BuildException {
if (mLibraryManifestFilePathOut == null) {
@@ -121,6 +138,13 @@
if (mTargetApi == -1) {
throw new BuildException("Missing attribute targetApi");
}
+ if (mBuildToolsFolder == null) {
+ throw new BuildException("Missing attribute buildToolsFolder");
+ }
+ if (mRenderscriptSupportLibsOut == null) {
+ throw new BuildException("Missing attribute renderscriptSupportLibsOut");
+ }
+
final Project antProject = getProject();
@@ -214,10 +238,11 @@
}
}
- boolean hasLibraries = jars.size() > 0;
+ boolean hasLibraries = !jars.isEmpty();
+
+ System.out.println("\n------------------");
if (mTargetApi <= 15) {
- System.out.println("\n------------------");
System.out.println("API<=15: Adding annotations.jar to the classpath.");
jars.add(new File(sdkDir, SdkConstants.FD_TOOLS +
@@ -226,6 +251,21 @@
}
+ Path rsSupportPath = new Path(antProject);
+
+ if (mRenderscriptSupportMode) {
+ File renderScriptSupportJar = RenderScriptProcessor.getSupportJar(mBuildToolsFolder);
+ System.out.println(
+ "Renderscript support mode: Adding " +
+ renderScriptSupportJar.getName() + " to the classpath.");
+ jars.add(renderScriptSupportJar);
+
+ PathElement element = rsSupportPath.createPathElement();
+ element.setPath(RenderScriptProcessor.getSupportNativeLibFolder(mBuildToolsFolder)
+ .getAbsolutePath());
+ }
+ antProject.addReference(mRenderscriptSupportLibsOut, rsSupportPath);
+
// even with no libraries, always setup these so that various tasks in Ant don't complain
// (the task themselves can handle a ref to an empty Path)
antProject.addReference(mLibraryNativeFolderPathOut, nativeFolderPath);
@@ -245,9 +285,7 @@
File libsFolder = new File(projectFolder, SdkConstants.FD_NATIVE_LIBS);
File[] jarFiles = libsFolder.listFiles(processor.getFilter());
if (jarFiles != null) {
- for (File jarFile : jarFiles) {
- jars.add(jarFile);
- }
+ Collections.addAll(jars, jarFiles);
}
// now sanitize the path to remove dups
diff --git a/legacy/ant-tasks/src/main/java/com/android/ant/DependencyGraph.java b/legacy/ant-tasks/src/main/java/com/android/ant/DependencyGraph.java
index 7cb13a0..016b51f 100644
--- a/legacy/ant-tasks/src/main/java/com/android/ant/DependencyGraph.java
+++ b/legacy/ant-tasks/src/main/java/com/android/ant/DependencyGraph.java
@@ -35,7 +35,7 @@
*/
public class DependencyGraph {
- private final static boolean DEBUG = false;
+ private static final boolean DEBUG = false;
private static enum DependencyStatus {
NONE, NEW_FILE, UPDATED_FILE, MISSING_FILE, ERROR;
@@ -136,7 +136,7 @@
private void parseDependencyFile(String dependencyFilePath) {
// first check if the dependency file is here.
File depFile = new File(dependencyFilePath);
- if (depFile.isFile() == false) {
+ if (!depFile.isFile()) {
mMissingDepFile = true;
return;
}
@@ -182,14 +182,14 @@
mTargets = new HashSet<File>(targets.length);
for (String path : targets) {
- if (path.length() > 0) {
+ if (!path.isEmpty()) {
mTargets.add(new File(path));
}
}
mPrereqs = new HashSet<File>(prereqs.length);
for (String path : prereqs) {
- if (path.length() > 0) {
+ if (!path.isEmpty()) {
if (DEBUG) {
System.out.println("PREREQ: " + path);
}
@@ -292,7 +292,7 @@
// if it's a file, remove it from the list of prereqs.
// This way if files in this folder don't trigger a build we'll have less
// files to go through manually
- if (mPrereqs.remove(file) == false) {
+ if (!mPrereqs.remove(file)) {
// turns out this is a new file!
if (DEBUG) {
@@ -329,7 +329,7 @@
// Loop through our prereq files and make sure they still exist
for (File prereq : mPrereqs) {
- if (prereq.exists() == false) {
+ if (!prereq.exists()) {
if (DEBUG) {
System.out.println("MISSING FILE: " + prereq.getAbsolutePath());
}
@@ -395,7 +395,7 @@
private boolean missingTargetFile() {
// Loop through our target files and make sure they still exist
for (File target : mTargets) {
- if (target.exists() == false) {
+ if (!target.exists()) {
return true;
}
}
@@ -411,7 +411,7 @@
// Find the oldest target
long oldestTarget = Long.MAX_VALUE;
// if there's no output, then compare to the time of the dependency file.
- if (mTargets.size() == 0) {
+ if (mTargets.isEmpty()) {
oldestTarget = mDepFileLastModified;
} else {
for (File target : mTargets) {
diff --git a/legacy/ant-tasks/src/main/java/com/android/ant/GetBuildToolsTask.java b/legacy/ant-tasks/src/main/java/com/android/ant/GetBuildToolsTask.java
index ce11cb3..b19a8e7 100644
--- a/legacy/ant-tasks/src/main/java/com/android/ant/GetBuildToolsTask.java
+++ b/legacy/ant-tasks/src/main/java/com/android/ant/GetBuildToolsTask.java
@@ -20,7 +20,7 @@
import com.android.sdklib.SdkManager;
import com.android.sdklib.internal.project.ProjectProperties;
import com.android.sdklib.repository.FullRevision;
-import com.android.utils.NullLogger;
+import com.android.utils.StdLogger;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
@@ -29,17 +29,23 @@
public class GetBuildToolsTask extends Task {
private String mName;
+ private boolean mVerbose = false;
public void setName(String name) {
mName = name;
}
+ public void setVerbose(boolean verbose) {
+ mVerbose = verbose;
+ }
+
@Override
public void execute() throws BuildException {
Project antProject = getProject();
SdkManager sdkManager = SdkManager.createManager(
- antProject.getProperty(ProjectProperties.PROPERTY_SDK), NullLogger.getLogger());
+ antProject.getProperty(ProjectProperties.PROPERTY_SDK),
+ new StdLogger(mVerbose ? StdLogger.Level.VERBOSE : StdLogger.Level.ERROR));
if (sdkManager == null) {
throw new BuildException("Unable to parse the SDK!");
diff --git a/legacy/ant-tasks/src/main/java/com/android/ant/MultiFilesTask.java b/legacy/ant-tasks/src/main/java/com/android/ant/MultiFilesTask.java
index 4f1ae40..68e8443 100644
--- a/legacy/ant-tasks/src/main/java/com/android/ant/MultiFilesTask.java
+++ b/legacy/ant-tasks/src/main/java/com/android/ant/MultiFilesTask.java
@@ -16,6 +16,8 @@
package com.android.ant;
+import com.android.annotations.NonNull;
+
import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.Path;
@@ -39,10 +41,11 @@
}
interface SourceProcessor {
- Set<String> getSourceFileExtensions();
- void process(String filePath, String sourceFolder,
- List<String> sourceFolders, Project taskProject);
- void displayMessage(DisplayType type, int count);
+ @NonNull Set<String> getSourceFileExtensions();
+ void process(@NonNull String filePath, @NonNull String sourceFolder,
+ @NonNull List<String> sourceFolders, @NonNull Project taskProject);
+ void displayMessage(@NonNull DisplayType type, int count);
+ void removedOutput(@NonNull File file);
}
protected void processFiles(SourceProcessor processor, List<Path> paths, String genFolder) {
@@ -68,7 +71,7 @@
// gather all the source files from all the source folders.
Map<String, String> sourceFiles = getFilesByNameEntryFilter(sourceFolders,
includePatterns.toArray(new String[includePatterns.size()]));
- if (sourceFiles.size() > 0) {
+ if (!sourceFiles.isEmpty()) {
processor.displayMessage(DisplayType.FOUND, sourceFiles.size());
}
@@ -123,29 +126,30 @@
toCompile.putAll(sourceFiles);
processor.displayMessage(DisplayType.COMPILING, toCompile.size());
- if (toCompile.size() > 0) {
+ if (!toCompile.isEmpty()) {
for (Entry<String, String> toCompilePath : toCompile.entrySet()) {
processor.process(toCompilePath.getKey(), toCompilePath.getValue(),
sourceFolders, taskProject);
}
}
- if (toRemove.size() > 0) {
+ if (!toRemove.isEmpty()) {
processor.displayMessage(DisplayType.REMOVE_OUTPUT, toRemove.size());
for (File toRemoveFile : toRemove) {
- if (toRemoveFile.delete() == false) {
+ processor.removedOutput(toRemoveFile);
+ if (!toRemoveFile.delete()) {
System.err.println("Failed to remove " + toRemoveFile.getAbsolutePath());
}
}
}
// remove the dependency files that are obsolete
- if (depsToRemove.size() > 0) {
+ if (!depsToRemove.isEmpty()) {
processor.displayMessage(DisplayType.REMOVE_DEP, toRemove.size());
for (String path : depsToRemove) {
- if (new File(path).delete() == false) {
+ if (!new File(path).delete()) {
System.err.println("Failed to remove " + path);
}
}
@@ -156,7 +160,7 @@
* Returns a list of files found in given folders, all matching a given filter.
* The result is a map of (file, folder).
* @param folders the folders to search
- * @param filter the filter for the files. Typically a glob.
+ * @param filters the filters for the files. Typically a glob.
* @return a map of (file, folder)
*/
private Map<String, String> getFilesByNameEntryFilter(List<String> folders, String[] filters) {
@@ -176,7 +180,7 @@
/**
* Returns a list of files found in a given folder, matching a given filter.
* @param folder the folder to search
- * @param filter the filter for the files. Typically a glob.
+ * @param filters the filter for the files. Typically a glob.
* @return an iterator.
*/
private Iterator<?> getFilesByNameEntryFilter(String folder, String... filters) {
diff --git a/legacy/ant-tasks/src/main/java/com/android/ant/RenderScriptTask.java b/legacy/ant-tasks/src/main/java/com/android/ant/RenderScriptTask.java
index fa1a19a..fb6b64d 100644
--- a/legacy/ant-tasks/src/main/java/com/android/ant/RenderScriptTask.java
+++ b/legacy/ant-tasks/src/main/java/com/android/ant/RenderScriptTask.java
@@ -17,24 +17,33 @@
package com.android.ant;
import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.build.ManualRenderScriptChecker;
+import com.android.sdklib.build.RenderScriptProcessor;
+import com.android.sdklib.repository.FullRevision;
+import com.google.common.base.Joiner;
+import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.apache.tools.ant.BuildException;
-import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.ExecTask;
import org.apache.tools.ant.types.Environment;
import org.apache.tools.ant.types.Path;
import java.io.File;
+import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.Set;
/**
* Task to execute renderscript.
* <p>
* It expects 7 attributes:<br>
- * 'executable' ({@link Path} with a single path) for the location of the llvm executable<br>
+ * 'buildToolsRoot' ({@link Path} with a single path) for the location of the build tools<br>
* 'framework' ({@link Path} with 1 or more paths) for the include paths.<br>
* 'genFolder' ({@link Path} with a single path) for the location of the gen folder.<br>
* 'resFolder' ({@link Path} with a single path) for the location of the res folder.<br>
@@ -45,7 +54,7 @@
* It also expects one or more inner elements called "source" which are identical to {@link Path}
* elements for where to find .rs files.
*/
-public class RenderScriptTask extends MultiFilesTask {
+public class RenderScriptTask extends BuildTypedTask {
private static final Set<String> EXTENSIONS = Sets.newHashSetWithExpectedSize(2);
static {
@@ -53,129 +62,21 @@
EXTENSIONS.add(SdkConstants.EXT_FS);
}
+
private String mBuildToolsRoot;
- private Path mIncludePath;
private String mGenFolder;
private String mResFolder;
+ private String mRsObjFolder;
+ private String mLibsFolder;
+ private String mBinFolder;
private final List<Path> mPaths = new ArrayList<Path>();
private int mTargetApi = 0;
+ private boolean mSupportMode = false;
+
public enum OptLevel { O0, O1, O2, O3 };
private OptLevel mOptLevel;
private boolean mDebug = false;
- private class RenderScriptProcessor implements SourceProcessor {
-
- private final String mTargetApiStr;
-
- public RenderScriptProcessor(int targetApi) {
- // get the target api value. Must be 11+ or llvm-rs-cc complains.
- mTargetApiStr = Integer.toString(mTargetApi < 11 ? 11 : mTargetApi);
- }
-
- @Override
- public Set<String> getSourceFileExtensions() {
- return EXTENSIONS;
- }
-
- @Override
- public void process(String filePath, String sourceFolder, List<String> sourceFolders,
- Project taskProject) {
-
- File exe = new File(mBuildToolsRoot, SdkConstants.FN_RENDERSCRIPT);
-
- ExecTask task = new ExecTask();
- task.setTaskName(SdkConstants.FN_RENDERSCRIPT);
- task.setProject(taskProject);
- task.setOwningTarget(getOwningTarget());
- task.setExecutable(exe.getAbsolutePath());
- task.setFailonerror(true);
-
- // create the env var for the dynamic libraries
- Environment.Variable var = new Environment.Variable();
- var.setValue(mBuildToolsRoot);
- if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) {
- var.setKey("DYLD_LIBRARY_PATH");
- } else if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_LINUX) {
- var.setKey("LD_LIBRARY_PATH");
- }
- task.addEnv(var);
-
- for (String path : mIncludePath.list()) {
- File res = new File(path);
- if (res.isDirectory()) {
- task.createArg().setValue("-I");
- task.createArg().setValue(path);
- } else {
- System.out.println(String.format(
- "WARNING: RenderScript include directory '%s' does not exist!",
- res.getAbsolutePath()));
- }
-
- }
-
- if (mDebug) {
- task.createArg().setValue("-g");
- }
-
- task.createArg().setValue("-O");
- task.createArg().setValue(Integer.toString(mOptLevel.ordinal()));
-
- task.createArg().setValue("-target-api");
- task.createArg().setValue(mTargetApiStr);
-
- task.createArg().setValue("-d");
- task.createArg().setValue(getDependencyFolder(filePath, sourceFolder));
- task.createArg().setValue("-MD");
-
- task.createArg().setValue("-p");
- task.createArg().setValue(mGenFolder);
- task.createArg().setValue("-o");
- task.createArg().setValue(mResFolder);
- task.createArg().setValue(filePath);
-
- // execute it.
- task.execute();
- }
-
- @Override
- public void displayMessage(DisplayType type, int count) {
- switch (type) {
- case FOUND:
- System.out.println(String.format("Found %1$d RenderScript files.", count));
- break;
- case COMPILING:
- if (count > 0) {
- System.out.println(String.format(
- "Compiling %1$d RenderScript files with -target-api %2$d",
- count, mTargetApi));
- System.out.println(String.format("Optimization Level: %1$d", mOptLevel.ordinal()));
- } else {
- System.out.println("No RenderScript files to compile.");
- }
- break;
- case REMOVE_OUTPUT:
- System.out.println(String.format("Found %1$d obsolete output files to remove.",
- count));
- break;
- case REMOVE_DEP:
- System.out.println(
- String.format("Found %1$d obsolete dependency files to remove.",
- count));
- break;
- }
- }
-
- private String getDependencyFolder(String filePath, String sourceFolder) {
- String relative = filePath.substring(sourceFolder.length());
- if (relative.charAt(0) == '/') {
- relative = relative.substring(1);
- }
-
- return new File(mGenFolder, relative).getParent();
- }
- }
-
-
/**
* Sets the value of the "buildToolsRoot" attribute.
* @param buildToolsRoot the value.
@@ -184,15 +85,6 @@
mBuildToolsRoot = TaskHelper.checkSinglePath("buildToolsRoot", buildToolsRoot);
}
- public void setIncludePathRefId(String refId) {
- Object path = getProject().getReference(refId);
- if (path instanceof Path) {
- mIncludePath = (Path) path;
- } else if (path != null) {
- throw new BuildException(refId + " is expected to reference a Path object.");
- }
- }
-
public void setGenFolder(Path value) {
mGenFolder = TaskHelper.checkSinglePath("genFolder", value);
}
@@ -201,6 +93,14 @@
mResFolder = TaskHelper.checkSinglePath("resFolder", value);
}
+ public void setRsObjFolder(Path value) {
+ mRsObjFolder = TaskHelper.checkSinglePath("rsObjFolder", value);
+ }
+
+ public void setLibsFolder(Path value) {
+ mLibsFolder = TaskHelper.checkSinglePath("libsFolder", value);
+ }
+
public void setTargetApi(String targetApi) {
try {
mTargetApi = Integer.parseInt(targetApi);
@@ -216,6 +116,14 @@
mOptLevel = optLevel;
}
+ public void setSupportMode(boolean supportMode) {
+ mSupportMode = supportMode;
+ }
+
+ public void setBinFolder(Path binFolder) {
+ mBinFolder = TaskHelper.checkSinglePath("binFolder", binFolder);
+ }
+
/** Sets the current build type. value is a boolean, true for debug build, false for release */
@Override
public void setBuildType(String buildType) {
@@ -234,19 +142,105 @@
if (mBuildToolsRoot == null) {
throw new BuildException("RenderScriptTask's 'buildToolsRoot' is required.");
}
- if (mIncludePath == null) {
- throw new BuildException("RenderScriptTask's 'includePath' is required.");
- }
if (mGenFolder == null) {
throw new BuildException("RenderScriptTask's 'genFolder' is required.");
}
if (mResFolder == null) {
throw new BuildException("RenderScriptTask's 'resFolder' is required.");
}
+ if (mRsObjFolder == null) {
+ throw new BuildException("RenderScriptTask's 'rsObjFolder' is required.");
+ }
+ if (mLibsFolder == null) {
+ throw new BuildException("RenderScriptTask's 'libsFolder' is required.");
+ }
if (mTargetApi == 0) {
throw new BuildException("RenderScriptTask's 'targetApi' is required.");
}
+ if (mBinFolder == null) {
+ throw new BuildException("RenderScriptTask's 'binFolder' is required.");
+ }
- processFiles(new RenderScriptProcessor(mTargetApi), mPaths, mGenFolder);
+ // convert the Path to List<File>
+ List<File> sourceFolders = Lists.newArrayList();
+ for (Path path : mPaths) {
+ String[] values = path.list();
+ if (values != null) {
+ for (String p : values) {
+ sourceFolders.add(new File(p));
+ }
+ }
+ }
+
+ try {
+ File binFile = new File(mBinFolder);
+
+ ManualRenderScriptChecker checker = new ManualRenderScriptChecker(
+ sourceFolders, binFile);
+
+ if (checker.mustCompile() || isNewBuild() || hasBuildTypeChanged()) {
+
+ checker.cleanDependencies();
+
+ List<File> emptyFileList = Collections.emptyList();
+
+ RenderScriptProcessor processor = new RenderScriptProcessor(
+ checker.getInputFiles(),
+ emptyFileList,
+ binFile,
+ new File(mGenFolder),
+ new File(mResFolder),
+ new File(mRsObjFolder),
+ new File(mLibsFolder),
+ new BuildToolInfo(new FullRevision(0), new File(mBuildToolsRoot)),
+ mTargetApi,
+ mDebug,
+ mOptLevel.ordinal(),
+ mSupportMode);
+
+ // clean old files first
+ processor.cleanOldOutput(checker.getOldOutputs());
+
+ // do the compilation(s).
+ processor.build(new RenderScriptProcessor.CommandLineLauncher() {
+ @Override
+ public void launch(
+ @NonNull File executable,
+ @NonNull List<String> arguments,
+ @NonNull Map<String, String> envVariableMap)
+ throws IOException, InterruptedException {
+
+ ExecTask task = new ExecTask();
+ task.setTaskName(executable.getName());
+ task.setProject(getProject());
+ task.setOwningTarget(getOwningTarget());
+ task.setExecutable(executable.getAbsolutePath());
+ task.setFailonerror(true);
+
+ // create the env var for the dynamic libraries
+ for (Map.Entry<String, String> entry : envVariableMap.entrySet()) {
+ Environment.Variable var = new Environment.Variable();
+ var.setKey(entry.getKey());
+ var.setValue(entry.getValue());
+ task.addEnv(var);
+ }
+
+ for (String arg : arguments) {
+ task.createArg().setValue(arg);
+ }
+
+ System.out.println(String.format(
+ "COMMAND: %s %s",
+ executable.getAbsolutePath(), Joiner.on(' ').join(arguments)));
+
+ task.execute();
+ }
+ });
+ }
+ } catch (IOException e) {
+ throw new BuildException(e);
+ } catch (InterruptedException e) {
+ throw new BuildException(e);
+ }
}
}
diff --git a/legacy/archquery/build.gradle b/legacy/archquery/build.gradle
index 591ed37..816a01c 100644
--- a/legacy/archquery/build.gradle
+++ b/legacy/archquery/build.gradle
@@ -1,5 +1,10 @@
+apply plugin: 'java'
+apply plugin: 'distrib'
+
group = 'com.android.tools'
archivesBaseName = 'archquery'
// configure the manifest of the buildDistributionJar task.
buildDistributionJar.manifest.attributes("Main-Class": "com.android.archquery.Main")
+
+apply from: '../../baseVersion.gradle'
\ No newline at end of file
diff --git a/lint/cli/build.gradle b/lint/cli/build.gradle
index 3d0816f..7407dde 100644
--- a/lint/cli/build.gradle
+++ b/lint/cli/build.gradle
@@ -1,3 +1,6 @@
+apply plugin: 'java'
+apply plugin: 'distrib'
+
group = 'com.android.tools.lint'
archivesBaseName = 'lint'
@@ -25,45 +28,9 @@
// configure the manifest of the buildDistributionJar task.
buildDistributionJar.manifest.attributes("Main-Class": "com.android.tools.lint.Main")
-uploadArchives {
- repositories {
- mavenDeployer {
- beforeDeployment { MavenDeployment deployment ->
- if (!project.has("release")) {
- throw new StopExecutionException("uploadArchives must be called with the release.gradle init script")
- }
+project.ext.pomName = 'Android Lint Tool'
+project.ext.pomDesc = 'Lint tools. Both a Command line tool and a library to add lint features to other tools'
- signing.signPom(deployment)
- }
-
- repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
- authentication(userName: project.ext.sonatypeUsername, password: project.ext.sonatypePassword)
- }
-
- pom.project {
- name 'Android Lint Tool'
- description 'Lint tools. Both a Command line tool and a library to add lint features to other tools'
- url 'http://tools.android.com'
- inceptionYear '2007'
-
- licenses {
- license {
- name 'The Apache Software License, Version 2.0'
- url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
- distribution 'repo'
- }
- }
-
- scm {
- url "https://android.googlesource.com/platform/tools/base"
- connection "git://android.googlesource.com/platform/tools/base.git"
- }
- developers {
- developer {
- name 'The Android Open Source Project'
- }
- }
- }
- }
- }
-}
+apply from: '../../baseVersion.gradle'
+apply from: '../../publish.gradle'
+apply from: '../../javadoc.gradle'
diff --git a/lint/cli/etc/lint.bat b/lint/cli/etc/lint.bat
index 2428d87..ff0bb52 100755
--- a/lint/cli/etc/lint.bat
+++ b/lint/cli/etc/lint.bat
@@ -21,7 +21,7 @@
set prog=%~f0
rem Grab current directory before we change it
-set work_dir="%cd%"
+set work_dir=%cd%
rem Change current directory and drive to where the script is, to avoid
rem issues with directories containing whitespaces.
@@ -36,13 +36,13 @@
if not defined java_exe goto :EOF
set jarfile=lint.jar
-set frameworkdir=
+set frameworkdir=.
-if exist %frameworkdir%%jarfile% goto JarFileOk
- set frameworkdir=lib\
+if exist %frameworkdir%\%jarfile% goto JarFileOk
+ set frameworkdir=lib
-if exist %frameworkdir%%jarfile% goto JarFileOk
- set frameworkdir=..\framework\
+if exist %frameworkdir%\%jarfile% goto JarFileOk
+ set frameworkdir=..\framework
:JarFileOk
@@ -51,8 +51,8 @@
shift 1
:NoDebug
-set jarpath=%frameworkdir%%jarfile%
+set jarpath=%frameworkdir%\%jarfile%
set javaextdirs=%frameworkdir%
-call %java_exe% %java_debug% -Xmx512m -Dcom.android.tools.lint.bindir=%prog_dir% -Dcom.android.tools.lint.workdir=%work_dir% -Djava.awt.headless=true -classpath "%jarpath%" com.android.tools.lint.Main %*
+call "%java_exe%" %java_debug% -Xmx512m "-Dcom.android.tools.lint.bindir=%prog_dir%" "-Dcom.android.tools.lint.workdir=%work_dir%" -Djava.awt.headless=true -classpath "%jarpath%" com.android.tools.lint.Main %*
diff --git a/lint/cli/lint-cli.iml b/lint/cli/lint-cli.iml
index ba6d076..ebbb3a1 100644
--- a/lint/cli/lint-cli.iml
+++ b/lint/cli/lint-cli.iml
@@ -14,6 +14,7 @@
<orderEntry type="module" module-name="lint-checks" exported="" />
<orderEntry type="module" module-name="testutils" exported="" scope="TEST" />
<orderEntry type="library" scope="TEST" name="JUnit3" level="project" />
+ <orderEntry type="module" module-name="builder-model" exported="" />
</component>
</module>
diff --git a/lint/cli/src/main/java/com/android/tools/lint/HtmlReporter.java b/lint/cli/src/main/java/com/android/tools/lint/HtmlReporter.java
index 7a912a8..100796f 100644
--- a/lint/cli/src/main/java/com/android/tools/lint/HtmlReporter.java
+++ b/lint/cli/src/main/java/com/android/tools/lint/HtmlReporter.java
@@ -18,7 +18,7 @@
import static com.android.SdkConstants.DOT_JPG;
import static com.android.SdkConstants.DOT_PNG;
-import static com.android.tools.lint.detector.api.Issue.OutputFormat.*;
+import static com.android.tools.lint.detector.api.Issue.OutputFormat.HTML;
import static com.android.tools.lint.detector.api.LintUtils.endsWith;
import com.android.tools.lint.checks.BuiltinIssueRegistry;
@@ -31,6 +31,7 @@
import com.android.tools.lint.detector.api.Severity;
import com.google.common.annotations.Beta;
import com.google.common.base.Charsets;
+import com.google.common.base.Joiner;
import com.google.common.collect.Maps;
import com.google.common.io.ByteStreams;
import com.google.common.io.Closeables;
@@ -60,6 +61,7 @@
@Beta
public class HtmlReporter extends Reporter {
private static final boolean USE_HOLO_STYLE = true;
+ @SuppressWarnings("ConstantConditions")
private static final String CSS = USE_HOLO_STYLE
? "hololike.css" : "default.css"; //$NON-NLS-1$ //$NON-NLS-2$
@@ -299,6 +301,16 @@
&& warning.location.getSecondary() != null) {
addImage(url, warning.location);
}
+
+ if (warning.isVariantSpecific()) {
+ mWriter.write("\n");
+ mWriter.write("Applies to variants: ");
+ mWriter.write(Joiner.on(", ").join(warning.getIncludedVariantNames()));
+ mWriter.write("<br/>\n");
+ mWriter.write("Does <b>not</b> apply to variants: ");
+ mWriter.write(Joiner.on(", ").join(warning.getExcludedVariantNames()));
+ mWriter.write("<br/>\n");
+ }
}
if (partialHide) { // Close up the extra div
mWriter.write("</div>\n"); //$NON-NLS-1$
diff --git a/lint/cli/src/main/java/com/android/tools/lint/LintCliClient.java b/lint/cli/src/main/java/com/android/tools/lint/LintCliClient.java
index d8ed5fd..ae004a2 100644
--- a/lint/cli/src/main/java/com/android/tools/lint/LintCliClient.java
+++ b/lint/cli/src/main/java/com/android/tools/lint/LintCliClient.java
@@ -16,6 +16,8 @@
package com.android.tools.lint;
+import static com.android.tools.lint.LintCliFlags.ERRNO_ERRORS;
+import static com.android.tools.lint.LintCliFlags.ERRNO_SUCCESS;
import static com.android.tools.lint.client.api.IssueRegistry.LINT_ERROR;
import static com.android.tools.lint.client.api.IssueRegistry.PARSER_ERROR;
@@ -81,11 +83,13 @@
protected IssueRegistry mRegistry;
protected LintDriver mDriver;
protected final LintCliFlags mFlags;
+ private Configuration mConfiguration;
/** Creates a CLI driver */
public LintCliClient() {
mFlags = new LintCliFlags();
- TextReporter reporter = new TextReporter(this, new PrintWriter(System.out, true), false);
+ TextReporter reporter = new TextReporter(this, mFlags, new PrintWriter(System.out, true),
+ false);
mFlags.getReporters().add(reporter);
}
@@ -97,7 +101,7 @@
* Runs the static analysis command line driver. You need to add at least one error reporter
* to the command line flags.
*/
- public int run(IssueRegistry registry, List<File> files) throws IOException {
+ public int run(@NonNull IssueRegistry registry, @NonNull List<File> files) throws IOException {
assert !mFlags.getReporters().isEmpty();
mRegistry = registry;
mDriver = new LintDriver(registry, this);
@@ -108,7 +112,7 @@
mDriver.addLintListener(new ProgressPrinter());
}
- mDriver.analyze(new LintRequest(this, files));
+ mDriver.analyze(createLintRequest(files));
Collections.sort(mWarnings);
@@ -116,7 +120,13 @@
reporter.write(mErrorCount, mWarningCount, mWarnings);
}
- return mFlags.isSetExitCode() ? (mHasErrors ? -1 : 0) : 0;
+ return mFlags.isSetExitCode() ? (mHasErrors ? ERRNO_ERRORS : ERRNO_SUCCESS) : ERRNO_SUCCESS;
+ }
+
+ /** Creates a lint request */
+ @NonNull
+ protected LintRequest createLintRequest(@NonNull List<File> files) {
+ return new LintRequest(this, files);
}
@Override
@@ -146,7 +156,7 @@
@Override
public Configuration getConfiguration(@NonNull Project project) {
- return new CliConfiguration(mFlags.getDefaultConfiguration(), project);
+ return new CliConfiguration(getConfiguration(), project);
}
/** File content cache */
@@ -176,7 +186,7 @@
@Nullable Location location,
@NonNull String message,
@Nullable Object data) {
- assert context.isEnabled(issue);
+ assert context.isEnabled(issue) || issue == LINT_ERROR;
if (severity == Severity.IGNORE) {
return;
@@ -568,7 +578,18 @@
/** Returns the configuration used by this client */
Configuration getConfiguration() {
- return mFlags.getDefaultConfiguration();
+ if (mConfiguration == null) {
+ File configFile = mFlags.getDefaultConfiguration();
+ if (configFile != null) {
+ if (!configFile.exists()) {
+ log(Severity.ERROR, null, "Warning: Configuration file %1$s does not exist",
+ configFile);
+ }
+ mConfiguration = createConfigurationFromFile(configFile);
+ }
+ }
+
+ return mConfiguration;
}
/** Returns true if the given issue has been explicitly disabled */
@@ -576,7 +597,7 @@
return mFlags.getSuppressedIds().contains(issue.getId());
}
- Configuration createConfigurationFromFile(File file) {
+ public Configuration createConfigurationFromFile(File file) {
return new CliConfiguration(file);
}
@@ -605,4 +626,8 @@
return null;
}
+
+ public boolean haveErrors() {
+ return mErrorCount > 0;
+ }
}
diff --git a/lint/cli/src/main/java/com/android/tools/lint/LintCliFlags.java b/lint/cli/src/main/java/com/android/tools/lint/LintCliFlags.java
index 7f4a545..caf74ce 100644
--- a/lint/cli/src/main/java/com/android/tools/lint/LintCliFlags.java
+++ b/lint/cli/src/main/java/com/android/tools/lint/LintCliFlags.java
@@ -53,9 +53,16 @@
private List<File> mLibraries;
private List<File> mResources;
- private Configuration mDefaultConfiguration;
+ private File mDefaultConfiguration;
private boolean mShowAll;
+ public static final int ERRNO_SUCCESS = 0;
+ public static final int ERRNO_ERRORS = 1;
+ public static final int ERRNO_USAGE = 2;
+ public static final int ERRNO_EXISTS = 3;
+ public static final int ERRNO_HELP = 4;
+ public static final int ERRNO_INVALID_ARGS = 5;
+
/**
* Returns the set of issue id's to suppress. Callers are allowed to modify this collection.
* To suppress a given issue, add the {@link Issue#getId()} to the returned set.
@@ -206,20 +213,20 @@
}
/**
- * Returns the default configuration to use as a fallback
+ * Returns the default configuration file to use as a fallback
*/
@Nullable
- public Configuration getDefaultConfiguration() {
+ public File getDefaultConfiguration() {
return mDefaultConfiguration;
}
/**
- * Sets the default configuration to use as a fallback. This corresponds to a {@code lint.xml}
+ * Sets the default config file to use as a fallback. This corresponds to a {@code lint.xml}
* file with severities etc to use when a project does not have more specific information.
* To construct a configuration from a {@link java.io.File}, use
* {@link LintCliClient#createConfigurationFromFile(java.io.File)}.
*/
- public void setDefaultConfiguration(@Nullable Configuration defaultConfiguration) {
+ public void setDefaultConfiguration(@Nullable File defaultConfiguration) {
mDefaultConfiguration = defaultConfiguration;
}
diff --git a/lint/cli/src/main/java/com/android/tools/lint/Main.java b/lint/cli/src/main/java/com/android/tools/lint/Main.java
index 9f7c3af..d5f9507 100644
--- a/lint/cli/src/main/java/com/android/tools/lint/Main.java
+++ b/lint/cli/src/main/java/com/android/tools/lint/Main.java
@@ -18,15 +18,25 @@
import static com.android.SdkConstants.DOT_XML;
import static com.android.SdkConstants.VALUE_NONE;
+import static com.android.tools.lint.LintCliFlags.ERRNO_ERRORS;
+import static com.android.tools.lint.LintCliFlags.ERRNO_EXISTS;
+import static com.android.tools.lint.LintCliFlags.ERRNO_HELP;
+import static com.android.tools.lint.LintCliFlags.ERRNO_INVALID_ARGS;
+import static com.android.tools.lint.LintCliFlags.ERRNO_SUCCESS;
+import static com.android.tools.lint.LintCliFlags.ERRNO_USAGE;
import static com.android.tools.lint.detector.api.Issue.OutputFormat.TEXT;
import static com.android.tools.lint.detector.api.LintUtils.endsWith;
+import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.tools.lint.checks.BuiltinIssueRegistry;
import com.android.tools.lint.client.api.IssueRegistry;
import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Context;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Project;
import com.android.utils.SdkUtils;
import com.google.common.annotations.Beta;
@@ -87,12 +97,6 @@
private static final String PROP_WORK_DIR = "com.android.tools.lint.workdir"; //$NON-NLS-1$
- private static final int ERRNO_ERRORS = 1;
- private static final int ERRNO_USAGE = 2;
- private static final int ERRNO_EXISTS = 3;
- private static final int ERRNO_HELP = 4;
- private static final int ERRNO_INVALID_ARGS = 5;
-
private LintCliFlags mFlags = new LintCliFlags();
/** Creates a CLI driver */
@@ -121,7 +125,32 @@
}
IssueRegistry registry = new BuiltinIssueRegistry();
- LintCliClient client = new LintCliClient(mFlags);
+
+ // When running lint from the command line, warn if the project is a Gradle project
+ // since those projects may have custom project configuration that the command line
+ // runner won't know about.
+ LintCliClient client = new LintCliClient(mFlags) {
+ @NonNull
+ @Override
+ protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+ Project project = super.createProject(dir, referenceDir);
+ if (project.isGradleProject()) {
+ @SuppressWarnings("SpellCheckingInspection")
+ String message = String.format("\"%1$s\" is a Gradle project. To correctly "
+ + "analyze Gradle projects, you should run \"gradlew :lint\" instead.",
+ project.getName());
+ Location location = Location.create(project.getDir());
+ Context context = new Context(mDriver, project, project, project.getDir());
+ if (context.isEnabled(IssueRegistry.LINT_ERROR)) {
+ report(context,
+ IssueRegistry.LINT_ERROR,
+ project.getConfiguration().getSeverity(IssueRegistry.LINT_ERROR),
+ location, message, null);
+ }
+ }
+ return project;
+ }
+ };
// Mapping from file path prefix to URL. Applies only to HTML reports
String urlMap = null;
@@ -169,7 +198,7 @@
} else {
displayValidIds(registry, System.out);
}
- System.exit(0);
+ System.exit(ERRNO_SUCCESS);
} else if (arg.equals(ARG_SHOW)) {
// Show specific issues?
if (index < args.length - 1 && !args[index + 1].startsWith("-")) { //$NON-NLS-1$
@@ -199,7 +228,7 @@
} else {
showIssues(registry);
}
- System.exit(0);
+ System.exit(ERRNO_SUCCESS);
} else if (arg.equals(ARG_FULL_PATH)
|| arg.equals(ARG_FULL_PATH + "s")) { // allow "--fullpaths" too
mFlags.setFullPath(true);
@@ -213,7 +242,7 @@
mFlags.setSetExitCode(true);
} else if (arg.equals(ARG_VERSION)) {
printVersion(client);
- System.exit(0);
+ System.exit(ERRNO_SUCCESS);
} else if (arg.equals(ARG_URL)) {
if (index == args.length - 1) {
System.err.println("Missing URL mapping string");
@@ -236,7 +265,7 @@
System.err.println(file.getAbsolutePath() + " does not exist");
System.exit(ERRNO_INVALID_ARGS);
}
- mFlags.setDefaultConfiguration(client.createConfigurationFromFile(file));
+ mFlags.setDefaultConfiguration(file);
} else if (arg.equals(ARG_HTML) || arg.equals(ARG_SIMPLE_HTML)) {
if (index == args.length - 1) {
System.err.println("Missing HTML output file name");
@@ -295,6 +324,10 @@
System.exit(ERRNO_INVALID_ARGS);
}
File output = getOutArgumentPath(args[++index]);
+ // Get an absolute path such that we can ask its parent directory for
+ // write permission etc.
+ output = output.getAbsoluteFile();
+
if (output.exists()) {
boolean delete = output.delete();
if (!delete) {
@@ -302,7 +335,7 @@
System.exit(ERRNO_EXISTS);
}
}
- if (output.canWrite()) {
+ if (output.getParentFile() != null && !output.getParentFile().canWrite()) {
System.err.println("Cannot write XML output file " + output);
System.exit(ERRNO_EXISTS);
}
@@ -314,7 +347,7 @@
}
} else if (arg.equals(ARG_TEXT)) {
if (index == args.length - 1) {
- System.err.println("Missing XML output file name");
+ System.err.println("Missing text output file name");
System.exit(ERRNO_INVALID_ARGS);
}
@@ -326,6 +359,11 @@
closeWriter = false;
} else {
File output = getOutArgumentPath(outputName);
+
+ // Get an absolute path such that we can ask its parent directory for
+ // write permission etc.
+ output = output.getAbsoluteFile();
+
if (output.exists()) {
boolean delete = output.delete();
if (!delete) {
@@ -333,8 +371,8 @@
System.exit(ERRNO_EXISTS);
}
}
- if (output.canWrite()) {
- System.err.println("Cannot write XML output file " + output);
+ if (output.getParentFile() != null && !output.getParentFile().canWrite()) {
+ System.err.println("Cannot write text output file " + output);
System.exit(ERRNO_EXISTS);
}
try {
@@ -345,7 +383,7 @@
}
closeWriter = true;
}
- mFlags.getReporters().add(new TextReporter(client, writer, closeWriter));
+ mFlags.getReporters().add(new TextReporter(client, mFlags, writer, closeWriter));
} else if (arg.equals(ARG_DISABLE) || arg.equals(ARG_IGNORE)) {
if (index == args.length - 1) {
System.err.println("Missing categories or id's to disable");
@@ -541,13 +579,15 @@
List<Reporter> reporters = mFlags.getReporters();
if (reporters.isEmpty()) {
+ //noinspection VariableNotUsedInsideIf
if (urlMap != null) {
System.err.println(String.format(
"Warning: The %1$s option only applies to HTML reports (%2$s)",
ARG_URL, ARG_HTML));
}
- reporters.add(new TextReporter(client, new PrintWriter(System.out, true), false));
+ reporters.add(new TextReporter(client, mFlags,
+ new PrintWriter(System.out, true), false));
} else {
if (urlMap == null) {
// By default just map from /foo to file:///foo
@@ -731,7 +771,7 @@
"\"lint --ignore UnusedResources,UselessLeaf /my/project/path\"\n";
}
- private void printVersion(LintCliClient client) {
+ private static void printVersion(LintCliClient client) {
String revision = client.getRevision();
if (revision != null) {
System.out.println(String.format("lint: version %1$s", revision));
diff --git a/lint/cli/src/main/java/com/android/tools/lint/Reporter.java b/lint/cli/src/main/java/com/android/tools/lint/Reporter.java
index d2252db..663fa02 100644
--- a/lint/cli/src/main/java/com/android/tools/lint/Reporter.java
+++ b/lint/cli/src/main/java/com/android/tools/lint/Reporter.java
@@ -159,7 +159,7 @@
}
/** Set mapping of path prefixes to corresponding URLs in the HTML report */
- void setUrlMap(Map<String, String> urlMap) {
+ public void setUrlMap(Map<String, String> urlMap) {
mUrlMap = urlMap;
}
diff --git a/lint/cli/src/main/java/com/android/tools/lint/TextReporter.java b/lint/cli/src/main/java/com/android/tools/lint/TextReporter.java
index 8568239..87ae189 100644
--- a/lint/cli/src/main/java/com/android/tools/lint/TextReporter.java
+++ b/lint/cli/src/main/java/com/android/tools/lint/TextReporter.java
@@ -20,7 +20,9 @@
import com.android.tools.lint.detector.api.Position;
import com.android.tools.lint.detector.api.Severity;
import com.google.common.annotations.Beta;
+import com.google.common.base.Joiner;
+import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.util.List;
@@ -35,23 +37,40 @@
public class TextReporter extends Reporter {
private final Writer mWriter;
private final boolean mClose;
+ private final LintCliFlags mFlags;
/**
* Constructs a new {@link TextReporter}
*
* @param client the client
+ * @param flags the flags
* @param writer the writer to write into
* @param close whether the writer should be closed when done
*/
- public TextReporter(LintCliClient client, Writer writer, boolean close) {
- super(client, null);
+ public TextReporter(LintCliClient client, LintCliFlags flags, Writer writer, boolean close) {
+ this(client, flags, null, writer, close);
+ }
+
+ /**
+ * Constructs a new {@link TextReporter}
+ *
+ * @param client the client
+ * @param flags the flags
+ * @param file the file corresponding to the writer, if any
+ * @param writer the writer to write into
+ * @param close whether the writer should be closed when done
+ */
+ public TextReporter(LintCliClient client, LintCliFlags flags, File file, Writer writer,
+ boolean close) {
+ super(client, file);
+ mFlags = flags;
mWriter = writer;
mClose = close;
}
@Override
public void write(int errorCount, int warningCount, List<Warning> issues) throws IOException {
- boolean abbreviate = mClient.getDriver().isAbbreviating();
+ boolean abbreviate = !mFlags.isShowEverything();
StringBuilder output = new StringBuilder(issues.size() * 200);
if (issues.isEmpty()) {
@@ -163,6 +182,19 @@
output.append(wrapped);
}
}
+
+ if (warning.isVariantSpecific()) {
+ List<String> names;
+ if (warning.includesMoreThanExcludes()) {
+ output.append("Applies to variants: ");
+ names = warning.getIncludedVariantNames();
+ } else {
+ output.append("Does not apply to variants: ");
+ names = warning.getExcludedVariantNames();
+ }
+ output.append(Joiner.on(", ").join(names));
+ output.append('\n');
+ }
}
mWriter.write(output.toString());
@@ -173,6 +205,11 @@
mWriter.flush();
if (mClose) {
mWriter.close();
+
+ if (mOutput != null) {
+ String path = mOutput.getAbsolutePath();
+ System.out.println(String.format("Wrote text report to %1$s", path));
+ }
}
}
}
diff --git a/lint/cli/src/main/java/com/android/tools/lint/Warning.java b/lint/cli/src/main/java/com/android/tools/lint/Warning.java
index b6f413b..c683ab8 100644
--- a/lint/cli/src/main/java/com/android/tools/lint/Warning.java
+++ b/lint/cli/src/main/java/com/android/tools/lint/Warning.java
@@ -16,13 +16,24 @@
package com.android.tools.lint;
+import com.android.annotations.NonNull;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.Variant;
import com.android.tools.lint.client.api.LintClient;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.Location;
import com.android.tools.lint.detector.api.Project;
import com.android.tools.lint.detector.api.Severity;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
/**
* A {@link Warning} represents a specific warning that a {@link LintClient}
@@ -30,12 +41,13 @@
* list of warnings such that it can sort them all before presenting them all at
* the end.
*/
-class Warning implements Comparable<Warning> {
+public class Warning implements Comparable<Warning> {
public final Issue issue;
public final String message;
public final Severity severity;
public final Object data;
public final Project project;
+ public AndroidProject gradleProject;
public Location location;
public File file;
public String path;
@@ -43,6 +55,7 @@
public int offset = -1;
public String errorLine;
public String fileContents;
+ public Set<Variant> variants;
public Warning(Issue issue, String message, Severity severity, Project project, Object data) {
this.issue = issue;
@@ -53,8 +66,9 @@
}
// ---- Implements Comparable<Warning> ----
+ @SuppressWarnings({"VariableNotUsedInsideIf", "ConstantConditions"})
@Override
- public int compareTo(Warning other) {
+ public int compareTo(@NonNull Warning other) {
// Sort by category, then by priority, then by id,
// then by file, then by line
int categoryDelta = issue.getCategory().compareTo(other.issue.getCategory());
@@ -68,19 +82,24 @@
}
String id1 = issue.getId();
String id2 = other.issue.getId();
- if (id1 == null || id2 == null) {
- return file.getName().compareTo(other.file.getName());
- }
+ assert id1 != null;
+ assert id2 != null;
int idDelta = id1.compareTo(id2);
if (idDelta != 0) {
return idDelta;
}
- if (file != null && other.file != null) {
- int fileDelta = file.getName().compareTo(
- other.file.getName());
- if (fileDelta != 0) {
- return fileDelta;
+ if (file != null) {
+ if (other.file != null) {
+ int fileDelta = file.getName().compareTo(
+ other.file.getName());
+ if (fileDelta != 0) {
+ return fileDelta;
+ }
+ } else {
+ return -1;
}
+ } else if (other.file != null) {
+ return 1;
}
if (line != other.line) {
return line - other.line;
@@ -88,4 +107,82 @@
return message.compareTo(other.message);
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ Warning warning = (Warning) o;
+
+ if (line != warning.line) {
+ return false;
+ }
+ if (file != null ? !file.equals(warning.file) : warning.file != null) {
+ return false;
+ }
+ if (!issue.getCategory().equals(warning.issue.getCategory())) {
+ return false;
+ }
+ if (issue.getPriority() != warning.issue.getPriority()) {
+ return false;
+ }
+ if (!issue.getId().equals(warning.issue.getId())) {
+ return false;
+ }
+ //noinspection RedundantIfStatement
+ if (!message.equals(warning.message)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = message.hashCode();
+ result = 31 * result + (file != null ? file.hashCode() : 0);
+ return result;
+ }
+
+ public boolean isVariantSpecific() {
+ return variants != null && variants.size() < gradleProject.getVariants().size();
+ }
+
+ public boolean includesMoreThanExcludes() {
+ assert isVariantSpecific();
+ int variantCount = variants.size();
+ int allVariantCount = gradleProject.getVariants().size();
+ return variantCount <= allVariantCount - variantCount;
+ }
+
+ public List<String> getIncludedVariantNames() {
+ assert isVariantSpecific();
+ List<String> names = new ArrayList<String>();
+ if (variants != null) {
+ for (Variant variant : variants) {
+ names.add(variant.getName());
+ }
+ }
+ Collections.sort(names);
+ return names;
+ }
+
+ public List<String> getExcludedVariantNames() {
+ assert isVariantSpecific();
+ Collection<Variant> variants = gradleProject.getVariants();
+ Set<String> allVariants = new HashSet<String>(variants.size());
+ for (Variant variant : variants) {
+ allVariants.add(variant.getName());
+ }
+ Set<String> included = new HashSet<String>(getIncludedVariantNames());
+ Set<String> excluded = Sets.difference(allVariants, included);
+ List<String> sorted = Lists.newArrayList(excluded);
+ Collections.sort(sorted);
+ return sorted;
+ }
}
diff --git a/lint/cli/src/main/java/com/android/tools/lint/XmlReporter.java b/lint/cli/src/main/java/com/android/tools/lint/XmlReporter.java
index 6c7feb9..a7b7be5 100644
--- a/lint/cli/src/main/java/com/android/tools/lint/XmlReporter.java
+++ b/lint/cli/src/main/java/com/android/tools/lint/XmlReporter.java
@@ -86,12 +86,9 @@
List<String> moreInfo = issue.getMoreInfo();
if (!moreInfo.isEmpty()) {
// Compatibility with old format: list first URL
- writeAttribute(mWriter, 2, "url", moreInfo.get(0)); //$NON-NLS-1$
- if (issue.getMoreInfo() != null) {
- writeAttribute(mWriter, 2, "urls", //$NON-NLS-1$
- Joiner.on(',').join(issue.getMoreInfo()));
-
- }
+ writeAttribute(mWriter, 2, "url", moreInfo.get(0)); //$NON-NLS-1$
+ writeAttribute(mWriter, 2, "urls", //$NON-NLS-1$
+ Joiner.on(',').join(issue.getMoreInfo()));
}
if (warning.errorLine != null && !warning.errorLine.isEmpty()) {
String line = warning.errorLine;
@@ -102,10 +99,16 @@
String line1 = line.substring(0, index1);
String line2 = line.substring(index1 + 1, index2);
writeAttribute(mWriter, 2, "errorLine1", line1); //$NON-NLS-1$
- writeAttribute(mWriter, 2, "errorLine2", line2); //$NON-NLS-1$
+ writeAttribute(mWriter, 2, "errorLine2", line2); //$NON-NLS-1$
}
}
}
+
+ if (warning.isVariantSpecific()) {
+ writeAttribute(mWriter, 2, "includedVariants", Joiner.on(',').join(warning.getIncludedVariantNames()));
+ writeAttribute(mWriter, 2, "excludedVariants", Joiner.on(',').join(warning.getExcludedVariantNames()));
+ }
+
if (mClient.getRegistry() instanceof BuiltinIssueRegistry &&
((BuiltinIssueRegistry) mClient.getRegistry()).hasAutoFix(
"adt", issue)) { //$NON-NLS-1$
diff --git a/lint/cli/src/test/java/com/android/tools/lint/MainTest.java b/lint/cli/src/test/java/com/android/tools/lint/MainTest.java
index e51407f..8737d51 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/MainTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/MainTest.java
@@ -16,9 +16,14 @@
package com.android.tools.lint;
+import static com.android.tools.lint.LintCliFlags.ERRNO_EXISTS;
+import static com.android.tools.lint.LintCliFlags.ERRNO_INVALID_ARGS;
+import static com.android.tools.lint.LintCliFlags.ERRNO_SUCCESS;
+
import com.android.tools.lint.checks.AbstractCheckTest;
import com.android.tools.lint.checks.AccessibilityDetector;
import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Issue;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -42,7 +47,8 @@
}
}
- private void checkDriver(String expectedOutput, String expectedError, String[] args)
+ private void checkDriver(String expectedOutput, String expectedError, int expectedExitCode,
+ String[] args)
throws Exception {
PrintStream previousOut = System.out;
PrintStream previousErr = System.err;
@@ -61,7 +67,7 @@
}
@Override
public void checkExit(int status) {
- throw new ExitException();
+ throw new ExitException(status);
}
});
@@ -70,14 +76,17 @@
final ByteArrayOutputStream error = new ByteArrayOutputStream();
System.setErr(new PrintStream(error));
+ int exitCode = 0xCAFEBABE; // not set
try {
Main.main(args);
} catch (ExitException e) {
// Allow
+ exitCode = e.getStatus();
}
assertEquals(expectedError, cleanup(error.toString()));
assertEquals(expectedOutput, cleanup(output.toString()));
+ assertEquals(expectedExitCode, exitCode);
} finally {
// Re-enable system exit for unit test
System.setSecurityManager(null);
@@ -103,6 +112,9 @@
// Expected error
"",
+ // Expected exit code
+ ERRNO_SUCCESS,
+
// Args
new String[] {
"--check",
@@ -152,6 +164,9 @@
// Expected error
"",
+ // Expected exit code
+ ERRNO_SUCCESS,
+
// Args
new String[] {
"--show",
@@ -183,6 +198,9 @@
// Expected error
"",
+ // Expected exit code
+ ERRNO_SUCCESS,
+
// Args
new String[] {
"--show",
@@ -195,6 +213,9 @@
"",
"Library foo.jar does not exist.\n",
+ // Expected exit code
+ ERRNO_INVALID_ARGS,
+
// Args
new String[] {
"--libraries",
@@ -210,6 +231,9 @@
"",
"The --sources, --classpath, --libraries and --resources arguments can only be used with a single project\n",
+ // Expected exit code
+ ERRNO_INVALID_ARGS,
+
// Args
new String[] {
"--libraries",
@@ -246,6 +270,9 @@
+ "0 errors, 4 warnings\n", // Expected output
"",
+ // Expected exit code
+ ERRNO_SUCCESS,
+
// Args
new String[] {
"--check",
@@ -284,6 +311,9 @@
+ "0 errors, 4 warnings\n", // Expected output
"",
+ // Expected exit code
+ ERRNO_SUCCESS,
+
// Args
new String[] {
"--check",
@@ -325,6 +355,9 @@
"0 errors, 5 warnings\n",
"",
+ // Expected exit code
+ ERRNO_SUCCESS,
+
// Args
new String[] {
"--check",
@@ -350,6 +383,9 @@
"No issues found.\n",
"",
+ // Expected exit code
+ ERRNO_SUCCESS,
+
// Args
new String[] {
"--check",
@@ -371,8 +407,15 @@
private static class ExitException extends SecurityException {
private static final long serialVersionUID = 1L;
- private ExitException() {
+ private final int mStatus;
+
+ public ExitException(int status) {
super("Unit test");
+ mStatus = status;
+ }
+
+ public int getStatus() {
+ return mStatus;
}
}
@@ -410,4 +453,111 @@
assertEquals(sep + "c:" + sep + "foo",
LintCliClient.getCleanPath(new File(sep + "c:" + sep + "foo")));
}
+
+ public void testGradle() throws Exception {
+ File project = getProjectDir(null,
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "multiproject/library.properties=>build.gradle", // dummy; only name counts
+ "apicheck/ApiCallTest.class.data=>bin/classes/foo/bar/ApiCallTest.class"
+ );
+ checkDriver(""
+ + "\n"
+ + "Scanning MainTest_testGradle: \n"
+ + "MainTest_testGradle: Error: \"MainTest_testGradle\" is a Gradle project. "
+ + "To correctly analyze Gradle projects, you should run \"gradlew :lint\" "
+ + "instead. [LintError]\n"
+ + "1 errors, 0 warnings\n",
+
+ "",
+
+ // Expected exit code
+ ERRNO_SUCCESS,
+
+ // Args
+ new String[] {
+ "--check",
+ "HardcodedText",
+ project.getPath()
+ });
+ }
+
+ public void testValidateOutput() throws Exception {
+ File project = getProjectDir(null,
+ "res/layout/accessibility.xml=>myres1/layout/accessibility1.xml"
+ );
+
+ File outputDir = new File(project, "build");
+ outputDir.mkdirs();
+
+ checkDriver(
+ "\n"
+ + "Scanning MainTest_testValidateOutput: .\n"
+ + "Scanning MainTest_testValidateOutput (Phase 2): \n", // Expected output
+
+ "",
+
+ // Expected exit code
+ ERRNO_SUCCESS,
+
+ // Args
+ new String[]{
+ "--text",
+ new File(outputDir, "foo2.text").getPath(),
+ project.getPath(),
+ });
+
+ //noinspection ResultOfMethodCallIgnored
+ boolean disabledWrite = outputDir.setWritable(false);
+ assertTrue(disabledWrite);
+
+ checkDriver(
+ "", // Expected output
+
+ "Cannot write XML output file /TESTROOT/build/foo.xml\n", // Expected error
+
+ // Expected exit code
+ ERRNO_EXISTS,
+
+ // Args
+ new String[] {
+ "--xml",
+ new File(outputDir, "foo.xml").getPath(),
+ project.getPath(),
+ });
+
+ checkDriver(
+ "", // Expected output
+
+ "Cannot write HTML output file /TESTROOT/build/foo.html\n", // Expected error
+
+ // Expected exit code
+ ERRNO_EXISTS,
+
+ // Args
+ new String[] {
+ "--html",
+ new File(outputDir, "foo.html").getPath(),
+ project.getPath(),
+ });
+
+ checkDriver(
+ "", // Expected output
+
+ "Cannot write text output file /TESTROOT/build/foo.text\n", // Expected error
+
+ // Expected exit code
+ ERRNO_EXISTS,
+
+ // Args
+ new String[] {
+ "--text",
+ new File(outputDir, "foo.text").getPath(),
+ project.getPath(),
+ });
+ }
+
+ @Override
+ protected boolean isEnabled(Issue issue) {
+ return true;
+ }
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/WarningTest.java b/lint/cli/src/test/java/com/android/tools/lint/WarningTest.java
new file mode 100644
index 0000000..46b567d
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/WarningTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.lint;
+
+import com.android.tools.lint.checks.AbstractCheckTest;
+import com.android.tools.lint.checks.UnusedResourceDetector;
+import com.android.tools.lint.client.api.LintDriver;
+import com.android.tools.lint.client.api.LintRequest;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Issue;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class WarningTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new UnusedResourceDetector();
+ }
+
+ @Override
+ protected boolean isEnabled(Issue issue) {
+ return true;
+ }
+
+ public void testComparator() throws Exception {
+ File projectDir = getProjectDir(null, // Rename .txt files to .java
+ "src/my/pkg/Test.java.txt=>src/my/pkg/Test.java",
+ "gen/my/pkg/R.java.txt=>gen/my/pkg/R.java",
+ "AndroidManifest.xml",
+ "res/layout/accessibility.xml");
+
+ final AtomicReference<List<Warning>> warningsHolder = new AtomicReference<List<Warning>>();
+ TestLintClient lintClient = new TestLintClient() {
+ @Override
+ public String analyze(List<File> files) throws Exception {
+ //String analyze = super.analyze(files);
+ mDriver = new LintDriver(new CustomIssueRegistry(), this);
+ configureDriver(mDriver);
+ mDriver.analyze(new LintRequest(this, files).setScope(getLintScope(files)));
+ warningsHolder.set(mWarnings);
+ return null;
+ }
+ };
+ List<File> files = Collections.singletonList(projectDir);
+ lintClient.analyze(files);
+
+ List<Warning> warnings = warningsHolder.get();
+ Warning prev = null;
+ for (Warning warning : warnings) {
+ if (prev != null) {
+ boolean equals = warning.equals(prev);
+ assertEquals(equals, prev.equals(warning));
+ int compare = warning.compareTo(prev);
+ assertEquals(equals, compare == 0);
+ assertEquals(-compare, prev.compareTo(warning));
+ }
+ prev = warning;
+ }
+
+ Collections.sort(warnings);
+
+ Warning prev2 = prev;
+ prev = null;
+ for (Warning warning : warnings) {
+ if (prev != null && prev2 != null) {
+ assertTrue(warning.compareTo(prev) > 0);
+ assertTrue(prev.compareTo(prev2) > 0);
+ assertTrue(warning.compareTo(prev2) > 0);
+
+ assertTrue(prev.compareTo(warning) < 0);
+ assertTrue(prev2.compareTo(prev) < 0);
+ assertTrue(prev2.compareTo(warning) < 0);
+ }
+ prev2 = prev;
+ prev = warning;
+ }
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/AbstractCheckTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/AbstractCheckTest.java
index 58ae88e..b575061 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/AbstractCheckTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/AbstractCheckTest.java
@@ -25,6 +25,7 @@
import com.android.tools.lint.LombokParser;
import com.android.tools.lint.Reporter;
import com.android.tools.lint.TextReporter;
+import com.android.tools.lint.Warning;
import com.android.tools.lint.client.api.Configuration;
import com.android.tools.lint.client.api.DefaultConfiguration;
import com.android.tools.lint.client.api.IDomParser;
@@ -40,6 +41,7 @@
import com.android.tools.lint.detector.api.Project;
import com.android.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
+import com.android.utils.SdkUtils;
import java.io.BufferedInputStream;
import java.io.File;
@@ -49,7 +51,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
-import java.net.URISyntaxException;
+import java.net.MalformedURLException;
import java.net.URL;
import java.security.CodeSource;
import java.util.ArrayList;
@@ -93,7 +95,8 @@
return issues;
}
- private class CustomIssueRegistry extends IssueRegistry {
+ public class CustomIssueRegistry extends IssueRegistry {
+ @NonNull
@Override
public List<Issue> getIssues() {
return AbstractCheckTest.this.getIssues();
@@ -185,7 +188,7 @@
return projectDir;
}
- private void addManifestFile(File projectDir) throws IOException {
+ private static void addManifestFile(File projectDir) throws IOException {
// Ensure that there is at least a manifest file there to make it a valid project
// as far as Lint is concerned:
if (!new File(projectDir, "AndroidManifest.xml").exists()) {
@@ -268,11 +271,11 @@
public TestLintClient() {
super(new LintCliFlags());
- mFlags.getReporters().add(new TextReporter(this, mWriter, false));
+ mFlags.getReporters().add(new TextReporter(this, mFlags, mWriter, false));
}
@Override
- public String getSuperClass(Project project, String name) {
+ public String getSuperClass(@NonNull Project project, @NonNull String name) {
String superClass = AbstractCheckTest.this.getSuperClass(project, name);
if (superClass != null) {
return superClass;
@@ -286,8 +289,38 @@
configureDriver(mDriver);
mDriver.analyze(new LintRequest(this, files).setScope(getLintScope(files)));
+ // Check compare contract
+ Warning prev = null;
+ for (Warning warning : mWarnings) {
+ if (prev != null) {
+ boolean equals = warning.equals(prev);
+ assertEquals(equals, prev.equals(warning));
+ int compare = warning.compareTo(prev);
+ assertEquals(equals, compare == 0);
+ assertEquals(-compare, prev.compareTo(warning));
+ }
+ prev = warning;
+ }
+
Collections.sort(mWarnings);
+ // Check compare contract & transitivity
+ Warning prev2 = prev;
+ prev = null;
+ for (Warning warning : mWarnings) {
+ if (prev != null && prev2 != null) {
+ assertTrue(warning.compareTo(prev) >= 0);
+ assertTrue(prev.compareTo(prev2) >= 0);
+ assertTrue(warning.compareTo(prev2) >= 0);
+
+ assertTrue(prev.compareTo(warning) <= 0);
+ assertTrue(prev2.compareTo(prev) <= 0);
+ assertTrue(prev2.compareTo(warning) <= 0);
+ }
+ prev2 = prev;
+ prev = warning;
+ }
+
for (Reporter reporter : mFlags.getReporters()) {
reporter.write(mErrorCount, mWarningCount, mWarnings);
}
@@ -342,6 +375,13 @@
}
super.report(context, issue, severity, location, message, data);
+
+ // Make sure errors are unique!
+ Warning prev = null;
+ for (Warning warning : mWarnings) {
+ assert prev == null || !warning.equals(prev);
+ prev = warning;
+ }
}
@Override
@@ -379,7 +419,7 @@
}
@Override
- public File findResource(String relativePath) {
+ public File findResource(@NonNull String relativePath) {
if (relativePath.equals("platform-tools/api/api-versions.xml")) {
// Look in the current Git repository and try to find it there
File rootDir = getRootDir();
@@ -420,6 +460,13 @@
return super.findResource(relativePath);
}
+
+ @NonNull
+ @Override
+ public List<File> findGlobalRuleJars() {
+ // Don't pick up random custom rules in ~/.android/lint when running unit tests
+ return Collections.emptyList();
+ }
}
/**
@@ -431,7 +478,7 @@
if (source != null) {
URL location = source.getLocation();
try {
- File dir = new File(location.toURI());
+ File dir = SdkUtils.urlToFile(location);
assertTrue(dir.getPath(), dir.exists());
while (dir != null) {
File settingsGradle = new File(dir, "settings.gradle"); //$NON-NLS-1$
@@ -446,7 +493,7 @@
}
return null;
- } catch (URISyntaxException e) {
+ } catch (MalformedURLException e) {
fail(e.getLocalizedMessage());
}
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/ApiDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/ApiDetectorTest.java
index 25e56ad..9a765a2 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/ApiDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/ApiDetectorTest.java
@@ -85,6 +85,18 @@
));
}
+ public void testXmlApiIceCreamSandwich() throws Exception {
+ assertEquals(
+ "No warnings.",
+
+ lintProject(
+ "apicheck/minics.xml=>AndroidManifest.xml",
+ "apicheck/layout.xml=>res/layout/layout.xml",
+ "apicheck/themes.xml=>res/values/themes.xml",
+ "apicheck/themes.xml=>res/color/colors.xml"
+ ));
+ }
+
public void testXmlApi1TargetApi() throws Exception {
assertEquals(
"No warnings.",
@@ -765,9 +777,6 @@
+ "src/test/pkg/ApiSourceCheck.java:55: Warning: Field requires API level 11 (current min is 1): android.view.View#MEASURED_HEIGHT_STATE_SHIFT [InlinedApi]\n"
+ " | ((child.getMeasuredHeight() >> View.MEASURED_HEIGHT_STATE_SHIFT) & (View.MEASURED_STATE_MASK >> View.MEASURED_HEIGHT_STATE_SHIFT));\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ApiSourceCheck.java:55: Warning: Field requires API level 11 (current min is 1): android.view.View#MEASURED_HEIGHT_STATE_SHIFT [InlinedApi]\n"
- + " | ((child.getMeasuredHeight() >> View.MEASURED_HEIGHT_STATE_SHIFT) & (View.MEASURED_STATE_MASK >> View.MEASURED_HEIGHT_STATE_SHIFT));\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/ApiSourceCheck.java:55: Warning: Field requires API level 11 (current min is 1): android.view.View#MEASURED_STATE_MASK [InlinedApi]\n"
+ " | ((child.getMeasuredHeight() >> View.MEASURED_HEIGHT_STATE_SHIFT) & (View.MEASURED_STATE_MASK >> View.MEASURED_HEIGHT_STATE_SHIFT));\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
@@ -789,7 +798,7 @@
+ "src/test/pkg/ApiSourceCheck.java:51: Error: Field requires API level 14 (current min is 1): android.widget.ZoomButton#ROTATION_X [NewApi]\n"
+ " Object rotationX = ZoomButton.ROTATION_X; // Requires API 14\n"
+ " ~~~~~~~~~~\n"
- + "1 errors, 18 warnings\n",
+ + "1 errors, 17 warnings\n",
lintProject(
"apicheck/classpath=>.classpath",
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/CallSuperDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/CallSuperDetectorTest.java
new file mode 100644
index 0000000..81b46f8
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/CallSuperDetectorTest.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+public class CallSuperDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new CallSuperDetector();
+ }
+
+ public void test() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/DetachedFromWindow.java:7: Warning: Overriding method should call super.onDetachedFromWindow [MissingSuperCall]\n"
+ + " protected void onDetachedFromWindow() {\n"
+ + " ~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/DetachedFromWindow.java:26: Warning: Overriding method should call super.onDetachedFromWindow [MissingSuperCall]\n"
+ + " protected void onDetachedFromWindow() {\n"
+ + " ~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 2 warnings\n",
+
+ lintProject("src/test/pkg/DetachedFromWindow.java.txt=>" +
+ "src/test/pkg/DetachedFromWindow.java"));
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/CheckPermissionDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/CheckPermissionDetectorTest.java
new file mode 100644
index 0000000..caca6a5
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/CheckPermissionDetectorTest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+public class CheckPermissionDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new CheckPermissionDetector();
+ }
+
+ public void test() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/CheckPermissions.java:7: Warning: The result of checkCallingOrSelfPermission is not used; did you mean to call enforceCallingOrSelfPermission? [UseCheckPermission]\n"
+ + " context.checkCallingOrSelfPermission(Manifest.permission.INTERNET); // WRONG\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+
+ lintProject("src/test/pkg/CheckPermissions.java.txt=>" +
+ "src/test/pkg/CheckPermissions.java"));
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/HandlerDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/HandlerDetectorTest.java
index 1e9350c..fe85237 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/HandlerDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/HandlerDetectorTest.java
@@ -41,6 +41,8 @@
"bytecode/HandlerTest.class.data=>bin/classes/test/pkg/HandlerTest.class",
"bytecode/HandlerTest$Inner.class.data=>bin/classes/test/pkg/HandlerTest$Inner.class",
"bytecode/HandlerTest$StaticInner.class.data=>bin/classes/test/pkg/HandlerTest$StaticInner.class",
- "bytecode/HandlerTest$1.class.data=>bin/classes/test/pkg/HandlerTest$1.class"));
+ "bytecode/HandlerTest$WithArbitraryLooper.class.data=>bin/classes/test/pkg/HandlerTest$WithArbitraryLooper.class",
+ "bytecode/HandlerTest$1.class.data=>bin/classes/test/pkg/HandlerTest$1.class",
+ "bytecode/HandlerTest$2.class.data=>bin/classes/test/pkg/HandlerTest$2.class"));
}
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/ManifestDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/ManifestDetectorTest.java
index 617ea80..ca7aadf 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/ManifestDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/ManifestDetectorTest.java
@@ -16,16 +16,20 @@
package com.android.tools.lint.checks;
+import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
+
import com.android.annotations.NonNull;
import com.android.tools.lint.client.api.LintClient;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.Project;
+import com.google.common.collect.Lists;
import java.io.File;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
@SuppressWarnings("javadoc")
@@ -80,6 +84,14 @@
"res/values/strings.xml"));
}
+ public void testMissingUsesSdkInGradle() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.SET_VERSION);
+ assertEquals(""
+ + "No warnings.",
+ lintProject("missingusessdk.xml=>AndroidManifest.xml",
+ "multiproject/library.properties=>build.gradle")); // dummy; only name counts
+ }
+
public void testMissingMinSdk() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.USES_SDK);
assertEquals(
@@ -301,6 +313,14 @@
lintProject("no_version.xml=>AndroidManifest.xml"));
}
+ public void testVersionNotMissingInGradleProjects() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.SET_VERSION);
+ assertEquals(""
+ + "No warnings.",
+ lintProject("no_version.xml=>AndroidManifest.xml",
+ "multiproject/library.properties=>build.gradle")); // dummy; only name counts
+ }
+
public void testIllegalReference() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.ILLEGAL_REFERENCE);
assertEquals(""
@@ -354,6 +374,16 @@
"res/values/strings.xml"));
}
+ public void testMissingApplicationIconInLibrary() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.APPLICATION_ICON);
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "missing_application_icon.xml=>AndroidManifest.xml",
+ "multiproject/library.properties=>project.properties",
+ "res/values/strings.xml"));
+ }
+
public void testMissingApplicationIconOk() throws Exception {
mEnabled = Collections.singleton(ManifestDetector.APPLICATION_ICON);
assertEquals(
@@ -378,4 +408,71 @@
+ "0 errors, 3 warnings\n",
lintProject("deviceadmin.xml=>AndroidManifest.xml"));
}
+
+ public void testMockLocations() throws Exception {
+ mEnabled = Collections.singleton(ManifestDetector.MOCK_LOCATION);
+ assertEquals(""
+ + "AndroidManifest.xml:9: Error: Mock locations should only be requested in a debug-specific manifest file (typically src/debug/AndroidManifest.xml) [MockLocation]\n"
+ + " <uses-permission android:name=\"android.permission.ACCESS_MOCK_LOCATION\" /> \n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "1 errors, 0 warnings\n",
+ lintProject(
+ "mock_location.xml=>AndroidManifest.xml",
+ "multiproject/library.properties=>build.gradle")); // dummy; only name counts
+ // TODO: When we have an instantiatable gradle model, test with real model and verify
+ // that a manifest file in a debug build type doesnot get flagged.
+ }
+
+ public void testMockLocationsOk() throws Exception {
+ // Not a Gradle project
+ mEnabled = Collections.singleton(ManifestDetector.MOCK_LOCATION);
+ assertEquals(""
+ + "No warnings.",
+ lintProject(
+ "mock_location.xml=>AndroidManifest.xml"));
+ }
+
+ // Custom project which locates all manifest files in the project rather than just
+ // being hardcoded to the root level
+ private static class MyProject extends Project {
+ protected MyProject(@NonNull LintClient client, @NonNull File dir,
+ @NonNull File referenceDir) {
+ super(client, dir, referenceDir);
+ }
+
+ @NonNull
+ @Override
+ public List<File> getManifestFiles() {
+ if (mManifestFiles == null) {
+ mManifestFiles = Lists.newArrayList();
+ addManifestFiles(mDir);
+ }
+
+ return mManifestFiles;
+ }
+
+ private void addManifestFiles(File dir) {
+ if (dir.getName().equals(ANDROID_MANIFEST_XML)) {
+ mManifestFiles.add(dir);
+ } else if (dir.isDirectory()) {
+ File[] files = dir.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ addManifestFiles(file);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ protected TestLintClient createClient() {
+ return new TestLintClient() {
+ @NonNull
+ @Override
+ protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
+ return new MyProject(this, dir, referenceDir);
+ }
+ };
+ }
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/NamespaceDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/NamespaceDetectorTest.java
index 0e7146c..7f8cd07 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/NamespaceDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/NamespaceDetectorTest.java
@@ -193,4 +193,16 @@
"multiproject/library-manifest.xml=>AndroidManifest.xml",
"multiproject/library.properties=>project.properties"));
}
+
+ public void testWrongResAutoUrl() throws Exception {
+ assertEquals(
+ "res/layout/namespace5.xml:3: Error: Suspicious namespace: Did you mean http://schemas.android.com/apk/res-auto [ResAuto]\n" +
+ " xmlns:app=\"http://schemas.android.com/apk/auto-res/\"\n" +
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
+ "1 errors, 0 warnings\n",
+
+ lintFiles("res/layout/namespace5.xml",
+ "multiproject/library-manifest.xml=>AndroidManifest.xml",
+ "multiproject/library.properties=>project.properties"));
+ }
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/ParcelDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/ParcelDetectorTest.java
index 4b9e52c..0f14dbe 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/ParcelDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/ParcelDetectorTest.java
@@ -37,10 +37,14 @@
"bytecode/MyParcelable1.java.txt=>src/test/bytecode/MyParcelable1.java",
"bytecode/MyParcelable2.java.txt=>src/test/bytecode/MyParcelable2.java",
"bytecode/MyParcelable3.java.txt=>src/test/bytecode/MyParcelable3.java",
+ "bytecode/MyParcelable4.java.txt=>src/test/bytecode/MyParcelable4.java",
+ "bytecode/MyParcelable5.java.txt=>src/test/bytecode/MyParcelable5.java",
"bytecode/MyParcelable1.class.data=>bin/classes/test/bytecode/MyParcelable1.class",
"bytecode/MyParcelable2.class.data=>bin/classes/test/bytecode/MyParcelable2.class",
"bytecode/MyParcelable2$1.class.data=>bin/classes/test/bytecode/MyParcelable2$1.class",
- "bytecode/MyParcelable3.class.data=>bin/classes/test/bytecode/MyParcelable3.class"
+ "bytecode/MyParcelable3.class.data=>bin/classes/test/bytecode/MyParcelable3.class",
+ "bytecode/MyParcelable4.class.data=>bin/classes/test/bytecode/MyParcelable4.class",
+ "bytecode/MyParcelable5.class.data=>bin/classes/test/bytecode/MyParcelable5.class"
));
}
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/PluralsDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/PluralsDetectorTest.java
index e62e0ce..0582d3b 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/PluralsDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/PluralsDetectorTest.java
@@ -40,16 +40,16 @@
public void test2() throws Exception {
assertEquals(""
- + "res/values-zh-rCN/plurals3.xml:3: Warning: For language \"zh\" the following quantities are not relevant: one [MissingQuantity]\n"
- + " <plurals name=\"draft\">\n"
- + " ^\n"
- + "res/values-cs/plurals3.xml:3: Warning: For locale \"cs\" the following quantities should also be defined: few [MissingQuantity]\n"
- + " <plurals name=\"draft\">\n"
- + " ^\n"
- + "res/values-zh-rCN/plurals3.xml:7: Warning: For language \"zh\" the following quantities are not relevant: one [MissingQuantity]\n"
- + " <plurals name=\"title_day_dialog_content\">\n"
- + " ^\n"
- + "0 errors, 3 warnings\n",
+ + "res/values-cs/plurals3.xml:3: Warning: For locale \"cs\" the following quantities should also be defined: few [MissingQuantity]\n" +
+ " <plurals name=\"draft\">\n" +
+ " ^\n" +
+ "res/values-zh-rCN/plurals3.xml:3: Warning: For language \"zh\" the following quantities are not relevant: one [UnusedQuantity]\n" +
+ " <plurals name=\"draft\">\n" +
+ " ^\n" +
+ "res/values-zh-rCN/plurals3.xml:7: Warning: For language \"zh\" the following quantities are not relevant: one [UnusedQuantity]\n" +
+ " <plurals name=\"title_day_dialog_content\">\n" +
+ " ^\n" +
+ "0 errors, 3 warnings\n",
lintProject(
"res/values-zh-rCN/plurals3.xml",
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/SecureRandomGeneratorDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/SecureRandomGeneratorDetectorTest.java
new file mode 100644
index 0000000..3b7e1fd
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/SecureRandomGeneratorDetectorTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+@SuppressWarnings("SpellCheckingInspection")
+public class SecureRandomGeneratorDetectorTest extends AbstractCheckTest {
+
+ @Override
+ protected Detector getDetector() {
+ return new SecureRandomGeneratorDetector();
+ }
+
+ public void testWithoutWorkaround() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/PrngCalls.java:13: Warning: Potentially insecure random numbers "
+ + "on Android 4.3 and older. Read "
+ + "https://android-developers.blogspot.com/2013/08/some-securerandom-thoughts.html"
+ + " for more info. [TrulyRandom]\n"
+ + " KeyGenerator generator = KeyGenerator.getInstance(\"AES\", \"BC\");\n"
+ + " ~~~~~~~~~~~\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "bytecode/PrngCalls.java.txt=>src/test/pkg/PrngCalls.java",
+ "bytecode/PrngCalls.class.data=>bin/classes/test/pkg/PrngCalls.class"
+ ));
+ }
+
+ public void testWithWorkaround() throws Exception {
+ assertEquals(
+ "No warnings.",
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "bytecode/PrngCalls.java.txt=>src/test/pkg/PrngCalls.java",
+ "bytecode/PrngCalls.class.data=>bin/classes/test/pkg/PrngCalls.class",
+ "bytecode/PrngWorkaround$LinuxPRNGSecureRandom.class.data=>bin/classes/test/pkg/PrngWorkaround$LinuxPRNGSecureRandom.class",
+ "bytecode/PrngWorkaround$LinuxPRNGSecureRandomProvider.class.data=>bin/classes/test/pkg/PrngWorkaround$LinuxPRNGSecureRandomProvider.class",
+ "bytecode/PrngWorkaround.class.data=>bin/classes/test/pkg/PrngWorkaround.class"
+ ));
+ }
+
+ public void testCipherInit() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/CipherTest1.java:11: Warning: Potentially insecure random "
+ + "numbers on Android 4.3 and older. Read "
+ + "https://android-developers.blogspot.com/2013/08/some-securerandom-thoughts.html"
+ + " for more info. [TrulyRandom]\n"
+ + " cipher.init(Cipher.WRAP_MODE, key); // FLAG\n"
+ + " ~~~~\n"
+ + "0 errors, 1 warnings\n",
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "bytecode/CipherTest1.java.txt=>src/test/pkg/CipherTest1.java",
+ "bytecode/CipherTest1.class.data=>bin/classes/test/pkg/CipherTest1.class"
+ ));
+ }
+
+ public void testGetArity() {
+ assertEquals(2, SecureRandomGeneratorDetector.getDescArity("(ILjava/security/Key;)V"));
+ assertEquals(0, SecureRandomGeneratorDetector.getDescArity("()V"));
+ assertEquals(1, SecureRandomGeneratorDetector.getDescArity("(I)V"));
+ assertEquals(3, SecureRandomGeneratorDetector.getDescArity(
+ "(Ljava/lang/String;Ljava/lang/String;I)V"));
+ assertEquals(0, SecureRandomGeneratorDetector.getDescArity("()Lfoo/bar/Baz;"));
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/TypoDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/TypoDetectorTest.java
index 3c49b3a..f7a0139 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/TypoDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/TypoDetectorTest.java
@@ -55,6 +55,18 @@
lintProject("res/values/typos.xml=>res/values/strings.xml"));
}
+ public void testRepeatedWords() throws Exception {
+ assertEquals(
+ "res/values/strings.xml:5: Warning: Repeated word \"to\" in message: possible typo [Typos]\n" +
+ " extra location provider commands. This may allow the app to to interfere\n" +
+ " ^\n" +
+ "res/values/strings.xml:7: Warning: Repeated word \"zü\" in message: possible typo [Typos]\n" +
+ " <string name=\"other\">\"ü test\\n zü zü\"</string>\n" +
+ " ^\n" +
+ "0 errors, 2 warnings\n",
+ lintProject("res/values/repeated_words.xml=>res/values/strings.xml"));
+ }
+
public void testEnLanguage() throws Exception {
assertEquals(
"res/values-en-rUS/strings-en.xml:6: Warning: \"Andriod\" is a common misspelling; did you mean \"Android\" ? [Typos]\n" +
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/UnusedResourceDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/UnusedResourceDetectorTest.java
index ccecc54..a564860 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/UnusedResourceDetectorTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/UnusedResourceDetectorTest.java
@@ -72,9 +72,6 @@
"Warning: The resource R.layout.main appears to be unused [UnusedResources]\n" +
"Warning: The resource R.layout.other appears to be unused [UnusedResources]\n" +
"Warning: The resource R.string.hello appears to be unused [UnusedResources]\n" +
- "Warning: The resource R.id.imageView1 appears to be unused [UnusedIds]\n" +
- "Warning: The resource R.id.include1 appears to be unused [UnusedIds]\n" +
- "Warning: The resource R.id.linearLayout2 appears to be unused [UnusedIds]\n" +
"res/layout/accessibility.xml:2: Warning: The resource R.id.newlinear appears to be unused [UnusedIds]\n" +
"<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\" android:id=\"@+id/newlinear\" android:orientation=\"vertical\" android:layout_width=\"match_parent\" android:layout_height=\"match_parent\">\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
@@ -87,8 +84,10 @@
"res/layout/accessibility.xml:5: Warning: The resource R.id.android_logo2 appears to be unused [UnusedIds]\n" +
" <ImageButton android:importantForAccessibility=\"yes\" android:id=\"@+id/android_logo2\" android:layout_width=\"wrap_content\" android:layout_height=\"wrap_content\" android:src=\"@drawable/android_button\" android:focusable=\"false\" android:clickable=\"false\" android:layout_weight=\"1.0\" />\n" +
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" +
- "0 errors, 11 warnings\n" +
- "",
+ "Warning: The resource R.id.imageView1 appears to be unused [UnusedIds]\n" +
+ "Warning: The resource R.id.include1 appears to be unused [UnusedIds]\n" +
+ "Warning: The resource R.id.linearLayout2 appears to be unused [UnusedIds]\n" +
+ "0 errors, 11 warnings\n",
lintProject(
// Rename .txt files to .java
@@ -286,4 +285,12 @@
"res/anim/slide_in_out.xml"
));
}
+
+ public void testIntegerArrays() throws Exception {
+ // See http://code.google.com/p/android/issues/detail?id=59761
+ mEnableIds = false;
+ assertEquals(
+ "No warnings.",
+ lintProject("res/values/integer_arrays.xml=>res/values/integer_arrays.xml"));
+ }
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/minics.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/minics.xml
new file mode 100644
index 0000000..9eb4706
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/minics.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="test.bytecode"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk android:minSdkVersion="IceCreamSandwich" />
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+ <activity
+ android:name=".BytecodeTestsActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/CipherTest1.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/CipherTest1.class.data
new file mode 100644
index 0000000..73cd72f
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/CipherTest1.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/CipherTest1.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/CipherTest1.java.txt
new file mode 100644
index 0000000..200de79
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/CipherTest1.java.txt
@@ -0,0 +1,21 @@
+package test.pkg;
+
+import java.security.Key;
+import java.security.SecureRandom;
+
+import javax.crypto.Cipher;
+
+@SuppressWarnings("all")
+public class CipherTest1 {
+ public void test1(Cipher cipher, Key key) throws Exception {
+ cipher.init(Cipher.WRAP_MODE, key); // FLAG
+ }
+
+ public void test2(Cipher cipher, Key key, SecureRandom random) throws Exception {
+ cipher.init(Cipher.ENCRYPT_MODE, key, random);
+ }
+
+ public void setup(String transform) throws Exception {
+ Cipher cipher = Cipher.getInstance(transform);
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$2.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$2.class.data
new file mode 100644
index 0000000..52183c2
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$2.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$WithArbitraryLooper.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$WithArbitraryLooper.class.data
new file mode 100644
index 0000000..b389dc5
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest$WithArbitraryLooper.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest.class.data
index 93f9999..8b01ef2 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest.class.data
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest.java.txt
index 7622c98..06f3298 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest.java.txt
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/HandlerTest.java.txt
@@ -1,5 +1,5 @@
package test.pkg;
-
+import android.os.Looper;
import android.os.Handler;
import android.os.Message;
@@ -20,5 +20,22 @@
super.dispatchMessage(msg);
};
};
+
+ Looper looper = null;
+ Handler anonymous2 = new Handler(looper) { // OK
+ public void dispatchMessage(Message msg) {
+ super.dispatchMessage(msg);
+ };
+ };
+ }
+
+ public class WithArbitraryLooper extends Handler {
+ public WithArbitraryLooper(String unused, Looper looper) { // OK
+ super(looper, null);
+ }
+
+ public void dispatchMessage(Message msg) {
+ super.dispatchMessage(msg);
+ };
}
}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable4.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable4.class.data
new file mode 100644
index 0000000..a1c2116
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable4.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable4.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable4.java.txt
new file mode 100644
index 0000000..0a9c90f
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable4.java.txt
@@ -0,0 +1,15 @@
+package test.pkg;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public abstract class MyParcelable4 implements Parcelable {
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel arg0, int arg1) {
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable5.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable5.class.data
new file mode 100644
index 0000000..861b020
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable5.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable5.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable5.java.txt
new file mode 100644
index 0000000..292db3a
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/MyParcelable5.java.txt
@@ -0,0 +1,8 @@
+package test.pkg;
+
+import android.os.Parcelable;
+
+public interface MyParcelable5 extends Parcelable {
+ @Override
+ public int describeContents();
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngCalls.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngCalls.class.data
new file mode 100644
index 0000000..fb46952
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngCalls.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngCalls.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngCalls.java.txt
new file mode 100644
index 0000000..5414ec8
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngCalls.java.txt
@@ -0,0 +1,26 @@
+package test.pkg;
+
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.SecureRandom;
+
+import javax.crypto.KeyAgreement;
+import javax.crypto.KeyGenerator;
+
+public class PrngCalls {
+ public void testKeyGenerator() throws NoSuchAlgorithmException, NoSuchProviderException {
+ KeyGenerator generator = KeyGenerator.getInstance("AES", "BC");
+ generator.init(128);
+
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+ keyGen.initialize(512);
+
+ KeyAgreement agreement = KeyAgreement.getInstance("DH", "BC");
+ agreement.generateSecret();
+
+ SecureRandom random = new SecureRandom();
+ byte bytes[] = new byte[20];
+ random.nextBytes(bytes);
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngWorkaround$LinuxPRNGSecureRandom.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngWorkaround$LinuxPRNGSecureRandom.class.data
new file mode 100644
index 0000000..1741ccc
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngWorkaround$LinuxPRNGSecureRandom.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngWorkaround$LinuxPRNGSecureRandomProvider.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngWorkaround$LinuxPRNGSecureRandomProvider.class.data
new file mode 100644
index 0000000..b1e6d3b
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngWorkaround$LinuxPRNGSecureRandomProvider.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngWorkaround.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngWorkaround.class.data
new file mode 100644
index 0000000..32a785a
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngWorkaround.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngWorkaround.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngWorkaround.java.txt
new file mode 100644
index 0000000..a63e267
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/PrngWorkaround.java.txt
@@ -0,0 +1,336 @@
+/*
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will Google be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, as long as the origin is not misrepresented.
+ */
+
+package test.pkg;
+
+import android.os.Build;
+import android.os.Process;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.security.SecureRandomSpi;
+import java.security.Security;
+
+/**
+ * Fixes for the output of the default PRNG having low entropy.
+ *
+ * The fixes need to be applied via {@link #apply()} before any use of Java
+ * Cryptography Architecture primitives. A good place to invoke them is in the
+ * application's {@code onCreate}.
+ */
+public final class PrngWorkaround {
+
+ private static final int VERSION_CODE_JELLY_BEAN = 16;
+ private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18;
+ private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL =
+ getBuildFingerprintAndDeviceSerial();
+
+ /** Hidden constructor to prevent instantiation. */
+ private PrngWorkaround() {}
+
+ /**
+ * Applies all fixes.
+ *
+ * @throws SecurityException if a fix is needed but could not be applied.
+ */
+ public static void apply() {
+ applyOpenSSLFix();
+ installLinuxPRNGSecureRandom();
+ }
+
+ /**
+ * Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the
+ * fix is not needed.
+ *
+ * @throws SecurityException if the fix is needed but could not be applied.
+ */
+ private static void applyOpenSSLFix() throws SecurityException {
+ if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN)
+ || (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) {
+ // No need to apply the fix
+ return;
+ }
+
+ try {
+ // Mix in the device- and invocation-specific seed.
+ Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
+ .getMethod("RAND_seed", byte[].class)
+ .invoke(null, generateSeed());
+
+ // Mix output of Linux PRNG into OpenSSL's PRNG
+ int bytesRead = (Integer) Class.forName(
+ "org.apache.harmony.xnet.provider.jsse.NativeCrypto")
+ .getMethod("RAND_load_file", String.class, long.class)
+ .invoke(null, "/dev/urandom", 1024);
+ if (bytesRead != 1024) {
+ throw new IOException(
+ "Unexpected number of bytes read from Linux PRNG: "
+ + bytesRead);
+ }
+ } catch (Exception e) {
+ throw new SecurityException("Failed to seed OpenSSL PRNG", e);
+ }
+ }
+
+ /**
+ * Installs a Linux PRNG-backed {@code SecureRandom} implementation as the
+ * default. Does nothing if the implementation is already the default or if
+ * there is not need to install the implementation.
+ *
+ * @throws SecurityException if the fix is needed but could not be applied.
+ */
+ private static void installLinuxPRNGSecureRandom()
+ throws SecurityException {
+ if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) {
+ // No need to apply the fix
+ return;
+ }
+
+ // Install a Linux PRNG-based SecureRandom implementation as the
+ // default, if not yet installed.
+ Provider[] secureRandomProviders =
+ Security.getProviders("SecureRandom.SHA1PRNG");
+ if ((secureRandomProviders == null)
+ || (secureRandomProviders.length < 1)
+ || (!LinuxPRNGSecureRandomProvider.class.equals(
+ secureRandomProviders[0].getClass()))) {
+ Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1);
+ }
+
+ // Assert that new SecureRandom() and
+ // SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed
+ // by the Linux PRNG-based SecureRandom implementation.
+ SecureRandom rng1 = new SecureRandom();
+ if (!LinuxPRNGSecureRandomProvider.class.equals(
+ rng1.getProvider().getClass())) {
+ throw new SecurityException(
+ "new SecureRandom() backed by wrong Provider: "
+ + rng1.getProvider().getClass());
+ }
+
+ SecureRandom rng2;
+ try {
+ rng2 = SecureRandom.getInstance("SHA1PRNG");
+ } catch (NoSuchAlgorithmException e) {
+ throw new SecurityException("SHA1PRNG not available", e);
+ }
+ if (!LinuxPRNGSecureRandomProvider.class.equals(
+ rng2.getProvider().getClass())) {
+ throw new SecurityException(
+ "SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong"
+ + " Provider: " + rng2.getProvider().getClass());
+ }
+ }
+
+ /**
+ * {@code Provider} of {@code SecureRandom} engines which pass through
+ * all requests to the Linux PRNG.
+ */
+ private static class LinuxPRNGSecureRandomProvider extends Provider {
+
+ public LinuxPRNGSecureRandomProvider() {
+ super("LinuxPRNG",
+ 1.0,
+ "A Linux-specific random number provider that uses"
+ + " /dev/urandom");
+ // Although /dev/urandom is not a SHA-1 PRNG, some apps
+ // explicitly request a SHA1PRNG SecureRandom and we thus need to
+ // prevent them from getting the default implementation whose output
+ // may have low entropy.
+ put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName());
+ put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
+ }
+ }
+
+ /**
+ * {@link SecureRandomSpi} which passes all requests to the Linux PRNG
+ * ({@code /dev/urandom}).
+ */
+ public static class LinuxPRNGSecureRandom extends SecureRandomSpi {
+
+ /*
+ * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed
+ * are passed through to the Linux PRNG (/dev/urandom). Instances of
+ * this class seed themselves by mixing in the current time, PID, UID,
+ * build fingerprint, and hardware serial number (where available) into
+ * Linux PRNG.
+ *
+ * Concurrency: Read requests to the underlying Linux PRNG are
+ * serialized (on sLock) to ensure that multiple threads do not get
+ * duplicated PRNG output.
+ */
+
+ private static final File URANDOM_FILE = new File("/dev/urandom");
+
+ private static final Object sLock = new Object();
+
+ /**
+ * Input stream for reading from Linux PRNG or {@code null} if not yet
+ * opened.
+ *
+ * @GuardedBy("sLock")
+ */
+ private static DataInputStream sUrandomIn;
+
+ /**
+ * Output stream for writing to Linux PRNG or {@code null} if not yet
+ * opened.
+ *
+ * @GuardedBy("sLock")
+ */
+ private static OutputStream sUrandomOut;
+
+ /**
+ * Whether this engine instance has been seeded. This is needed because
+ * each instance needs to seed itself if the client does not explicitly
+ * seed it.
+ */
+ private boolean mSeeded;
+
+ @Override
+ protected void engineSetSeed(byte[] bytes) {
+ try {
+ OutputStream out;
+ synchronized (sLock) {
+ out = getUrandomOutputStream();
+ }
+ out.write(bytes);
+ out.flush();
+ } catch (IOException e) {
+ // On a small fraction of devices /dev/urandom is not writable.
+ // Log and ignore.
+ Log.w(PrngWorkaround.class.getSimpleName(),
+ "Failed to mix seed into " + URANDOM_FILE);
+ } finally {
+ mSeeded = true;
+ }
+ }
+
+ @Override
+ protected void engineNextBytes(byte[] bytes) {
+ if (!mSeeded) {
+ // Mix in the device- and invocation-specific seed.
+ engineSetSeed(generateSeed());
+ }
+
+ try {
+ DataInputStream in;
+ synchronized (sLock) {
+ in = getUrandomInputStream();
+ }
+ synchronized (in) {
+ in.readFully(bytes);
+ }
+ } catch (IOException e) {
+ throw new SecurityException(
+ "Failed to read from " + URANDOM_FILE, e);
+ }
+ }
+
+ @Override
+ protected byte[] engineGenerateSeed(int size) {
+ byte[] seed = new byte[size];
+ engineNextBytes(seed);
+ return seed;
+ }
+
+ private DataInputStream getUrandomInputStream() {
+ synchronized (sLock) {
+ if (sUrandomIn == null) {
+ // NOTE: Consider inserting a BufferedInputStream between
+ // DataInputStream and FileInputStream if you need higher
+ // PRNG output performance and can live with future PRNG
+ // output being pulled into this process prematurely.
+ try {
+ sUrandomIn = new DataInputStream(
+ new FileInputStream(URANDOM_FILE));
+ } catch (IOException e) {
+ throw new SecurityException("Failed to open "
+ + URANDOM_FILE + " for reading", e);
+ }
+ }
+ return sUrandomIn;
+ }
+ }
+
+ private OutputStream getUrandomOutputStream() throws IOException {
+ synchronized (sLock) {
+ if (sUrandomOut == null) {
+ sUrandomOut = new FileOutputStream(URANDOM_FILE);
+ }
+ return sUrandomOut;
+ }
+ }
+ }
+
+ /**
+ * Generates a device- and invocation-specific seed to be mixed into the
+ * Linux PRNG.
+ */
+ private static byte[] generateSeed() {
+ try {
+ ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream();
+ DataOutputStream seedBufferOut =
+ new DataOutputStream(seedBuffer);
+ seedBufferOut.writeLong(System.currentTimeMillis());
+ seedBufferOut.writeLong(System.nanoTime());
+ seedBufferOut.writeInt(Process.myPid());
+ seedBufferOut.writeInt(Process.myUid());
+ seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL);
+ seedBufferOut.close();
+ return seedBuffer.toByteArray();
+ } catch (IOException e) {
+ throw new SecurityException("Failed to generate seed", e);
+ }
+ }
+
+ /**
+ * Gets the hardware serial number of this device.
+ *
+ * @return serial number or {@code null} if not available.
+ */
+ private static String getDeviceSerialNumber() {
+ // We're using the Reflection API because Build.SERIAL is only available
+ // since API Level 9 (Gingerbread, Android 2.3).
+ try {
+ return (String) Build.class.getField("SERIAL").get(null);
+ } catch (Exception ignored) {
+ return null;
+ }
+ }
+
+ private static byte[] getBuildFingerprintAndDeviceSerial() {
+ StringBuilder result = new StringBuilder();
+ String fingerprint = Build.FINGERPRINT;
+ if (fingerprint != null) {
+ result.append(fingerprint);
+ }
+ String serial = getDeviceSerialNumber();
+ if (serial != null) {
+ result.append(serial);
+ }
+ try {
+ return result.toString().getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("UTF-8 encoding not supported");
+ }
+ }
+}
\ No newline at end of file
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/mock_location.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/mock_location.xml
new file mode 100644
index 0000000..fbf2c2e
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/mock_location.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="foo.bar2"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk android:minSdkVersion="14" />
+ <uses-permission android:name="com.example.helloworld.permission" />
+ <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" />
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+ <activity
+ android:label="@string/app_name"
+ android:name=".Foo2Activity" >
+ <intent-filter >
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/namespace5.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/namespace5.xml
new file mode 100644
index 0000000..66be530
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/namespace5.xml
@@ -0,0 +1,18 @@
+<android.support.v7.widget.GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ xmlns:app="http://schemas.android.com/apk/auto-res/"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:columnCount="1"
+ tools:context=".MainActivity" >
+
+ <Button
+ android:id="@+id/button1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:layout_column="0"
+ app:layout_gravity="center"
+ app:layout_row="0"
+ android:text="Button" />
+
+</android.support.v7.widget.GridLayout>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/integer_arrays.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/integer_arrays.xml
new file mode 100644
index 0000000..686bd07
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/integer_arrays.xml
@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <dimen name="used">16dp</dimen>
+
+ <integer-array name="iconsets_array_ids" tools:ignore="UnusedResources">
+ <item>@array/iconset_pixelmixer_basic</item>
+ <item>@array/iconset_dryicons_coquette</item>
+ </integer-array>
+
+ <integer-array name="iconset_pixelmixer_basic">
+ <item>@dimen/used</item>
+ </integer-array>
+
+ <integer-array name="iconset_dryicons_coquette">
+ <item>@dimen/used</item>
+ </integer-array>
+</resources>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/repeated_words.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/repeated_words.xml
new file mode 100644
index 0000000..7196eca
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/repeated_words.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Repeated words -->
+ <string name="permdesc_accessLocationExtraCommands">Allows the app to access
+ extra location provider commands. This may allow the app to to interfere
+ with the operation of the GPS or other location sources.</string>
+ <string name="other">"ü test\n zü zü"</string>
+ <string name="ignore1">android/android/foo"</string>
+ <string name="ignore2">%s/%s/%s</string>
+ <string name="ignore3">"Dial (866) 555 0123\" \n\"Dial 911, 811, ..."</string>
+</resources>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/typos.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/typos.xml
index dd24812..ace76a3 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/typos.xml
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/typos.xml
@@ -22,4 +22,6 @@
<string name="issue39599">"Please take a moment\nto rate ^1"</string>
<!-- escaped separator 2 -->
<string name="issue39599_2">"\nto</string>
-</resources>
\ No newline at end of file
+ <!-- not translatable -->
+ <string name="translatable" translatable="false">"Andriod</string>
+</resources>
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/rules/AppCompatTest.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/rules/AppCompatTest.class.data
new file mode 100644
index 0000000..7961456
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/rules/AppCompatTest.class.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/rules/AppCompatTest.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/rules/AppCompatTest.java.txt
new file mode 100644
index 0000000..5f73f70
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/rules/AppCompatTest.java.txt
@@ -0,0 +1,24 @@
+package test.pkg;
+
+import android.support.v7.app.ActionBarActivity;
+
+public class AppCompatTest extends ActionBarActivity {
+ public void test() {
+ getActionBar(); // ERROR
+ getSupportActionBar(); // OK
+
+ startActionMode(null); // ERROR
+ startSupportActionMode(null); // OK
+
+ requestWindowFeature(0); // ERROR
+ supportRequestWindowFeature(0); // OK
+
+ setProgressBarVisibility(true); // ERROR
+ setProgressBarIndeterminate(true);
+ setProgressBarIndeterminateVisibility(true);
+
+ setSupportProgressBarVisibility(true); // OK
+ setSupportProgressBarIndeterminate(true);
+ setSupportProgressBarIndeterminateVisibility(true);
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/rules/appcompat.jar.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/rules/appcompat.jar.data
new file mode 100644
index 0000000..defc450
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/rules/appcompat.jar.data
Binary files differ
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CheckPermissions.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CheckPermissions.java.txt
new file mode 100644
index 0000000..1d17110
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/CheckPermissions.java.txt
@@ -0,0 +1,17 @@
+package test.pkg;
+
+import android.view.View;
+
+public class CheckPermissions {
+ private void foo() {
+ context.checkCallingOrSelfPermission(Manifest.permission.INTERNET); // WRONG
+ context.checkPermission(Manifest.permission.INTERNET); // UNKNOWN (without type resolution)
+ check(context.checkCallingOrSelfPermission(Manifest.permission.INTERNET)); // OK
+ int check = context.checkCallingOrSelfPermission(Manifest.permission.INTERNET); // OK
+ if (context.checkCallingOrSelfPermission(Manifest.permission.INTERNET) // OK
+ != PackageManager.PERMISSION_GRANTED) {
+ Util.showAlert(context, "Error",
+ "Application requires permission to access the Internet");
+ }
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/DetachedFromWindow.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/DetachedFromWindow.java.txt
new file mode 100644
index 0000000..314f835
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/DetachedFromWindow.java.txt
@@ -0,0 +1,34 @@
+package test.pkg;
+
+import android.view.View;
+
+public class DetachedFromWindow {
+ private static class Test1 extends View {
+ protected void onDetachedFromWindow() {
+ // Error
+ }
+ }
+
+ private static class Test2 extends View {
+ protected void onDetachedFromWindow(int foo) {
+ // OK: not overriding the right method
+ }
+ }
+
+ private static class Test3 extends View {
+ protected void onDetachedFromWindow() {
+ // OK: Calling super
+ super.onDetachedFromWindow();
+ }
+ }
+
+ private static class Test4 extends View {
+ protected void onDetachedFromWindow() {
+ // Error: missing detach call
+ int x = 1;
+ x++;
+ System.out.println(x);
+ }
+ }
+
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/client/api/CompositeIssueRegistryTest.java b/lint/cli/src/test/java/com/android/tools/lint/client/api/CompositeIssueRegistryTest.java
new file mode 100644
index 0000000..339f107
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/client/api/CompositeIssueRegistryTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.lint.client.api;
+
+import static com.android.tools.lint.checks.HardcodedValuesDetector.ISSUE;
+import static com.android.tools.lint.checks.IconDetector.ICON_COLORS;
+import static com.android.tools.lint.checks.IconDetector.ICON_DIP_SIZE;
+import static com.android.tools.lint.checks.ManifestDetector.APPLICATION_ICON;
+import static com.android.tools.lint.checks.ManifestDetector.DEVICE_ADMIN;
+import static com.android.tools.lint.checks.ManifestDetector.DUPLICATE_ACTIVITY;
+
+import com.android.annotations.NonNull;
+import com.android.tools.lint.detector.api.Issue;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public class CompositeIssueRegistryTest extends TestCase {
+ public void test() {
+ IssueRegistry registry1 = new IssueRegistry() {
+ @NonNull
+ @Override
+ public List<Issue> getIssues() {
+ return Collections.singletonList(ISSUE);
+ }
+ };
+ IssueRegistry registry2 = new IssueRegistry() {
+ @NonNull
+ @Override
+ public List<Issue> getIssues() {
+ return Arrays.asList(APPLICATION_ICON, DEVICE_ADMIN, DUPLICATE_ACTIVITY);
+ }
+ };
+ IssueRegistry registry3 = new IssueRegistry() {
+ @NonNull
+ @Override
+ public List<Issue> getIssues() {
+ return Arrays.asList(ICON_COLORS, ICON_DIP_SIZE);
+ }
+ };
+
+ assertEquals(Collections.singletonList(ISSUE),
+ new CompositeIssueRegistry(Collections.singletonList(registry1)).getIssues());
+
+ assertEquals(Arrays.asList(ISSUE, ICON_COLORS, ICON_DIP_SIZE),
+ new CompositeIssueRegistry(Arrays.asList(registry1, registry3)).getIssues());
+
+ assertEquals(Arrays.asList(ISSUE, APPLICATION_ICON, DEVICE_ADMIN, DUPLICATE_ACTIVITY,
+ ICON_COLORS, ICON_DIP_SIZE), new CompositeIssueRegistry(Arrays.asList(registry1,
+ registry2, registry3)).getIssues());
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/client/api/CustomRuleTest.java b/lint/cli/src/test/java/com/android/tools/lint/client/api/CustomRuleTest.java
new file mode 100644
index 0000000..4485d8e
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/client/api/CustomRuleTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.lint.client.api;
+
+import com.android.annotations.NonNull;
+import com.android.tools.lint.checks.AbstractCheckTest;
+import com.android.tools.lint.checks.HardcodedValuesDetector;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Project;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+
+public class CustomRuleTest extends AbstractCheckTest {
+ private List<File> myGlobalJars = Collections.emptyList();
+ private List<File> myProjectJars = Collections.emptyList();
+
+ public void test() throws Exception {
+ File projectDir = getProjectDir(null,
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "rules/appcompat.jar.data=>lint.jar",
+ "rules/AppCompatTest.java.txt=>src/test/pkg/AppCompatTest.java",
+ "rules/AppCompatTest.class.data=>bin/classes/test/pkg/AppCompatTest.class"
+ );
+
+ File lintJar = new File(projectDir, "lint.jar");
+ assertTrue(lintJar.getPath(), lintJar.isFile());
+
+ myProjectJars = Collections.singletonList(lintJar);
+ assertEquals(""
+ + "src/test/pkg/AppCompatTest.java:7: Warning: Should use getSupportActionBar instead of getActionBar name [AppCompatMethod]\n"
+ + " getActionBar(); // ERROR\n"
+ + " ~~~~~~~~~~~~\n"
+ + "src/test/pkg/AppCompatTest.java:10: Warning: Should use startSupportActionMode instead of startActionMode name [AppCompatMethod]\n"
+ + " startActionMode(null); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/AppCompatTest.java:13: Warning: Should use supportRequestWindowFeature instead of requestWindowFeature name [AppCompatMethod]\n"
+ + " requestWindowFeature(0); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/AppCompatTest.java:16: Warning: Should use setSupportProgressBarVisibility instead of setProgressBarVisibility name [AppCompatMethod]\n"
+ + " setProgressBarVisibility(true); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/AppCompatTest.java:17: Warning: Should use setSupportProgressBarIndeterminate instead of setProgressBarIndeterminate name [AppCompatMethod]\n"
+ + " setProgressBarIndeterminate(true);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/AppCompatTest.java:18: Warning: Should use setSupportProgressBarIndeterminateVisibility instead of setProgressBarIndeterminateVisibility name [AppCompatMethod]\n"
+ + " setProgressBarIndeterminateVisibility(true);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 6 warnings\n",
+ checkLint(Collections.singletonList(projectDir)));
+ }
+
+ public void test2() throws Exception {
+ File projectDir = getProjectDir(null,
+ "apicheck/classpath=>.classpath",
+ "apicheck/minsdk1.xml=>AndroidManifest.xml",
+ "rules/appcompat.jar.data=>lint.jar",
+ "rules/AppCompatTest.java.txt=>src/test/pkg/AppCompatTest.java",
+ "rules/AppCompatTest.class.data=>bin/classes/test/pkg/AppCompatTest.class"
+ );
+
+ File lintJar = new File(projectDir, "lint.jar");
+ assertTrue(lintJar.getPath(), lintJar.isFile());
+
+ myGlobalJars = Collections.singletonList(lintJar);
+ assertEquals(""
+ + "src/test/pkg/AppCompatTest.java:7: Warning: Should use getSupportActionBar instead of getActionBar name [AppCompatMethod]\n"
+ + " getActionBar(); // ERROR\n"
+ + " ~~~~~~~~~~~~\n"
+ + "src/test/pkg/AppCompatTest.java:10: Warning: Should use startSupportActionMode instead of startActionMode name [AppCompatMethod]\n"
+ + " startActionMode(null); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/AppCompatTest.java:13: Warning: Should use supportRequestWindowFeature instead of requestWindowFeature name [AppCompatMethod]\n"
+ + " requestWindowFeature(0); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/AppCompatTest.java:16: Warning: Should use setSupportProgressBarVisibility instead of setProgressBarVisibility name [AppCompatMethod]\n"
+ + " setProgressBarVisibility(true); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/AppCompatTest.java:17: Warning: Should use setSupportProgressBarIndeterminate instead of setProgressBarIndeterminate name [AppCompatMethod]\n"
+ + " setProgressBarIndeterminate(true);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "src/test/pkg/AppCompatTest.java:18: Warning: Should use setSupportProgressBarIndeterminateVisibility instead of setProgressBarIndeterminateVisibility name [AppCompatMethod]\n"
+ + " setProgressBarIndeterminateVisibility(true);\n"
+ + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ + "0 errors, 6 warnings\n",
+ checkLint(Collections.singletonList(projectDir)));
+ }
+
+ @Override
+ protected TestLintClient createClient() {
+ return new TestLintClient() {
+ @NonNull
+ @Override
+ public List<File> findGlobalRuleJars() {
+ return myGlobalJars;
+ }
+
+ @NonNull
+ @Override
+ public List<File> findRuleJars(@NonNull Project project) {
+ return myProjectJars;
+ }
+ };
+ }
+
+ @Override
+ protected boolean isEnabled(Issue issue) {
+ // Allow other issues than the one returned by getDetector below
+ return true;
+ }
+
+ @Override
+ protected Detector getDetector() {
+ return new HardcodedValuesDetector();
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/client/api/JarFileIssueRegistryTest.java b/lint/cli/src/test/java/com/android/tools/lint/client/api/JarFileIssueRegistryTest.java
new file mode 100644
index 0000000..4a73420
--- /dev/null
+++ b/lint/cli/src/test/java/com/android/tools/lint/client/api/JarFileIssueRegistryTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.lint.client.api;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.checks.AbstractCheckTest;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Severity;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.StringWriter;
+
+@SuppressWarnings("SpellCheckingInspection")
+public class JarFileIssueRegistryTest extends AbstractCheckTest {
+ public void testError() {
+ try {
+ JarFileIssueRegistry.get(new TestLintClient(), new File("bogus"));
+ fail("Expected exception for bogus path");
+ } catch (Throwable t) {
+ // pass
+ }
+ }
+
+ public void testCached() throws Exception {
+ File targetDir = Files.createTempDir();
+ File file1 = getTestfile(targetDir, "rules/appcompat.jar.data");
+ File file2 = getTestfile(targetDir, "apicheck/unsupported.jar.data");
+ assertTrue(file1.getPath(), file1.exists());
+ final StringWriter mLoggedWarnings = new StringWriter();
+ TestLintClient client = new TestLintClient() {
+ @Override
+ public void log(@NonNull Severity severity, @Nullable Throwable exception,
+ @Nullable String format, @Nullable Object... args) {
+ if (format != null) {
+ mLoggedWarnings.append(String.format(format, args));
+ }
+ }
+
+ };
+ IssueRegistry registry1 = JarFileIssueRegistry.get(client, file1);
+ IssueRegistry registry2 = JarFileIssueRegistry.get(client, new File(file1.getPath()));
+ assertSame(registry1, registry2);
+ IssueRegistry registry3 = JarFileIssueRegistry.get(client, file2);
+ assertNotSame(registry1, registry3);
+
+ assertEquals(1, registry1.getIssues().size());
+ assertEquals("AppCompatMethod", registry1.getIssues().get(0).getId());
+
+ assertEquals(
+ "Custom lint rule jar " + file2.getPath() + " does not contain a valid "
+ + "registry manifest key (Lint-Registry).\n"
+ + "Either the custom jar is invalid, or it uses an outdated API not "
+ + "supported this lint client", mLoggedWarnings.toString());
+ }
+
+ @Override
+ protected Detector getDetector() {
+ fail("Not used in this test");
+ return null;
+ }
+}
diff --git a/lint/cli/src/test/java/com/android/tools/lint/client/api/ProjectTest.java b/lint/cli/src/test/java/com/android/tools/lint/client/api/ProjectTest.java
index 4f3c9ef..c919321 100644
--- a/lint/cli/src/test/java/com/android/tools/lint/client/api/ProjectTest.java
+++ b/lint/cli/src/test/java/com/android/tools/lint/client/api/ProjectTest.java
@@ -16,12 +16,18 @@
package com.android.tools.lint.client.api;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.tools.lint.checks.AbstractCheckTest;
import com.android.tools.lint.checks.UnusedResourceDetector;
import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.Severity;
import java.io.File;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
public class ProjectTest extends AbstractCheckTest {
@Override
@@ -53,6 +59,94 @@
checkLint(Arrays.asList(master, library)));
}
+ public void testInvalidLibraryReferences1() throws Exception {
+ TestClient client = new TestClient();
+ File dir = new File("project");
+ TestProject project1 = new TestProject(client, dir);
+ client.registerProject(dir, project1);
+ project1.setDirectLibraries(Collections.<Project>singletonList(project1));
+ List<Project> libraries = project1.getAllLibraries();
+ assertNotNull(libraries);
+ assertEquals(
+ "Warning: Internal lint error: cyclic library dependency for Project [dir=project]",
+ client.getLoggedOutput());
+ }
+
+ public void testInvalidLibraryReferences2() throws Exception {
+ TestClient client = new TestClient();
+ File dir1 = new File("project1");
+ File dir2 = new File("project2");
+ TestProject project1 = new TestProject(client, dir1);
+ client.registerProject(dir1, project1);
+ TestProject project2 = new TestProject(client, dir2);
+ client.registerProject(dir2, project2);
+ project2.setDirectLibraries(Collections.<Project>singletonList(project1));
+ project1.setDirectLibraries(Collections.<Project>singletonList(project2));
+ List<Project> libraries = project1.getAllLibraries();
+ assertNotNull(libraries);
+ assertEquals(
+ "Warning: Internal lint error: cyclic library dependency for Project [dir=project1]",
+ client.getLoggedOutput());
+ assertEquals(1, libraries.size());
+ assertSame(project2, libraries.get(0));
+ assertEquals(1, project2.getAllLibraries().size());
+ assertSame(project1, project2.getAllLibraries().get(0));
+ }
+
+ public void testOkLibraryReferences() throws Exception {
+ TestClient client = new TestClient();
+ File dir1 = new File("project1");
+ File dir2 = new File("project2");
+ File dir3 = new File("project3");
+ TestProject project1 = new TestProject(client, dir1);
+ client.registerProject(dir1, project1);
+ TestProject project2 = new TestProject(client, dir2);
+ client.registerProject(dir2, project2);
+ TestProject project3 = new TestProject(client, dir3);
+ client.registerProject(dir3, project3);
+ project1.setDirectLibraries(Arrays.<Project>asList(project2, project3));
+ project2.setDirectLibraries(Collections.<Project>singletonList(project3));
+ project3.setDirectLibraries(Collections.<Project>emptyList());
+ List<Project> libraries = project1.getAllLibraries();
+ assertNotNull(libraries);
+ assertEquals(
+ "",
+ client.getLoggedOutput());
+ assertEquals(2, libraries.size());
+ assertTrue(libraries.contains(project2));
+ assertTrue(libraries.contains(project3));
+ assertEquals(1, project2.getAllLibraries().size());
+ assertSame(project3, project2.getAllLibraries().get(0));
+ assertTrue(project3.getAllLibraries().isEmpty());
+ }
+
+ private class TestClient extends TestLintClient {
+ @SuppressWarnings("StringBufferField")
+ private StringBuilder mLog = new StringBuilder();
+
+ @Override
+ public void log(@NonNull Severity severity, @Nullable Throwable exception,
+ @Nullable String format, @Nullable Object... args) {
+ assertNotNull(format);
+ mLog.append(severity.getDescription()).append(": ");
+ mLog.append(String.format(format, args));
+ }
+
+ public String getLoggedOutput() {
+ return mLog.toString();
+ }
+ }
+
+ private static class TestProject extends Project {
+ protected TestProject(@NonNull LintClient client, @NonNull File dir) {
+ super(client, dir, dir);
+ }
+
+ public void setDirectLibraries(List<Project> libraries) {
+ mDirectLibraries = libraries;
+ }
+ }
+
@Override
protected Detector getDetector() {
return new UnusedResourceDetector();
diff --git a/lint/libs/lint-api/build.gradle b/lint/libs/lint-api/build.gradle
index 5aeeddd..1d75042 100644
--- a/lint/libs/lint-api/build.gradle
+++ b/lint/libs/lint-api/build.gradle
@@ -1,10 +1,14 @@
+apply plugin: 'java'
+apply plugin: 'distrib'
+
group = 'com.android.tools.lint'
archivesBaseName = 'lint-api'
dependencies {
compile project(':sdk-common')
+ compile project(':builder-model')
- compile files("$rootProject.ext.androidRootDir/prebuilts/tools/common/lombok-ast/lombok-ast-0.2.1.jar")
+ compile 'com.android.tools.external.lombok:lombok-ast:0.2.1'
compile 'org.ow2.asm:asm:4.0'
compile 'org.ow2.asm:asm-tree:4.0'
}
@@ -18,45 +22,10 @@
from 'NOTICE'
}
-uploadArchives {
- repositories {
- mavenDeployer {
- beforeDeployment { MavenDeployment deployment ->
- if (!project.has("release")) {
- throw new StopExecutionException("uploadArchives must be called with the release.gradle init script")
- }
+project.ext.pomName = 'Android Tools Lint API'
+project.ext.pomDesc = 'API to build lint checks'
- signing.signPom(deployment)
- }
+apply from: '../../../baseVersion.gradle'
+apply from: '../../../publish.gradle'
+apply from: '../../../javadoc.gradle'
- repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
- authentication(userName: project.ext.sonatypeUsername, password: project.ext.sonatypePassword)
- }
-
- pom.project {
- name 'Android Tools Lint API'
- description 'API to build lint checks'
- url 'http://tools.android.com'
- inceptionYear '2007'
-
- licenses {
- license {
- name 'The Apache Software License, Version 2.0'
- url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
- distribution 'repo'
- }
- }
-
- scm {
- url "https://android.googlesource.com/platform/tools/base"
- connection "git://android.googlesource.com/platform/tools/base.git"
- }
- developers {
- developer {
- name 'The Android Open Source Project'
- }
- }
- }
- }
- }
-}
diff --git a/lint/libs/lint-api/lint-api.iml b/lint/libs/lint-api/lint-api.iml
index 283ede47..50315be 100644
--- a/lint/libs/lint-api/lint-api.iml
+++ b/lint/libs/lint-api/lint-api.iml
@@ -12,6 +12,7 @@
<orderEntry type="library" exported="" name="asm-tools" level="project" />
<orderEntry type="library" exported="" name="lombok-ast" level="project" />
<orderEntry type="module" module-name="sdk-common" exported="" />
+ <orderEntry type="library" exported="" name="builder-model" level="project" />
</component>
</module>
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/CompositeIssueRegistry.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/CompositeIssueRegistry.java
new file mode 100644
index 0000000..70b6a09
--- /dev/null
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/CompositeIssueRegistry.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.lint.client.api;
+
+import com.android.annotations.NonNull;
+import com.android.tools.lint.detector.api.Issue;
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+/**
+ * Registry which merges many issue registries into one, and presents a unified list
+ * of issues.
+ * <p>
+ * <b>NOTE: This is not a public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+class CompositeIssueRegistry extends IssueRegistry {
+ private final List<IssueRegistry> myRegistries;
+ private List<Issue> myIssues;
+
+ public CompositeIssueRegistry(@NonNull List<IssueRegistry> registries) {
+ myRegistries = registries;
+ }
+
+ @NonNull
+ @Override
+ public List<Issue> getIssues() {
+ if (myIssues == null) {
+ List<Issue> issues = Lists.newArrayListWithExpectedSize(200);
+ for (IssueRegistry registry : myRegistries) {
+ issues.addAll(registry.getIssues());
+ }
+ myIssues = issues;
+ }
+
+ return myIssues;
+ }
+}
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/IssueRegistry.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/IssueRegistry.java
index 7d81700..9e94758 100644
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/IssueRegistry.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/IssueRegistry.java
@@ -37,7 +37,8 @@
import java.util.Map;
import java.util.Set;
-/** Registry which provides a list of checks to be performed on an Android project
+/**
+ * Registry which provides a list of checks to be performed on an Android project
* <p>
* <b>NOTE: This is not a public or final API; if you rely on this be prepared
* to adjust your code for the next tools release.</b>
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JarFileIssueRegistry.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JarFileIssueRegistry.java
new file mode 100644
index 0000000..0262691
--- /dev/null
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JarFileIssueRegistry.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.lint.client.api;
+
+import com.android.annotations.NonNull;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.utils.SdkUtils;
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.ref.SoftReference;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+
+/**
+ * <p> An {@link IssueRegistry} for a custom lint rule jar file. The rule jar should provide a
+ * manifest entry with the key {@code Lint-Registry} and the value of the fully qualified name of an
+ * implementation of {@link IssueRegistry} (with a default constructor). </p>
+ *
+ * <p> NOTE: The custom issue registry should not extend this file; it should be a plain
+ * IssueRegistry! This file is used internally to wrap the given issue registry.</p>
+ */
+class JarFileIssueRegistry extends IssueRegistry {
+ /**
+ * Manifest constant for declaring an issue provider. Example: Lint-Registry:
+ * foo.bar.CustomIssueRegistry
+ */
+ private static final String MF_LINT_REGISTRY = "Lint-Registry"; //$NON-NLS-1$
+
+ private static Map<File, SoftReference<JarFileIssueRegistry>> sCache;
+
+ private final List<Issue> myIssues;
+
+ @NonNull
+ static IssueRegistry get(@NonNull LintClient client, @NonNull File jarFile) throws IOException,
+ ClassNotFoundException, IllegalAccessException, InstantiationException {
+ if (sCache == null) {
+ sCache = new HashMap<File, SoftReference<JarFileIssueRegistry>>();
+ } else {
+ SoftReference<JarFileIssueRegistry> reference = sCache.get(jarFile);
+ if (reference != null) {
+ JarFileIssueRegistry registry = reference.get();
+ if (registry != null) {
+ return registry;
+ }
+ }
+ }
+
+ JarFileIssueRegistry registry = new JarFileIssueRegistry(client, jarFile);
+ sCache.put(jarFile, new SoftReference<JarFileIssueRegistry>(registry));
+ return registry;
+ }
+
+ private JarFileIssueRegistry(@NonNull LintClient client, @NonNull File file)
+ throws IOException, ClassNotFoundException, IllegalAccessException,
+ InstantiationException {
+ myIssues = Lists.newArrayList();
+ JarFile jarFile = null;
+ try {
+ jarFile = new JarFile(file);
+ Manifest manifest = jarFile.getManifest();
+ Attributes attrs = manifest.getMainAttributes();
+ Object object = attrs.get(new Attributes.Name(MF_LINT_REGISTRY));
+ if (object instanceof String) {
+ String className = (String) object;
+ // Make a class loader for this jar
+ URL url = SdkUtils.fileToUrl(file);
+ URLClassLoader loader = new URLClassLoader(new URL[]{url},
+ JarFileIssueRegistry.class.getClassLoader());
+ Class<?> registryClass = Class.forName(className, true, loader);
+ IssueRegistry registry = (IssueRegistry) registryClass.newInstance();
+ myIssues.addAll(registry.getIssues());
+ } else {
+ client.log(Severity.ERROR, null,
+ "Custom lint rule jar %1$s does not contain a valid registry manifest key " +
+ "(%2$s).\n" +
+ "Either the custom jar is invalid, or it uses an outdated API not supported " +
+ "this lint client", file.getPath(), MF_LINT_REGISTRY);
+ }
+ } finally {
+ if (jarFile != null) {
+ jarFile.close();
+ }
+ }
+ }
+
+ @NonNull
+ @Override
+ public List<Issue> getIssues() {
+ return myIssues;
+ }
+}
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JavaVisitor.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JavaVisitor.java
index be52510..96a973d 100644
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JavaVisitor.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/JavaVisitor.java
@@ -24,6 +24,7 @@
import com.android.tools.lint.detector.api.Detector.JavaScanner;
import com.android.tools.lint.detector.api.Detector.XmlScanner;
import com.android.tools.lint.detector.api.JavaContext;
+import com.google.common.collect.Maps;
import java.io.File;
import java.util.ArrayList;
@@ -133,7 +134,7 @@
private static final int SAME_TYPE_COUNT = 8;
private final Map<String, List<VisitingDetector>> mMethodDetectors =
- new HashMap<String, List<VisitingDetector>>();
+ Maps.newHashMapWithExpectedSize(24);
private final List<VisitingDetector> mResourceFieldDetectors =
new ArrayList<VisitingDetector>();
private final List<VisitingDetector> mAllDetectors;
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintClient.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintClient.java
index 006d2d5..307706e 100644
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintClient.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintClient.java
@@ -16,16 +16,21 @@
package com.android.tools.lint.client.api;
+import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
+import static com.android.SdkConstants.BIN_FOLDER;
import static com.android.SdkConstants.CLASS_FOLDER;
+import static com.android.SdkConstants.DOT_AAR;
import static com.android.SdkConstants.DOT_JAR;
import static com.android.SdkConstants.GEN_FOLDER;
import static com.android.SdkConstants.LIBS_FOLDER;
import static com.android.SdkConstants.RES_FOLDER;
import static com.android.SdkConstants.SRC_FOLDER;
+import static com.android.tools.lint.detector.api.LintUtils.endsWith;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.sdk.SdkVersionInfo;
+import com.android.prefs.AndroidLocation;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkManager;
import com.android.tools.lint.detector.api.Context;
@@ -590,6 +595,35 @@
return project;
}
+ /**
+ * Registers the given project for the given directory. This can
+ * be used when projects are initialized outside of the client itself.
+ *
+ * @param dir the directory of the project, which must be unique
+ * @param project the project
+ */
+ public void registerProject(@NonNull File dir, @NonNull Project project) {
+ File canonicalDir = dir;
+ try {
+ // Attempt to use the canonical handle for the file, in case there
+ // are symlinks etc present (since when handling library projects,
+ // we also call getCanonicalFile to compute the result of appending
+ // relative paths, which can then resolve symlinks and end up with
+ // a different prefix)
+ canonicalDir = dir.getCanonicalFile();
+ } catch (IOException ioe) {
+ // pass
+ }
+
+
+ if (mDirToProject == null) {
+ mDirToProject = new HashMap<File, Project>();
+ } else {
+ assert !mDirToProject.containsKey(dir) : dir;
+ }
+ mDirToProject.put(canonicalDir, project);
+ }
+
private Set<File> mProjectDirs = Sets.newHashSet();
/**
@@ -634,7 +668,11 @@
if (sdkHome != null) {
StdLogger log = new StdLogger(Level.WARNING);
SdkManager manager = SdkManager.createManager(sdkHome.getPath(), log);
- mTargets = manager.getTargets();
+ if (manager != null) {
+ mTargets = manager.getTargets();
+ } else {
+ mTargets = new IAndroidTarget[0];
+ }
} else {
mTargets = new IAndroidTarget[0];
}
@@ -697,6 +735,7 @@
* @param superClassName the name of the super class to compare to
* @return true if the class of the given name extends the given super class
*/
+ @SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
@Nullable
public Boolean isSubclassOf(
@NonNull Project project,
@@ -704,4 +743,92 @@
String superClassName) {
return null;
}
+
+ /**
+ * Finds any custom lint rule jars that should be included for analysis,
+ * regardless of project.
+ * <p>
+ * The default implementation locates custom lint jars in ~/.android/lint/ and
+ * in $ANDROID_LINT_JARS
+ *
+ * @return a list of rule jars (possibly empty).
+ */
+ @SuppressWarnings("MethodMayBeStatic") // Intentionally instance method so it can be overridden
+ @NonNull
+ public List<File> findGlobalRuleJars() {
+ // Look for additional detectors registered by the user, via
+ // (1) an environment variable (useful for build servers etc), and
+ // (2) via jar files in the .android/lint directory
+ List<File> files = null;
+ try {
+ String androidHome = AndroidLocation.getFolder();
+ File lint = new File(androidHome + File.separator + "lint"); //$NON-NLS-1$
+ if (lint.exists()) {
+ File[] list = lint.listFiles();
+ if (list != null) {
+ for (File jarFile : list) {
+ if (endsWith(jarFile.getName(), DOT_JAR)) {
+ if (files == null) {
+ files = new ArrayList<File>();
+ }
+ files.add(jarFile);
+ }
+ }
+ }
+ }
+ } catch (AndroidLocation.AndroidLocationException e) {
+ // Ignore -- no android dir, so no rules to load.
+ }
+
+ String lintClassPath = System.getenv("ANDROID_LINT_JARS"); //$NON-NLS-1$
+ if (lintClassPath != null && !lintClassPath.isEmpty()) {
+ String[] paths = lintClassPath.split(File.pathSeparator);
+ for (String path : paths) {
+ File jarFile = new File(path);
+ if (jarFile.exists()) {
+ if (files == null) {
+ files = new ArrayList<File>();
+ } else if (files.contains(jarFile)) {
+ continue;
+ }
+ files.add(jarFile);
+ }
+ }
+ }
+
+ return files != null ? files : Collections.<File>emptyList();
+ }
+
+ /**
+ * Finds any custom lint rule jars that should be included for analysis
+ * in the given project
+ *
+ * @param project the project to look up rule jars from
+ * @return a list of rule jars (possibly empty).
+ */
+ @SuppressWarnings("MethodMayBeStatic") // Intentionally instance method so it can be overridden
+ @NonNull
+ public List<File> findRuleJars(@NonNull Project project) {
+ if (project.getDir().getPath().endsWith(DOT_AAR)) {
+ File lintJar = new File(project.getDir(), "lint.jar"); //$NON-NLS-1$
+ if (lintJar.exists()) {
+ return Collections.singletonList(lintJar);
+ }
+ }
+
+ return Collections.emptyList();
+ }
+
+ /**
+ * Returns true if the given directory is a lint project directory.
+ * By default, a project directory is the directory containing a manifest file,
+ * but in Gradle projects for example it's the root gradle directory.
+ *
+ * @param dir the directory to check
+ * @return true if the directory represents a lint project
+ */
+ @SuppressWarnings("MethodMayBeStatic") // Intentionally instance method so it can be overridden
+ public boolean isProjectDirectory(@NonNull File dir) {
+ return LintUtils.isManifestFolder(dir) || Project.isAospFrameworksProject(dir);
+ }
}
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintDriver.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintDriver.java
index 430cdb1..e0a2588 100644
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintDriver.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintDriver.java
@@ -56,8 +56,10 @@
import com.google.common.base.CharMatcher;
import com.google.common.base.Splitter;
import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
import com.google.common.io.ByteStreams;
import com.google.common.io.Closeables;
@@ -129,7 +131,7 @@
private final LintClient mClient;
private LintRequest mRequest;
- private final IssueRegistry mRegistry;
+ private IssueRegistry mRegistry;
private volatile boolean mCanceled;
private EnumSet<Scope> mScope;
private List<? extends Detector> mApplicableDetectors;
@@ -370,7 +372,10 @@
mScope = mRequest.getScope();
Collection<Project> projects;
try {
- projects = computeProjects(mRequest.getFiles());
+ projects = mRequest.getProjects();
+ if (projects == null) {
+ projects = computeProjects(mRequest.getFiles());
+ }
} catch (CircularDependencyException e) {
mCurrentProject = e.getProject();
if (mCurrentProject != null) {
@@ -389,37 +394,10 @@
return;
}
+ registerCustomRules(projects);
+
if (mScope == null) {
- // Infer the scope
- mScope = EnumSet.noneOf(Scope.class);
- for (Project project : projects) {
- List<File> subset = project.getSubset();
- if (subset != null) {
- for (File file : subset) {
- String name = file.getName();
- if (name.equals(ANDROID_MANIFEST_XML)) {
- mScope.add(Scope.MANIFEST);
- } else if (name.endsWith(DOT_XML)) {
- mScope.add(Scope.RESOURCE_FILE);
- } else if (name.equals(RES_FOLDER)
- || file.getParent().equals(RES_FOLDER)) {
- mScope.add(Scope.ALL_RESOURCE_FILES);
- mScope.add(Scope.RESOURCE_FILE);
- } else if (name.endsWith(DOT_JAVA)) {
- mScope.add(Scope.JAVA_FILE);
- } else if (name.endsWith(DOT_CLASS)) {
- mScope.add(Scope.CLASS_FILE);
- } else if (name.equals(OLD_PROGUARD_FILE)
- || name.equals(FN_PROJECT_PROGUARD_FILE)) {
- mScope.add(Scope.PROGUARD_FILE);
- }
- }
- } else {
- // Specified a full project: just use the full project scope
- mScope = Scope.ALL;
- break;
- }
- }
+ mScope = Scope.infer(projects);
}
fireEvent(EventType.STARTING, null);
@@ -446,6 +424,34 @@
fireEvent(mCanceled ? EventType.CANCELED : EventType.COMPLETED, null);
}
+ private void registerCustomRules(Collection<Project> projects) {
+ // Look at the various projects, and if any of them provide a custom
+ // lint jar, "add" them (this will replace the issue registry with
+ // a CompositeIssueRegistry containing the original issue registry
+ // plus JarFileIssueRegistry instances for each lint jar
+ Set<File> jarFiles = Sets.newHashSet();
+ for (Project project : projects) {
+ jarFiles.addAll(mClient.findRuleJars(project));
+ }
+
+ jarFiles.addAll(mClient.findGlobalRuleJars());
+
+ if (!jarFiles.isEmpty()) {
+ List<IssueRegistry> registries = Lists.newArrayListWithExpectedSize(jarFiles.size());
+ registries.add(mRegistry);
+ for (File jarFile : jarFiles) {
+ try {
+ registries.add(JarFileIssueRegistry.get(mClient, jarFile));
+ } catch (Throwable e) {
+ mClient.log(e, "Could not load custom rule jar file %1$s", jarFile);
+ }
+ }
+ if (registries.size() > 1) { // the first item is mRegistry itself
+ mRegistry = new CompositeIssueRegistry(registries);
+ }
+ }
+ }
+
private void runExtraPhases(Project project) {
// Did any detectors request another phase?
if (mRepeatingDetectors != null) {
@@ -669,7 +675,6 @@
}
}
-
for (File file : files) {
if (file.isDirectory()) {
File rootDir = sharedRoot;
@@ -692,18 +697,18 @@
// right thing, which is to see if you're pointing right at a project or
// right within it (say at the src/ or res/) folder, and if not, you're
// hopefully pointing at a project tree that you want to scan recursively.
- if (LintUtils.isProjectDir(file)) {
+ if (mClient.isProjectDirectory(file)) {
registerProjectFile(fileToProject, file, file, rootDir);
continue;
} else {
File parent = file.getParentFile();
if (parent != null) {
- if (LintUtils.isProjectDir(parent)) {
+ if (mClient.isProjectDirectory(parent)) {
registerProjectFile(fileToProject, file, parent, parent);
continue;
} else {
parent = parent.getParentFile();
- if (parent != null && LintUtils.isProjectDir(parent)) {
+ if (parent != null && mClient.isProjectDirectory(parent)) {
registerProjectFile(fileToProject, file, parent, parent);
continue;
}
@@ -717,7 +722,7 @@
// Pointed at a file: Search upwards for the containing project
File parent = file.getParentFile();
while (parent != null) {
- if (LintUtils.isProjectDir(parent)) {
+ if (mClient.isProjectDirectory(parent)) {
registerProjectFile(fileToProject, file, parent, parent);
break;
}
@@ -798,7 +803,7 @@
return;
}
- if (LintUtils.isProjectDir(dir)) {
+ if (mClient.isProjectDirectory(dir)) {
registerProjectFile(fileToProject, dir, dir, rootDir);
} else {
File[] files = dir.listFiles();
@@ -891,8 +896,7 @@
private void runFileDetectors(@NonNull Project project, @Nullable Project main) {
// Look up manifest information (but not for library projects)
- File manifestFile = project.getManifestFile();
- if (manifestFile != null) {
+ for (File manifestFile : project.getManifestFiles()) {
XmlContext context = new XmlContext(this, project, main, manifestFile, null);
IDomParser parser = mClient.getDomParser();
if (parser != null) {
@@ -996,35 +1000,7 @@
private void checkProGuard(Project project, Project main) {
List<Detector> detectors = mScopeDetectors.get(Scope.PROGUARD_FILE);
if (detectors != null) {
- Project p = main != null ? main : project;
- List<File> files = new ArrayList<File>();
- String paths = p.getProguardPath();
- if (paths != null) {
- Splitter splitter = Splitter.on(CharMatcher.anyOf(":;")); //$NON-NLS-1$
- for (String path : splitter.split(paths)) {
- if (path.contains("${")) { //$NON-NLS-1$
- // Don't analyze the global/user proguard files
- continue;
- }
- File file = new File(path);
- if (!file.isAbsolute()) {
- file = new File(project.getDir(), path);
- }
- if (file.exists()) {
- files.add(file);
- }
- }
- }
- if (files.isEmpty()) {
- File file = new File(project.getDir(), OLD_PROGUARD_FILE);
- if (file.exists()) {
- files.add(file);
- }
- file = new File(project.getDir(), FN_PROJECT_PROGUARD_FILE);
- if (file.exists()) {
- files.add(file);
- }
- }
+ List<File> files = project.getProguardFiles();
for (File file : files) {
Context context = new Context(this, project, main, file);
fireEvent(EventType.SCANNING_FILE, context);
@@ -1654,7 +1630,7 @@
@Nullable Project main,
@NonNull File res,
@NonNull List<ResourceXmlDetector> checks) {
- assert res.isDirectory();
+ assert res.isDirectory() : res;
File[] resourceDirs = res.listFiles();
if (resourceDirs == null) {
return;
@@ -1973,6 +1949,29 @@
protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
return mDelegate.createProject(dir, referenceDir);
}
+
+ @NonNull
+ @Override
+ public List<File> findGlobalRuleJars() {
+ return mDelegate.findGlobalRuleJars();
+ }
+
+ @NonNull
+ @Override
+ public List<File> findRuleJars(@NonNull Project project) {
+ return mDelegate.findRuleJars(project);
+ }
+
+ @Override
+ public boolean isProjectDirectory(@NonNull File dir) {
+ return mDelegate.isProjectDirectory(dir);
+ }
+
+ @Override
+ public void registerProject(@NonNull File dir, @NonNull Project project) {
+ log(Severity.WARNING, null, "Too late to register projects");
+ mDelegate.registerProject(dir, project);
+ }
}
/**
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintRequest.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintRequest.java
index 4b09d88..0816bd5 100644
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintRequest.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/LintRequest.java
@@ -18,12 +18,15 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.android.tools.lint.detector.api.Project;
import com.android.tools.lint.detector.api.Scope;
import com.google.common.annotations.Beta;
import java.io.File;
+import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
+import java.util.Set;
/**
* Information about a request to run lint
@@ -34,16 +37,19 @@
@Beta
public class LintRequest {
@NonNull
- private final LintClient mClient;
+ protected final LintClient mClient;
@NonNull
- private final List<File> mFiles;
+ protected final List<File> mFiles;
@Nullable
- private EnumSet<Scope> mScope;
+ protected EnumSet<Scope> mScope;
@Nullable
- private Boolean mReleaseMode;
+ protected Boolean mReleaseMode;
+
+ @Nullable
+ protected Collection<Project> mProjects;
/**
* Creates a new {@linkplain LintRequest}, to be passed to a {@link LintDriver}
@@ -52,9 +58,6 @@
* @param files the set of files to check with lint. This can reference Android projects,
* or directories containing Android projects, or individual XML or Java files
* (typically for incremental IDE analysis).
- *
- * @return the set of files to check, should not be empty
- *
*/
public LintRequest(@NonNull LintClient client, @NonNull List<File> files) {
mClient = client;
@@ -132,4 +135,33 @@
mReleaseMode = releaseMode;
return this;
}
+
+ /**
+ * Gets the projects for the lint requests. This is optional; if not provided lint will search
+ * the {@link #getFiles()} directories and look for projects via {@link
+ * LintClient#isProjectDirectory(java.io.File)}. However, this method allows a lint client to
+ * set up all the projects ahead of time, and associate those projects with native resources
+ * (in an IDE for example, each lint project can be associated with the corresponding IDE
+ * project).
+ *
+ * @return a collection of projects, or null
+ */
+ @Nullable
+ public Collection<Project> getProjects() {
+ return mProjects;
+ }
+
+ /**
+ * Sets the projects for the lint requests. This is optional; if not provided lint will search
+ * the {@link #getFiles()} directories and look for projects via {@link
+ * LintClient#isProjectDirectory(java.io.File)}. However, this method allows a lint client to
+ * set up all the projects ahead of time, and associate those projects with native resources
+ * (in an IDE for example, each lint project can be associated with the corresponding IDE
+ * project).
+ *
+ * @param projects a collection of projects, or null
+ */
+ public void setProjects(@Nullable Collection<Project> projects) {
+ mProjects = projects;
+ }
}
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/OtherFileVisitor.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/OtherFileVisitor.java
index 573d4f0..0d3becd 100644
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/OtherFileVisitor.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/OtherFileVisitor.java
@@ -33,7 +33,6 @@
import java.io.File;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.List;
@@ -159,9 +158,9 @@
mFiles.put(Scope.MANIFEST, files);
}
} else {
- File manifestFile = project.getManifestFile();
- if (manifestFile != null) {
- mFiles.put(Scope.MANIFEST, Collections.<File>singletonList(manifestFile));
+ List<File> manifestFiles = project.getManifestFiles();
+ if (manifestFiles != null) {
+ mFiles.put(Scope.MANIFEST, manifestFiles);
}
}
}
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Issue.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Issue.java
index 6462420..c74d28a 100644
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Issue.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Issue.java
@@ -234,6 +234,7 @@
return Collections.singletonList((String) mMoreInfoUrls);
} else {
assert mMoreInfoUrls instanceof List;
+ //noinspection unchecked
return (List<String>) mMoreInfoUrls;
}
}
@@ -450,7 +451,7 @@
/**
* The format of output from the description methods
* @see #getDescription(OutputFormat)
- * @see Issue#getExplanation(OutputFormat)}
+ * @see Issue#getExplanation(OutputFormat)
* @see Issue#getBriefDescription(OutputFormat)
* */
public enum OutputFormat {
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/JavaContext.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/JavaContext.java
index d91f423..623101f 100644
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/JavaContext.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/JavaContext.java
@@ -23,6 +23,7 @@
import java.io.File;
+import lombok.ast.ClassDeclaration;
import lombok.ast.ConstructorDeclaration;
import lombok.ast.MethodDeclaration;
import lombok.ast.Node;
@@ -124,4 +125,21 @@
return null;
}
+
+ @Nullable
+ public static ClassDeclaration findSurroundingClass(Node scope) {
+ while (scope != null) {
+ Class<? extends Node> type = scope.getClass();
+ // The Lombok AST uses a flat hierarchy of node type implementation classes
+ // so no need to do instanceof stuff here.
+ if (type == ClassDeclaration.class) {
+ return (ClassDeclaration) scope;
+ }
+
+ scope = scope.getParent();
+ }
+
+ return null;
+ }
+
}
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/LintUtils.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/LintUtils.java
index fa48a88..608bf1f 100644
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/LintUtils.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/LintUtils.java
@@ -341,7 +341,7 @@
* path separators.
* @return the individual path components as an Iterable of strings
*/
- public static Iterable<String> splitPath(String path) {
+ public static Iterable<String> splitPath(@NonNull String path) {
if (path.indexOf(';') != -1) {
return Splitter.on(';').omitEmptyStrings().trimResults().split(path);
}
@@ -670,15 +670,12 @@
}
/**
- * Returns true if the given directory represents an Android project
- * directory. Note: This doesn't necessarily mean it's an Eclipse directory,
- * only that it looks like it contains a logical Android project -- one
- * including a manifest file, a resource folder, etc.
+ * Returns true if the given directory is a lint manifest file directory.
*
* @param dir the directory to check
- * @return true if the directory looks like an Android project
+ * @return true if the directory contains a manifest file
*/
- public static boolean isProjectDir(@NonNull File dir) {
+ public static boolean isManifestFolder(File dir) {
boolean hasManifest = new File(dir, ANDROID_MANIFEST_XML).exists();
if (hasManifest) {
// Special case: the bin/ folder can also contain a copy of the
@@ -687,14 +684,12 @@
// ...unless of course it just *happens* to be a project named bin, in
// which case we peek at its parent to see if this is the case
dir = dir.getParentFile();
- if (dir != null && isProjectDir(dir)) {
+ //noinspection ConstantConditions
+ if (dir != null && isManifestFolder(dir)) {
// Yes, it's a bin/ directory inside a real project: ignore this dir
return false;
}
}
- } else if (Project.isAospFrameworksProject(dir)) {
- // Hardcoded AOSP support for the frameworks project
- return true;
}
return hasManifest;
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Project.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Project.java
index 21ea5cd..ca0c5ec 100644
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Project.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Project.java
@@ -23,6 +23,8 @@
import static com.android.SdkConstants.ATTR_MIN_SDK_VERSION;
import static com.android.SdkConstants.ATTR_PACKAGE;
import static com.android.SdkConstants.ATTR_TARGET_SDK_VERSION;
+import static com.android.SdkConstants.FN_PROJECT_PROGUARD_FILE;
+import static com.android.SdkConstants.OLD_PROGUARD_FILE;
import static com.android.SdkConstants.PROGUARD_CONFIG;
import static com.android.SdkConstants.PROJECT_PROPERTIES;
import static com.android.SdkConstants.RES_FOLDER;
@@ -31,13 +33,20 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.android.builder.model.AndroidLibrary;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.SourceProvider;
+import com.android.builder.model.Variant;
import com.android.ide.common.sdk.SdkVersionInfo;
import com.android.tools.lint.client.api.CircularDependencyException;
import com.android.tools.lint.client.api.Configuration;
import com.android.tools.lint.client.api.LintClient;
import com.android.tools.lint.client.api.SdkInfo;
import com.google.common.annotations.Beta;
+import com.google.common.base.CharMatcher;
import com.google.common.base.Charsets;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Sets;
import com.google.common.io.Closeables;
import com.google.common.io.Files;
@@ -54,6 +63,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Properties;
+import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -66,34 +76,37 @@
*/
@Beta
public class Project {
- private final LintClient mClient;
- private final File mDir;
- private final File mReferenceDir;
- private Configuration mConfiguration;
- private String mPackage;
- private int mMinSdk = 1;
- private int mTargetSdk = -1;
- private int mBuildSdk = -1;
- private boolean mLibrary;
- private String mName;
- private String mProguardPath;
- private boolean mMergeManifests;
+ protected final LintClient mClient;
+ protected final File mDir;
+ protected final File mReferenceDir;
+ protected Configuration mConfiguration;
+ protected String mPackage;
+ protected int mMinSdk = 1;
+ protected int mTargetSdk = -1;
+ protected int mBuildSdk = -1;
+ protected boolean mLibrary;
+ protected String mName;
+ protected String mProguardPath;
+ protected boolean mMergeManifests;
/** The SDK info, if any */
- private SdkInfo mSdkInfo;
+ protected SdkInfo mSdkInfo;
/**
* If non null, specifies a non-empty list of specific files under this
* project which should be checked.
*/
- private List<File> mFiles;
- private List<File> mJavaSourceFolders;
- private List<File> mJavaClassFolders;
- private List<File> mJavaLibraries;
- private List<Project> mDirectLibraries;
- private List<Project> mAllLibraries;
- private boolean mReportIssues = true;
- private Boolean mGradleProject;
+ protected List<File> mFiles;
+ protected List<File> mProguardFiles;
+ protected List<File> mManifestFiles;
+ protected List<File> mJavaSourceFolders;
+ protected List<File> mJavaClassFolders;
+ protected List<File> mJavaLibraries;
+ protected List<File> mResourceFolders;
+ protected List<Project> mDirectLibraries;
+ protected List<Project> mAllLibraries;
+ protected boolean mReportIssues = true;
+ protected Boolean mGradleProject;
/**
* Creates a new {@link Project} for the given directory.
@@ -124,19 +137,60 @@
return mGradleProject;
}
+ /**
+ * Returns the project model for this project if it corresponds to
+ * a Gradle project. This is the case if {@link #isGradleProject()}
+ * is true and {@link #isLibrary()} is false.
+ *
+ * @return the project model, or null
+ */
+ @Nullable
+ public AndroidProject getGradleProjectModel() {
+ return null;
+ }
+
+ /**
+ * Returns the project model for this project if it corresponds to
+ * a Gradle library. This is the case if both
+ * {@link #isGradleProject()} and {@link #isLibrary()} return true.
+ *
+ * @return the project model, or null
+ */
+ @Nullable
+ public AndroidLibrary getGradleLibraryModel() {
+ return null;
+ }
+
+ /**
+ * Returns the current selected variant, if any (and if the current project is a Gradle
+ * project). This can be used by incremental lint rules to warn about problems in the current
+ * context. Lint rules should however strive to perform cross variant analysis and warn about
+ * problems in any configuration.
+ *
+ * @return the select variant, or null
+ */
+ @Nullable
+ public Variant getCurrentVariant() {
+ return null;
+ }
+
/** Creates a new Project. Use one of the factory methods to create. */
- private Project(
+ protected Project(
@NonNull LintClient client,
@NonNull File dir,
@NonNull File referenceDir) {
mClient = client;
mDir = dir;
mReferenceDir = referenceDir;
+ initialize();
+ }
+ protected void initialize() {
+ // Default initialization: Use ADT/ant style project.properties file
try {
// Read properties file and initialize library state
Properties properties = new Properties();
- File propFile = new File(dir, PROJECT_PROPERTIES);
+ File propFile = new File(mDir, PROJECT_PROPERTIES);
if (propFile.exists()) {
@SuppressWarnings("resource") // Eclipse doesn't know about Closeables.closeQuietly
BufferedInputStream is = new BufferedInputStream(new FileInputStream(propFile));
@@ -144,7 +198,10 @@
properties.load(is);
String value = properties.getProperty(ANDROID_LIBRARY);
mLibrary = VALUE_TRUE.equals(value);
- mProguardPath = properties.getProperty(PROGUARD_CONFIG);
+ String proguardPath = properties.getProperty(PROGUARD_CONFIG);
+ if (proguardPath != null) {
+ mProguardPath = proguardPath;
+ }
mMergeManifests = VALUE_TRUE.equals(properties.getProperty(
"manifestmerger.enabled")); //$NON-NLS-1$
String target = properties.getProperty("target"); //$NON-NLS-1$
@@ -172,7 +229,7 @@
break;
}
- File libraryDir = new File(dir, library).getCanonicalFile();
+ File libraryDir = new File(mDir, library).getCanonicalFile();
if (mDirectLibraries == null) {
mDirectLibraries = new ArrayList<Project>();
@@ -180,12 +237,12 @@
// Adjust the reference dir to be a proper prefix path of the
// library dir
- File libraryReferenceDir = referenceDir;
- if (!libraryDir.getPath().startsWith(referenceDir.getPath())) {
+ File libraryReferenceDir = mReferenceDir;
+ if (!libraryDir.getPath().startsWith(mReferenceDir.getPath())) {
// Symlinks etc might have been resolved, so do those to
// the reference dir as well
libraryReferenceDir = libraryReferenceDir.getCanonicalFile();
- if (!libraryDir.getPath().startsWith(referenceDir.getPath())) {
+ if (!libraryDir.getPath().startsWith(mReferenceDir.getPath())) {
File file = libraryReferenceDir;
while (file != null && !file.getPath().isEmpty()) {
if (libraryDir.getPath().startsWith(file.getPath())) {
@@ -198,7 +255,8 @@
}
try {
- Project libraryPrj = client.getProject(libraryDir, libraryReferenceDir);
+ Project libraryPrj = mClient.getProject(libraryDir,
+ libraryReferenceDir);
mDirectLibraries.add(libraryPrj);
// By default, we don't report issues in inferred library projects.
// The driver will set report = true for those library explicitly
@@ -215,7 +273,7 @@
}
}
} catch (IOException ioe) {
- client.log(ioe, "Initializing project state");
+ mClient.log(ioe, "Initializing project state");
}
if (mDirectLibraries != null) {
@@ -232,10 +290,7 @@
@Override
public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((mDir == null) ? 0 : mDir.hashCode());
- return result;
+ return mDir.hashCode();
}
@Override
@@ -247,12 +302,7 @@
if (getClass() != obj.getClass())
return false;
Project other = (Project) obj;
- if (mDir == null) {
- if (other.mDir != null)
- return false;
- } else if (!mDir.equals(other.mDir))
- return false;
- return true;
+ return mDir.equals(other.mDir);
}
/**
@@ -369,18 +419,23 @@
*/
@NonNull
public List<File> getResourceFolders() {
- List<File> folders = mClient.getResourceFolders(this);
+ if (mResourceFolders == null) {
+ List<File> folders = mClient.getResourceFolders(this);
- if (folders.size() == 1 && isAospFrameworksProject(mDir)) {
- // No manifest file for this project: just init the manifest values here
- mMinSdk = mTargetSdk = SdkVersionInfo.HIGHEST_KNOWN_API;
- File folder = new File(folders.get(0), RES_FOLDER);
- if (!folder.exists()) {
- folders = Collections.emptyList();
+ if (folders.size() == 1 && isAospFrameworksProject(mDir)) {
+ // No manifest file for this project: just init the manifest values here
+ mMinSdk = mTargetSdk = SdkVersionInfo.HIGHEST_KNOWN_API;
+ File folder = new File(folders.get(0), RES_FOLDER);
+ if (!folder.exists()) {
+ folders = Collections.emptyList();
+ }
}
+
+ mResourceFolders = folders;
}
- return folders;
+ return mResourceFolders;
+
}
/**
@@ -540,7 +595,8 @@
try {
mMinSdk = Integer.valueOf(minSdk);
} catch (NumberFormatException e) {
- mMinSdk = 1;
+ // Codename?
+ mMinSdk = SdkVersionInfo.getApiByPreviewName(minSdk, true);
}
}
@@ -554,8 +610,7 @@
try {
mTargetSdk = Integer.valueOf(targetSdk);
} catch (NumberFormatException e) {
- // TODO: Handle codenames?
- mTargetSdk = -1;
+ mTargetSdk = SdkVersionInfo.getApiByPreviewName(minSdk, false);
}
}
} else if (isAospBuildEnvironment()) {
@@ -580,7 +635,7 @@
*/
@NonNull
public List<Project> getDirectLibraries() {
- return mDirectLibraries;
+ return mDirectLibraries != null ? mDirectLibraries : Collections.<Project>emptyList();
}
/**
@@ -596,7 +651,11 @@
}
List<Project> all = new ArrayList<Project>();
- addLibraryProjects(all);
+ Set<Project> seen = Sets.newHashSet();
+ Set<Project> path = Sets.newHashSet();
+ seen.add(this);
+ path.add(this);
+ addLibraryProjects(all, seen, path);
mAllLibraries = all;
}
@@ -608,12 +667,25 @@
* recursively into the given collection of projects
*
* @param collection the collection to add the projects into
+ * @param seen full set of projects we've processed
+ * @param path the current path of library dependencies followed
*/
- private void addLibraryProjects(@NonNull Collection<Project> collection) {
+ private void addLibraryProjects(@NonNull Collection<Project> collection,
+ @NonNull Set<Project> seen, @NonNull Set<Project> path) {
for (Project library : mDirectLibraries) {
+ if (seen.contains(library)) {
+ if (path.contains(library)) {
+ mClient.log(Severity.WARNING, null,
+ "Internal lint error: cyclic library dependency for %1$s", library);
+ }
+ continue;
+ }
collection.add(library);
+ seen.add(library);
+ path.add(library);
// Recurse
- library.addLibraryProjects(collection);
+ library.addLibraryProjects(collection, seen, path);
+ path.remove(library);
}
}
@@ -632,29 +704,64 @@
}
/**
- * Gets the path to the manifest file in this project, if it exists
+ * Gets the paths to the manifest files in this project, if any exists. The manifests
+ * should be provided such that the main manifest comes first, then any flavor versions,
+ * then any build types.
*
* @return the path to the manifest file, or null if it does not exist
*/
- @Nullable
- public File getManifestFile() {
- File manifestFile = new File(mDir, ANDROID_MANIFEST_XML);
- if (manifestFile.exists()) {
- return manifestFile;
+ @NonNull
+ public List<File> getManifestFiles() {
+ if (mManifestFiles == null) {
+ File manifestFile = new File(mDir, ANDROID_MANIFEST_XML);
+ if (manifestFile.exists()) {
+ mManifestFiles = Collections.singletonList(manifestFile);
+ } else {
+ mManifestFiles = Collections.emptyList();
+ }
}
- return null;
+ return mManifestFiles;
}
/**
- * Returns the proguard path configured for this project, or null if ProGuard is
- * not configured.
+ * Returns the proguard files configured for this project, if any
*
- * @return the proguard path, or null
+ * @return the proguard files, if any
*/
- @Nullable
- public String getProguardPath() {
- return mProguardPath;
+ @NonNull
+ public List<File> getProguardFiles() {
+ if (mProguardFiles == null) {
+ List<File> files = new ArrayList<File>();
+ if (mProguardPath != null) {
+ Splitter splitter = Splitter.on(CharMatcher.anyOf(":;")); //$NON-NLS-1$
+ for (String path : splitter.split(mProguardPath)) {
+ if (path.contains("${")) { //$NON-NLS-1$
+ // Don't analyze the global/user proguard files
+ continue;
+ }
+ File file = new File(path);
+ if (!file.isAbsolute()) {
+ file = new File(getDir(), path);
+ }
+ if (file.exists()) {
+ files.add(file);
+ }
+ }
+ }
+ if (files.isEmpty()) {
+ File file = new File(getDir(), OLD_PROGUARD_FILE);
+ if (file.exists()) {
+ files.add(file);
+ }
+ file = new File(getDir(), FN_PROJECT_PROGUARD_FILE);
+ if (file.exists()) {
+ files.add(file);
+ }
+ }
+ mProguardFiles = files;
+ }
+ return mProguardFiles;
}
/**
@@ -749,7 +856,7 @@
* @param dir the project directory to check
* @return true if this looks like the frameworks/base/core project
*/
- static boolean isAospFrameworksProject(@NonNull File dir) {
+ public static boolean isAospFrameworksProject(@NonNull File dir) {
if (!dir.getPath().endsWith("core")) { //$NON-NLS-1$
return false;
}
diff --git a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Scope.java b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Scope.java
index 8e1739c..681d5ed 100644
--- a/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Scope.java
+++ b/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/Scope.java
@@ -16,10 +16,21 @@
package com.android.tools.lint.detector.api;
+import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
+import static com.android.SdkConstants.DOT_CLASS;
+import static com.android.SdkConstants.DOT_JAVA;
+import static com.android.SdkConstants.DOT_XML;
+import static com.android.SdkConstants.FN_PROJECT_PROGUARD_FILE;
+import static com.android.SdkConstants.OLD_PROGUARD_FILE;
+import static com.android.SdkConstants.RES_FOLDER;
+
import com.android.annotations.NonNull;
import com.google.common.annotations.Beta;
+import java.io.File;
+import java.util.Collection;
import java.util.EnumSet;
+import java.util.List;
/**
* The scope of a detector is the set of files a detector must consider when
@@ -135,6 +146,47 @@
return scope;
}
+ /**
+ * Infers a suitable scope to use from the given projects to be analyzed
+ * @param projects the projects to find a suitable scope for
+ * @return the scope to use
+ */
+ @NonNull
+ public static EnumSet<Scope> infer(@NonNull Collection<Project> projects) {
+ // Infer the scope
+ EnumSet<Scope> scope = EnumSet.noneOf(Scope.class);
+ for (Project project : projects) {
+ List<File> subset = project.getSubset();
+ if (subset != null) {
+ for (File file : subset) {
+ String name = file.getName();
+ if (name.equals(ANDROID_MANIFEST_XML)) {
+ scope.add(MANIFEST);
+ } else if (name.endsWith(DOT_XML)) {
+ scope.add(RESOURCE_FILE);
+ } else if (name.equals(RES_FOLDER)
+ || file.getParent().equals(RES_FOLDER)) {
+ scope.add(ALL_RESOURCE_FILES);
+ scope.add(RESOURCE_FILE);
+ } else if (name.endsWith(DOT_JAVA)) {
+ scope.add(JAVA_FILE);
+ } else if (name.endsWith(DOT_CLASS)) {
+ scope.add(CLASS_FILE);
+ } else if (name.equals(OLD_PROGUARD_FILE)
+ || name.equals(FN_PROJECT_PROGUARD_FILE)) {
+ scope.add(PROGUARD_FILE);
+ }
+ }
+ } else {
+ // Specified a full project: just use the full project scope
+ scope = Scope.ALL;
+ break;
+ }
+ }
+
+ return scope;
+ }
+
/** All scopes: running lint on a project will check these scopes */
public static final EnumSet<Scope> ALL = EnumSet.allOf(Scope.class);
/** Scope-set used for detectors which are affected by a single resource file */
@@ -156,7 +208,7 @@
EnumSet.of(RESOURCE_FILE, JAVA_FILE);
/** Scope-set used for analyzing individual class files and all resource files */
public static final EnumSet<Scope> CLASS_AND_ALL_RESOURCE_FILES =
- EnumSet.of(Scope.ALL_RESOURCE_FILES, Scope.CLASS_FILE);
+ EnumSet.of(ALL_RESOURCE_FILES, CLASS_FILE);
/** Scope-set used for detectors which are affected by Java libraries */
- public static final EnumSet<Scope> JAVA_LIBRARY_SCOPE = EnumSet.of(Scope.JAVA_LIBRARIES);
+ public static final EnumSet<Scope> JAVA_LIBRARY_SCOPE = EnumSet.of(JAVA_LIBRARIES);
}
diff --git a/lint/libs/lint-checks/build.gradle b/lint/libs/lint-checks/build.gradle
index 8fd8066..e0b6ed0 100644
--- a/lint/libs/lint-checks/build.gradle
+++ b/lint/libs/lint-checks/build.gradle
@@ -1,3 +1,6 @@
+apply plugin: 'java'
+apply plugin: 'distrib'
+
group = 'com.android.tools.lint'
archivesBaseName = 'lint-checks'
@@ -15,45 +18,10 @@
test.resources.srcDir 'src/test/java'
}
-uploadArchives {
- repositories {
- mavenDeployer {
- beforeDeployment { MavenDeployment deployment ->
- if (!project.has("release")) {
- throw new StopExecutionException("uploadArchives must be called with the release.gradle init script")
- }
+project.ext.pomName = 'Android Lint Checks'
+project.ext.pomDesc = 'Checks for Android Lint'
- signing.signPom(deployment)
- }
+apply from: '../../../baseVersion.gradle'
+apply from: '../../../publish.gradle'
+apply from: '../../../javadoc.gradle'
- repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
- authentication(userName: project.ext.sonatypeUsername, password: project.ext.sonatypePassword)
- }
-
- pom.project {
- name 'Android Lint Checks'
- description 'Checks for Android Lint'
- url 'http://tools.android.com'
- inceptionYear '2007'
-
- licenses {
- license {
- name 'The Apache Software License, Version 2.0'
- url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
- distribution 'repo'
- }
- }
-
- scm {
- url "https://android.googlesource.com/platform/tools/base"
- connection "git://android.googlesource.com/platform/tools/base.git"
- }
- developers {
- developer {
- name 'The Android Open Source Project'
- }
- }
- }
- }
- }
-}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiClass.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiClass.java
index 3e0fb9d..c5946b9 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiClass.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiClass.java
@@ -218,7 +218,7 @@
addToArray(mInterfaces, interfaceClass, since);
}
- void addToArray(List<Pair<String, Integer>> list, String name, int value) {
+ static void addToArray(List<Pair<String, Integer>> list, String name, int value) {
// check if we already have that name (at a lower level)
for (Pair<String, Integer> pair : list) {
if (name.equals(pair.getFirst())) {
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiDetector.java
index 26283ce..efd36f4 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiDetector.java
@@ -998,13 +998,8 @@
} catch (NumberFormatException nufe) {
break;
}
- }
-
- for (int api = 1; api <= SdkVersionInfo.HIGHEST_KNOWN_API; api++) {
- String code = SdkVersionInfo.getBuildCode(api);
- if (code != null && code.equalsIgnoreCase(targetApi)) {
- return api;
- }
+ } else {
+ return SdkVersionInfo.getApiByBuildCode(targetApi, true);
}
}
@@ -1096,7 +1091,7 @@
* @return true if the given usage is safe on older versions than the introduction
* level of the constant
*/
- public boolean isBenignConstantUsage(
+ public static boolean isBenignConstantUsage(
@Nullable lombok.ast.Node node,
@NonNull String name,
@NonNull String owner) {
@@ -1474,6 +1469,23 @@
if (list == null) {
list = new ArrayList<Pair<String, Location>>();
mPendingFields.put(fqcn, list);
+ } else {
+ // See if this location already exists. This can happen if
+ // we have multiple references to an inlined field on the same
+ // line. Since the class file only gives us line information, we
+ // can't distinguish between these in the client as separate usages,
+ // so they end up being identical errors.
+ for (Pair<String, Location> pair : list) {
+ Location existingLocation = pair.getSecond();
+ if (location.getFile().equals(existingLocation.getFile())) {
+ Position start = location.getStart();
+ Position existingStart = existingLocation.getStart();
+ if (start != null && existingStart != null
+ && start.getLine() == existingStart.getLine()) {
+ return true;
+ }
+ }
+ }
}
list.add(Pair.of(message, location));
}
@@ -1571,15 +1583,15 @@
return literal.astIntValue();
} else if (valueNode instanceof StringLiteral) {
String value = ((StringLiteral) valueNode).astValue();
- return codeNameToApi(value);
+ return SdkVersionInfo.getApiByBuildCode(value, true);
} else if (valueNode instanceof Select) {
Select select = (Select) valueNode;
String codename = select.astIdentifier().astValue();
- return codeNameToApi(codename);
+ return SdkVersionInfo.getApiByBuildCode(codename, true);
} else if (valueNode instanceof VariableReference) {
VariableReference reference = (VariableReference) valueNode;
String codename = reference.astIdentifier().astValue();
- return codeNameToApi(codename);
+ return SdkVersionInfo.getApiByBuildCode(codename, true);
}
}
}
@@ -1589,15 +1601,4 @@
return -1;
}
}
-
- private static int codeNameToApi(String codename) {
- for (int api = 1; api <= SdkVersionInfo.HIGHEST_KNOWN_API; api++) {
- String code = SdkVersionInfo.getBuildCode(api);
- if (code != null && code.equalsIgnoreCase(codename)) {
- return api;
- }
- }
-
- return -1;
- }
}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiLookup.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiLookup.java
index 5c27f34..65e33ca 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiLookup.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ApiLookup.java
@@ -630,7 +630,7 @@
* @param name the class name in VM format (e.g. using / instead of .)
* @return true if the owner is <b>possibly</b> relevant
*/
- public boolean isRelevantClass(String name) {
+ public static boolean isRelevantClass(String name) {
// TODO: Add quick switching here. This is tied to the database file so if
// we end up with unexpected prefixes there, this could break. For that reason,
// for now we consider everything relevant.
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/BuiltinIssueRegistry.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/BuiltinIssueRegistry.java
index 7f449fe..af3a6ef 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/BuiltinIssueRegistry.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/BuiltinIssueRegistry.java
@@ -17,46 +17,26 @@
package com.android.tools.lint.checks;
import static com.android.tools.lint.detector.api.LintUtils.assertionsEnabled;
-import static com.android.tools.lint.detector.api.LintUtils.endsWith;
import com.android.annotations.NonNull;
import com.android.annotations.VisibleForTesting;
-import com.android.prefs.AndroidLocation;
-import com.android.prefs.AndroidLocation.AndroidLocationException;
import com.android.tools.lint.client.api.IssueRegistry;
import com.android.tools.lint.detector.api.Issue;
import com.google.common.annotations.Beta;
import com.google.common.collect.Sets;
-import java.io.File;
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import java.util.jar.Attributes;
-import java.util.jar.JarFile;
-import java.util.jar.Manifest;
/** Registry which provides a list of checks to be performed on an Android project */
public class BuiltinIssueRegistry extends IssueRegistry {
- /** Folder name in the .android dir where additional detector jars are found */
- private static final String LINT_FOLDER = "lint"; //$NON-NLS-1$
-
- /**
- * Manifest constant for declaring an issue provider. Example:
- * Lint-Registry: foo.bar.CustomIssueRegistry
- */
- private static final String MF_LINT_REGISTRY = "Lint-Registry"; //$NON-NLS-1$
-
private static final List<Issue> sIssues;
static {
- final int initialCapacity = 157;
+ final int initialCapacity = 161;
List<Issue> issues = new ArrayList<Issue>(initialCapacity);
issues.add(AccessibilityDetector.ISSUE);
@@ -97,6 +77,7 @@
issues.add(TooManyViewsDetector.TOO_DEEP);
issues.add(GridLayoutDetector.ISSUE);
issues.add(OverrideDetector.ISSUE);
+ issues.add(CallSuperDetector.ISSUE);
issues.add(OnClickDetector.ISSUE);
issues.add(ViewTagDetector.ISSUE);
issues.add(LocaleDetector.STRING_LOCALE);
@@ -146,6 +127,7 @@
issues.add(ManifestDetector.DUPLICATE_USES_FEATURE);
issues.add(ManifestDetector.APPLICATION_ICON);
issues.add(ManifestDetector.DEVICE_ADMIN);
+ issues.add(ManifestDetector.MOCK_LOCATION);
issues.add(ManifestTypoDetector.ISSUE);
issues.add(SecurityDetector.EXPORTED_PROVIDER);
issues.add(SecurityDetector.EXPORTED_SERVICE);
@@ -154,6 +136,8 @@
issues.add(SecurityDetector.WORLD_READABLE);
issues.add(SecurityDetector.WORLD_WRITEABLE);
issues.add(SecureRandomDetector.ISSUE);
+ issues.add(CheckPermissionDetector.ISSUE);
+ issues.add(SecureRandomGeneratorDetector.ISSUE);
issues.add(IconDetector.GIF_USAGE);
issues.add(IconDetector.ICON_DENSITIES);
issues.add(IconDetector.ICON_MISSING_FOLDER);
@@ -219,8 +203,6 @@
assert initialCapacity >= issues.size() : issues.size();
- addCustomIssues(issues);
-
sIssues = Collections.unmodifiableList(issues);
// Check that ids are unique
@@ -246,96 +228,6 @@
return sIssues;
}
- /**
- * Add in custom issues registered by the user - via an environment variable
- * or in the .android/lint directory.
- */
- private static void addCustomIssues(List<Issue> issues) {
- // Look for additional detectors registered by the user, via
- // (1) an environment variable (useful for build servers etc), and
- // (2) via jar files in the .android/lint directory
- Set<File> files = null;
- try {
- File lint = new File(AndroidLocation.getFolder() + File.separator + LINT_FOLDER);
- if (lint.exists()) {
- File[] list = lint.listFiles();
- if (list != null) {
- for (File jarFile : list) {
- if (endsWith(jarFile.getName(), ".jar")) { //$NON-NLS-1$
- if (files == null) {
- files = new HashSet<File>();
- }
- files.add(jarFile);
- addIssuesFromJar(jarFile, issues);
- }
- }
- }
- }
- } catch (AndroidLocationException e) {
- // Ignore -- no android dir, so no rules to load.
- }
-
- String lintClassPath = System.getenv("ANDROID_LINT_JARS"); //$NON-NLS-1$
- if (lintClassPath != null && !lintClassPath.isEmpty()) {
- String[] paths = lintClassPath.split(File.pathSeparator);
- for (String path : paths) {
- File jarFile = new File(path);
- if (jarFile.exists() && (files == null || !files.contains(jarFile))) {
- addIssuesFromJar(jarFile, issues);
- }
- }
- }
- }
-
- /** Add the issues found in the given jar file into the given list of issues */
- private static void addIssuesFromJar(File jarFile, List<Issue> issues) {
- JarFile jarfile = null;
- try {
- jarfile = new JarFile(jarFile);
- Manifest manifest = jarfile.getManifest();
- Attributes attrs = manifest.getMainAttributes();
- Object object = attrs.get(new Attributes.Name(MF_LINT_REGISTRY));
- if (object instanceof String) {
- String className = (String) object;
-
- // Make a class loader for this jar
- try {
- URL url = jarFile.toURI().toURL();
- URLClassLoader loader = new URLClassLoader(new URL[] { url },
- BuiltinIssueRegistry.class.getClassLoader());
- try {
- Class<?> registryClass = Class.forName(className, true, loader);
- IssueRegistry registry = (IssueRegistry) registryClass.newInstance();
- for (Issue issue : registry.getIssues()) {
- issues.add(issue);
- }
- } catch (Throwable e) {
- log(e);
- }
- } catch (MalformedURLException e) {
- log(e);
- }
- }
- } catch (IOException e) {
- log(e);
- } finally {
- if (jarfile != null) {
- try {
- jarfile.close();
- } catch (IOException e) {
- // Nothing to be done
- }
- }
- }
- }
-
- private static void log(Throwable e) {
- // TODO: Where do we log this? There's no embedding tool context here. For now,
- // just dump to the console so detector developers get some feedback on what went
- // wrong.
- e.printStackTrace();
- }
-
private static Set<Issue> sAdtFixes;
/**
@@ -347,6 +239,7 @@
* given issue
*/
@Beta
+ @SuppressWarnings("MethodMayBeStatic") // Intentionally instance method so it can be overridden
public boolean hasAutoFix(String tool, Issue issue) {
assert tool.equals("adt"); // This is not yet a generic facility;
// the primary purpose right now is to allow for example the HTML report
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ButtonDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ButtonDetector.java
index 39343f2..d870445 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ButtonDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ButtonDetector.java
@@ -394,7 +394,7 @@
/** The Ok/Cancel detector only works with default and English locales currently.
* TODO: Add in patterns for other languages. We can use the
- * @android:string/ok and @android:string/cancel localizations to look
+ * {@code @android:string/ok} and {@code @android:string/cancel} localizations to look
* up the canonical ones. */
private static boolean isEnglishResource(XmlContext context) {
String folder = context.file.getParentFile().getName();
@@ -636,12 +636,12 @@
}
/** Is the cancel button in the wrong position? It has to be on the left. */
- private boolean isWrongCancelPosition(Element element) {
+ private static boolean isWrongCancelPosition(Element element) {
return isWrongPosition(element, true /*isCancel*/);
}
/** Is the OK button in the wrong position? It has to be on the right. */
- private boolean isWrongOkPosition(Element element) {
+ private static boolean isWrongOkPosition(Element element) {
return isWrongPosition(element, false /*isCancel*/);
}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CallSuperDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CallSuperDetector.java
new file mode 100644
index 0000000..0887476
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CallSuperDetector.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.annotations.NonNull;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.JavaContext;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import lombok.ast.AstVisitor;
+import lombok.ast.ForwardingAstVisitor;
+import lombok.ast.MethodDeclaration;
+import lombok.ast.MethodInvocation;
+import lombok.ast.Node;
+import lombok.ast.Super;
+
+/**
+ * Makes sure that methods call super when overriding methods
+ */
+public class CallSuperDetector extends Detector implements Detector.JavaScanner {
+
+ private static final Implementation IMPLEMENTATION = new Implementation(
+ CallSuperDetector.class,
+ Scope.JAVA_FILE_SCOPE);
+
+ /** Missing call to super */
+ public static final Issue ISSUE = Issue.create(
+ "MissingSuperCall", //$NON-NLS-1$
+ "Missing Super Call",
+ "Looks for overriding methods that should also invoke the parent method",
+
+ "Some methods, such as `View#onDetachedFromWindow`, require that you also " +
+ "call the super implementation as part of your method.",
+
+ Category.CORRECTNESS,
+ 9,
+ Severity.WARNING,
+ IMPLEMENTATION);
+
+ static final String ON_DETACHED_FROM_WINDOW = "onDetachedFromWindow"; //$NON-NLS-1$
+
+ /** Constructs a new {@link CallSuperDetector} check */
+ public CallSuperDetector() {
+ }
+
+ @Override
+ public boolean appliesTo(@NonNull Context context, @NonNull File file) {
+ return true;
+ }
+
+ @NonNull
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ // ---- Implements JavaScanner ----
+
+ @Override
+ public List<Class<? extends Node>> getApplicableNodeTypes() {
+ return Collections.<Class<? extends Node>>singletonList(MethodDeclaration.class);
+ }
+
+ @Override
+ public AstVisitor createJavaVisitor(@NonNull JavaContext context) {
+ return new PerformanceVisitor(context);
+ }
+
+ private static class PerformanceVisitor extends ForwardingAstVisitor {
+ private final JavaContext mContext;
+
+ public PerformanceVisitor(JavaContext context) {
+ mContext = context;
+ }
+
+ @Override
+ public boolean visitMethodDeclaration(MethodDeclaration node) {
+ // TODO: Check methods in Activity that require super as well
+ if (node.astMethodName().astValue().equals(ON_DETACHED_FROM_WINDOW) &&
+ node.astParameters() != null && node.astParameters().isEmpty()) {
+ if (!callsSuper(node, ON_DETACHED_FROM_WINDOW)) {
+ String message = "Overriding method should call super."
+ + ON_DETACHED_FROM_WINDOW;
+ Location location = mContext.getLocation(node.astMethodName());
+ mContext.report(ISSUE, node, location, message, null);
+ }
+ }
+
+ return super.visitMethodDeclaration(node);
+ }
+
+ private boolean callsSuper(MethodDeclaration node, final String methodName) {
+ final AtomicBoolean result = new AtomicBoolean();
+ node.accept(new ForwardingAstVisitor() {
+ @Override
+ public boolean visitMethodInvocation(MethodInvocation node) {
+ if (node.astName().astValue().equals(methodName) &&
+ node.astOperand() instanceof Super) {
+ result.set(true);
+ }
+ return super.visitMethodInvocation(node);
+ }
+ });
+
+ return result.get();
+ }
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CheckPermissionDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CheckPermissionDetector.java
new file mode 100644
index 0000000..f0a9233
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CheckPermissionDetector.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.lint.detector.api.*;
+
+import lombok.ast.*;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Ensures that calls to check permission use the result (otherwise they probably meant to call the
+ * <b>enforce</b> permission methods instead)
+ */
+public class CheckPermissionDetector extends Detector implements Detector.JavaScanner {
+ /** Main issue checked by this detector */
+ public static final Issue ISSUE = Issue.create("UseCheckPermission", //$NON-NLS-1$
+ "Using the result of check permission calls",
+ "Ensures that the return value of check permission calls are used",
+
+ "You normally want to use the result of checking a permission; these methods " +
+ "return whether the permission is held; they do not throw an error if the permission " +
+ "is not granted. Code which does not do anything with the return value probably " +
+ "meant to be calling the enforce methods instead, e.g. rather than " +
+ "`Context#checkCallingPermission` it should call `Context#enforceCallingPermission`.",
+
+ Category.SECURITY, 6, Severity.WARNING,
+ new Implementation(CheckPermissionDetector.class, Scope.JAVA_FILE_SCOPE));
+
+ private static final String CHECK_PERMISSION = "checkPermission";
+
+ /**
+ * Constructs a new {@link com.android.tools.lint.checks.CheckPermissionDetector} check
+ */
+ public CheckPermissionDetector() {
+ }
+
+ // ---- Implements JavaScanner ----
+
+ @Override
+ public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
+ @NonNull MethodInvocation node) {
+ if (node.getParent() instanceof ExpressionStatement) {
+ String check = node.astName().astValue();
+ if (CHECK_PERMISSION.equals(check)) {
+ if (!ensureContextMethod(context, node)) {
+ return;
+ }
+ }
+ assert check.startsWith("check") : check;
+ String enforce = "enforce" + check.substring("check".length());
+ context.report(ISSUE, node, context.getLocation(node),
+ String.format(
+ "The result of %1$s is not used; did you mean to call %2$s?",
+ check, enforce), null);
+ }
+ }
+
+ private static boolean ensureContextMethod(
+ @NonNull JavaContext context,
+ @NonNull MethodInvocation node) {
+ // Method name used in many other contexts where it doesn't have the
+ // same semantics; only use this one if we can resolve types
+ // and we're certain this is the Context method
+ Node resolved = context.parser.resolve(context, node);
+ if (resolved instanceof MethodDeclaration) {
+ ClassDeclaration declaration = JavaContext.findSurroundingClass(resolved);
+ if (declaration != null && declaration.astName() != null) {
+ String className = declaration.astName().astValue();
+ if ("ContextWrapper".equals(className) || "Context".equals(className)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public List<String> getApplicableMethodNames() {
+ return Arrays.asList(
+ CHECK_PERMISSION,
+ "checkUriPermission",
+ "checkCallingOrSelfPermission",
+ "checkCallingPermission",
+ "checkCallingUriPermission",
+ "checkCallingOrSelfUriPermission"
+ );
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DuplicateIdDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DuplicateIdDetector.java
index a8c9735..7923711 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DuplicateIdDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DuplicateIdDetector.java
@@ -665,7 +665,7 @@
}
@Override
- public int compareTo(Occurrence other) {
+ public int compareTo(@NonNull Occurrence other) {
// First sort by length, then sort by name
int delta = toString().length() - other.toString().length();
if (delta != 0) {
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HandlerDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HandlerDetector.java
index ad029b3..91704f0 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HandlerDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/HandlerDetector.java
@@ -16,6 +16,9 @@
package com.android.tools.lint.checks;
+import static com.android.SdkConstants.CONSTRUCTOR_NAME;
+
+import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.ClassContext;
@@ -30,6 +33,7 @@
import com.android.tools.lint.detector.api.Speed;
import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.MethodNode;
/**
* Checks that Handler implementations are top level classes or static.
@@ -43,11 +47,14 @@
"Handler reference leaks",
"Ensures that Handler classes do not hold on to a reference to an outer class",
- "In Android, Handler classes should be static or leaks might occur. " +
- "Messages enqueued on the application thread's MessageQueue also retain their " +
- "target Handler. If the Handler is an inner class, its outer class will be " +
- "retained as well. To avoid leaking the outer class, declare the Handler as a " +
- "static nested class with a WeakReference to its outer class.",
+ "Since this Handler is declared as an inner class, it may prevent the outer " +
+ "class from being garbage collected. If the Handler is using a Looper or " +
+ "MessageQueue for a thread other than the main thread, then there is no issue. " +
+ "If the Handler is using the Looper or MessageQueue of the main thread, you " +
+ "need to fix your Handler declaration, as follows: Declare the Handler as a " +
+ "static class; In the outer class, instantiate a WeakReference to the outer " +
+ "class and pass this object to your Handler when you instantiate the Handler; " +
+ "Make all references to members of the outer class using the WeakReference object.",
Category.PERFORMANCE,
4,
@@ -76,6 +83,15 @@
if (context.getDriver().isSubclassOf(classNode, "android/os/Handler") //$NON-NLS-1$
&& !LintUtils.isStaticInnerClass(classNode)) {
+ // Only flag handlers using the default looper
+ for (Object m : classNode.methods) {
+ MethodNode method = (MethodNode) m;
+ if (CONSTRUCTOR_NAME.equals(method.name) &&
+ method.desc.contains("Landroid/os/Looper;")) {
+ return;
+ }
+ }
+
Location location = context.getLocation(classNode);
context.report(ISSUE, location, String.format(
"This Handler class should be static or leaks might occur (%1$s)",
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/JavaScriptInterfaceDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/JavaScriptInterfaceDetector.java
index 03ee0ac..7bd6e6c 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/JavaScriptInterfaceDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/JavaScriptInterfaceDetector.java
@@ -149,7 +149,7 @@
}
}
- private boolean containsJavaScriptAnnotation(@NonNull ClassNode classNode) {
+ private static boolean containsJavaScriptAnnotation(@NonNull ClassNode classNode) {
List methodList = classNode.methods;
for (Object om : methodList) {
MethodNode m = (MethodNode) om;
@@ -168,8 +168,8 @@
}
@Nullable
- private String findFirstArgType(ClassContext context, ClassNode classNode, MethodNode method,
- MethodInsnNode call) {
+ private static String findFirstArgType(ClassContext context, ClassNode classNode,
+ MethodNode method, MethodInsnNode call) {
// Find object being passed in as the first argument
Analyzer analyzer = new Analyzer(new SourceInterpreter() {
@Override
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LayoutConsistencyDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LayoutConsistencyDetector.java
index 2c394f8..466d0b2 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LayoutConsistencyDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/LayoutConsistencyDetector.java
@@ -188,7 +188,7 @@
}
}
- private void lookupLocations(
+ private static void lookupLocations(
@NonNull XmlContext context,
@NonNull Element element,
@NonNull Map<String, List<Location>> map) {
@@ -339,7 +339,7 @@
}
}
- private Set<String> getInconsistentIds(Map<File, Set<String>> idMap) {
+ private static Set<String> getInconsistentIds(Map<File, Set<String>> idMap) {
Set<String> union = getAllIds(idMap);
Set<String> inconsistent = new HashSet<String>();
for (Map.Entry<File, Set<String>> entry : idMap.entrySet()) {
@@ -353,7 +353,7 @@
return inconsistent;
}
- private Set<String> getAllIds(Map<File, Set<String>> idMap) {
+ private static Set<String> getAllIds(Map<File, Set<String>> idMap) {
Iterator<Set<String>> iterator = idMap.values().iterator();
assert iterator.hasNext();
Set<String> union = new HashSet<String>(iterator.next());
@@ -390,15 +390,17 @@
for (String id : ids) {
String message = messageMap.get(id);
List<Location> locations = locationMap.get(id);
- Location location = chainLocations(locations);
+ if (locations != null) {
+ Location location = chainLocations(locations);
- context.report(INCONSISTENT_IDS, location, message, null);
+ context.report(INCONSISTENT_IDS, location, message, null);
+ }
}
}
}
@NonNull
- private Location chainLocations(@NonNull List<Location> locations) {
+ private static Location chainLocations(@NonNull List<Location> locations) {
assert !locations.isEmpty();
// Sort locations by the file parent folders
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ManifestDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ManifestDetector.java
index 82e1f98..c296f0b 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ManifestDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ManifestDetector.java
@@ -20,7 +20,6 @@
import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_MIN_SDK_VERSION;
import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_PACKAGE;
import static com.android.SdkConstants.ATTR_TARGET_SDK_VERSION;
import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
import static com.android.SdkConstants.TAG_ACTIVITY;
@@ -30,16 +29,18 @@
import static com.android.SdkConstants.TAG_PROVIDER;
import static com.android.SdkConstants.TAG_RECEIVER;
import static com.android.SdkConstants.TAG_SERVICE;
+import static com.android.SdkConstants.TAG_USES_FEATURE;
import static com.android.SdkConstants.TAG_USES_LIBRARY;
import static com.android.SdkConstants.TAG_USES_PERMISSION;
import static com.android.SdkConstants.TAG_USES_SDK;
-import static com.android.SdkConstants.TAG_USES_FEATURE;
import static com.android.xml.AndroidManifest.NODE_ACTION;
import static com.android.xml.AndroidManifest.NODE_DATA;
import static com.android.xml.AndroidManifest.NODE_METADATA;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
+import com.android.builder.model.AndroidProject;
+import com.android.builder.model.BuildTypeContainer;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Context;
import com.android.tools.lint.detector.api.Detector;
@@ -324,6 +325,30 @@
Severity.WARNING,
IMPLEMENTATION);
+ /** Using a mock location in a non-debug-specific manifest file */
+ public static final Issue MOCK_LOCATION = Issue.create(
+ "MockLocation", //$NON-NLS-1$
+ "Using mock location provider in production",
+ "Checks that mock location providers are only used in debug builds",
+
+ "Using a mock location provider (by requiring the permission " +
+ "`android.permission.ACCESS_MOCK_LOCATION`) should *only* be done " +
+ "in debug builds. In Gradle projects, that means you should only " +
+ "request this permission in a debug source set specific manifest file.\n" +
+ "\n" +
+ "To fix this, create a new manifest file in the debug folder and move " +
+ "the `<uses-permission>` element there. A typical path to a debug manifest " +
+ "override file in a Gradle project is src/debug/AndroidManifest.xml.",
+
+ Category.CORRECTNESS,
+ 8,
+ Severity.ERROR,
+ IMPLEMENTATION);
+
+ /** Permission name of mock location permission */
+ public static final String MOCK_LOCATION_PERMISSION =
+ "android.permission.ACCESS_MOCK_LOCATION"; //$NON-NLS-1$
+
/** Constructs a new {@link ManifestDetector} check */
public ManifestDetector() {
}
@@ -342,9 +367,6 @@
/** Permission basenames */
private Map<String, String> mPermissionNames;
- /** Package declared in the manifest */
- private String mPackage;
-
@NonNull
@Override
public Speed getSpeed() {
@@ -370,7 +392,10 @@
checkDocumentElement(xmlContext, element);
}
- if (mSeenUsesSdk == 0 && context.isEnabled(USES_SDK)) {
+ if (mSeenUsesSdk == 0 && context.isEnabled(USES_SDK)
+ // Not required in Gradle projects; typically defined in build.gradle instead
+ // and inserted at build time
+ && !context.getMainProject().isGradleProject()) {
context.report(USES_SDK, Location.create(context.file),
"Manifest should specify a minimum API level with " +
"<uses-sdk android:minSdkVersion=\"?\" />; if it really supports " +
@@ -378,14 +403,17 @@
}
}
- private void checkDocumentElement(XmlContext context, Element element) {
+ private static void checkDocumentElement(XmlContext context, Element element) {
Attr codeNode = element.getAttributeNodeNS(ANDROID_URI, "versionCode");//$NON-NLS-1$
if (codeNode != null && codeNode.getValue().startsWith(PREFIX_RESOURCE_REF)
&& context.isEnabled(ILLEGAL_REFERENCE)) {
context.report(ILLEGAL_REFERENCE, element, context.getLocation(element),
"The android:versionCode cannot be a resource url, it must be "
+ "a literal integer", null);
- } else if (codeNode == null && context.isEnabled(SET_VERSION)) {
+ } else if (codeNode == null && context.isEnabled(SET_VERSION)
+ // Not required in Gradle projects; typically defined in build.gradle instead
+ // and inserted at build time
+ && !context.getMainProject().isGradleProject()) {
context.report(SET_VERSION, element, context.getLocation(element),
"Should set android:versionCode to specify the application version", null);
}
@@ -395,7 +423,10 @@
context.report(ILLEGAL_REFERENCE, element, context.getLocation(element),
"The android:versionName cannot be a resource url, it must be "
+ "a literal string", null);
- } else if (nameNode == null && context.isEnabled(SET_VERSION)) {
+ } else if (nameNode == null && context.isEnabled(SET_VERSION)
+ // Not required in Gradle projects; typically defined in build.gradle instead
+ // and inserted at build time
+ && !context.getMainProject().isGradleProject()) {
context.report(SET_VERSION, element, context.getLocation(element),
"Should set android:versionName to specify the application version", null);
}
@@ -450,10 +481,11 @@
if (nameNode != null) {
String name = nameNode.getValue();
if (!name.isEmpty()) {
+ String pkg = context.getMainProject().getPackage();
if (name.charAt(0) == '.') {
- name = getPackage(element) + name;
+ name = pkg + name;
} else if (name.indexOf('.') == -1) {
- name = getPackage(element) + '.' + name;
+ name = pkg + '.' + name;
}
if (mActivities.contains(name)) {
String message = String.format(
@@ -507,7 +539,7 @@
}
if (!element.hasAttributeNS(ANDROID_URI, ATTR_MIN_SDK_VERSION)) {
- if (context.isEnabled(USES_SDK)) {
+ if (context.isEnabled(USES_SDK) && !context.getMainProject().isGradleProject()) {
context.report(USES_SDK, element, context.getLocation(element),
"<uses-sdk> tag should specify a minimum API level with " +
"android:minSdkVersion=\"?\"", null);
@@ -526,7 +558,7 @@
// Warn if not setting target SDK -- but only if the min SDK is somewhat
// old so there's some compatibility stuff kicking in (such as the menu
// button etc)
- if (context.isEnabled(USES_SDK)) {
+ if (context.isEnabled(USES_SDK) && !context.getMainProject().isGradleProject()) {
context.report(USES_SDK, element, context.getLocation(element),
"<uses-sdk> tag should specify a target API level (the " +
"highest verified version; when running on later versions, " +
@@ -597,18 +629,32 @@
}
}
+ if (tag.equals(TAG_USES_PERMISSION)) {
+ Attr name = element.getAttributeNodeNS(ANDROID_URI, ATTR_NAME);
+ if (name != null && name.getValue().equals(MOCK_LOCATION_PERMISSION)
+ && context.getMainProject().isGradleProject()
+ && !isDebugManifest(context, context.file)) {
+ String message = "Mock locations should only be requested in a debug-specific "
+ + "manifest file (typically src/debug/AndroidManifest.xml)";
+ Location location = context.getLocation(name);
+ context.report(MOCK_LOCATION, element, location, message, null);
+ }
+ }
+
if (tag.equals(TAG_APPLICATION)) {
mSeenApplication = true;
if (!element.hasAttributeNS(ANDROID_URI, SdkConstants.ATTR_ALLOW_BACKUP)
&& context.isEnabled(ALLOW_BACKUP)
&& context.getMainProject().getMinSdk() >= 4) {
+ // TODO: For gradle, just needs to be set SOMEWHERE
context.report(ALLOW_BACKUP, element, context.getLocation(element),
"Should explicitly set android:allowBackup to true or " +
"false (it's true by default, and that can have some security " +
"implications for the application's data)", null);
}
- if (!element.hasAttributeNS(ANDROID_URI, SdkConstants.ATTR_ICON)) {
+ if (!element.hasAttributeNS(ANDROID_URI, SdkConstants.ATTR_ICON)
+ && !context.getProject().isLibrary()) {
context.report(APPLICATION_ICON, element, context.getLocation(element),
"Should explicitly set android:icon, there is no default", null);
}
@@ -640,7 +686,23 @@
}
}
- private void checkDeviceAdmin(XmlContext context, Element element) {
+ /** Returns true iff the given manifest file is in a debug-specific source set */
+ private static boolean isDebugManifest(XmlContext context, File manifestFile) {
+ AndroidProject model = context.getProject().getGradleProjectModel();
+ if (model != null) {
+ for (BuildTypeContainer container : model.getBuildTypes()) {
+ if (container.getBuildType().isDebuggable()) {
+ if (manifestFile.equals(container.getSourceProvider().getManifestFile())) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static void checkDeviceAdmin(XmlContext context, Element element) {
List<Element> children = LintUtils.getChildren(element);
boolean requiredIntentFilterFound = false;
boolean deviceAdmin = false;
@@ -682,14 +744,5 @@
+ "android.app.action.DEVICE_ADMIN_ENABLED",
null);
}
-
- }
-
- private String getPackage(Element element) {
- if (mPackage == null) {
- mPackage = element.getOwnerDocument().getDocumentElement().getAttribute(ATTR_PACKAGE);
- }
-
- return mPackage;
}
}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MissingClassDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MissingClassDetector.java
index 5865ce2..33ccbfb 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MissingClassDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MissingClassDetector.java
@@ -21,7 +21,6 @@
import static com.android.SdkConstants.ATTR_CLASS;
import static com.android.SdkConstants.ATTR_FRAGMENT;
import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_PACKAGE;
import static com.android.SdkConstants.CONSTRUCTOR_NAME;
import static com.android.SdkConstants.TAG_ACTIVITY;
import static com.android.SdkConstants.TAG_APPLICATION;
@@ -212,14 +211,13 @@
|| TAG_SERVICE.equals(tag)
|| TAG_RECEIVER.equals(tag)
|| TAG_PROVIDER.equals(tag)) {
- Element root = element.getOwnerDocument().getDocumentElement();
- pkg = root.getAttribute(ATTR_PACKAGE);
Attr attr = element.getAttributeNodeNS(ANDROID_URI, ATTR_NAME);
if (attr == null) {
return;
}
className = attr.getValue();
classNameNode = attr;
+ pkg = context.getMainProject().getPackage();
} else {
return;
}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/NamespaceDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/NamespaceDetector.java
index f80f8c6..f7de894 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/NamespaceDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/NamespaceDetector.java
@@ -154,10 +154,20 @@
mUnusedNamespaces.put(item.getNodeName().substring(XMLNS_PREFIX.length()),
attribute);
} else if (!value.startsWith("http://")) { //$NON-NLS-1$
- context.report(TYPO, attribute, context.getLocation(attribute),
- "Suspicious namespace: should start with http://", null);
+ if (context.isEnabled(TYPO)) {
+ context.report(TYPO, attribute, context.getLocation(attribute),
+ "Suspicious namespace: should start with http://", null);
+ }
continue;
+ } else if (!value.equals(AUTO_URI) && value.contains("auto") && //$NON-NLS-1$
+ value.startsWith("http://schemas.android.com/")) { //$NON-NLS-1$
+ context.report(RES_AUTO, attribute, context.getLocation(attribute),
+ "Suspicious namespace: Did you mean " + AUTO_URI, null);
+ }
+
+ if (!context.isEnabled(TYPO)) {
+ continue;
}
String name = attribute.getName();
@@ -180,10 +190,6 @@
continue;
}
- if (!context.isEnabled(TYPO)) {
- continue;
- }
-
if (name.equals(XMLNS_A)) {
// For the "android" prefix we always assume that the namespace prefix
// should be our expected prefix, but for the "a" prefix we make sure
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ParcelDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ParcelDetector.java
index 14548bb..bb64ef5 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ParcelDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ParcelDetector.java
@@ -74,6 +74,10 @@
@Override
public void checkClass(@NonNull ClassContext context, @NonNull ClassNode classNode) {
+ // Only applies to concrete classes
+ if ((classNode.access & (Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT)) != 0) {
+ return;
+ }
List interfaces = classNode.interfaces;
if (interfaces != null) {
for (Object o : interfaces) {
@@ -88,7 +92,8 @@
}
}
- private boolean hasCreatorField(@NonNull ClassContext context, @NonNull ClassNode classNode) {
+ private static boolean hasCreatorField(@NonNull ClassContext context,
+ @NonNull ClassNode classNode) {
List<FieldNode> fields = classNode.fields;
if (fields != null) {
for (FieldNode field : fields) {
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PluralsDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PluralsDetector.java
index a0408f4..ae62f0e 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PluralsDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/PluralsDetector.java
@@ -188,7 +188,7 @@
String message = String.format(
"For language \"%1$s\" the following quantities are not relevant: %2$s",
language, formatSet(extra));
- context.report(MISSING, element, context.getLocation(element), message, null);
+ context.report(EXTRA, element, context.getLocation(element), message, null);
}
}
@@ -217,8 +217,9 @@
private static Map<String, EnumSet<Quantity>> sPlurals;
+ @SuppressWarnings({"UnnecessaryLocalVariable", "UnusedDeclaration"})
@Nullable
- public EnumSet<Quantity> getRelevant(@NonNull String language) {
+ public static EnumSet<Quantity> getRelevant(@NonNull String language) {
// Based on the plurals table in plurals.txt in icu4c
if (sPlurals == null) {
EnumSet<Quantity> empty = EnumSet.noneOf(Quantity.class);
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RegistrationDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RegistrationDetector.java
index 63dd2c6..a6492fe 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RegistrationDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RegistrationDetector.java
@@ -22,7 +22,6 @@
import static com.android.SdkConstants.ANDROID_CONTENT_CONTENT_PROVIDER;
import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_PACKAGE;
import static com.android.SdkConstants.TAG_ACTIVITY;
import static com.android.SdkConstants.TAG_PROVIDER;
import static com.android.SdkConstants.TAG_RECEIVER;
@@ -100,7 +99,7 @@
@Override
public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
- String fqcn = getFqcn(element);
+ String fqcn = getFqcn(context, element);
String tag = element.getTagName();
String frameworkClass = tagToClass(tag);
if (frameworkClass != null) {
@@ -127,22 +126,21 @@
* Returns the fully qualified class name for a manifest entry element that
* specifies a name attribute
*
+ * @param context the query context providing the project
* @param element the element
* @return the fully qualified class name
*/
@NonNull
- private static String getFqcn(@NonNull Element element) {
- Element root = element.getOwnerDocument().getDocumentElement();
- String pkg = root.getAttribute(ATTR_PACKAGE);
+ private static String getFqcn(@NonNull XmlContext context, @NonNull Element element) {
String className = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
if (className.startsWith(".")) { //$NON-NLS-1$
- return pkg + className;
+ return context.getMainProject().getPackage() + className;
} else if (className.indexOf('.') == -1) {
// According to the <activity> manifest element documentation, this is not
// valid ( http://developer.android.com/guide/topics/manifest/activity-element.html )
// but it appears in manifest files and appears to be supported by the runtime
// so handle this in code as well:
- return pkg + '.' + className;
+ return context.getMainProject().getPackage() + '.' + className;
} // else: the class name is already a fully qualified class name
return className;
@@ -207,6 +205,13 @@
className, tag, classToTag(wrongClass)),
null);
} else if (!tag.equals(TAG_RECEIVER)) { // don't need to be registered
+ if (context.getMainProject().isGradleProject()) {
+ // Disabled for now; we need to formalize the difference between
+ // the *manifest* package and the variant package, since in some contexts
+ // (such as manifest registrations) we should be using the manifest package,
+ // not the gradle package
+ return;
+ }
Location location = context.getLocation(classNode);
context.report(
ISSUE,
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RtlDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RtlDetector.java
index ad69c5d..08acba2 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RtlDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/RtlDetector.java
@@ -221,9 +221,9 @@
@Override
public void afterCheckProject(@NonNull Context context) {
if (mUsesRtlAttributes && mEnabledRtlSupport == null && rtlApplies(context)) {
- File manifestFile = context.getMainProject().getManifestFile();
- if (manifestFile != null) {
- Location location = Location.create(manifestFile);
+ List<File> manifestFile = context.getMainProject().getManifestFiles();
+ if (!manifestFile.isEmpty()) {
+ Location location = Location.create(manifestFile.get(0));
context.report(ENABLED, location,
"The project references RTL attributes, but does not explicitly enable " +
"or disable RTL support with android:supportsRtl in the manifest",
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecureRandomDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecureRandomDetector.java
index 33af4a0..b75ead7 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecureRandomDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecureRandomDetector.java
@@ -66,7 +66,7 @@
.addMoreInfo("http://developer.android.com/reference/java/security/SecureRandom.html");
private static final String SET_SEED = "setSeed"; //$NON-NLS-1$
- private static final String OWNER_SECURE_RANDOM = "java/security/SecureRandom"; //$NON-NLS-1$
+ static final String OWNER_SECURE_RANDOM = "java/security/SecureRandom"; //$NON-NLS-1$
private static final String OWNER_RANDOM = "java/util/Random"; //$NON-NLS-1$
private static final String VM_SECURE_RANDOM = 'L' + OWNER_SECURE_RANDOM + ';';
/** Method description for a method that takes a long argument (no return type specified */
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecureRandomGeneratorDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecureRandomGeneratorDetector.java
new file mode 100644
index 0000000..7c9012d
--- /dev/null
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SecureRandomGeneratorDetector.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.lint.checks;
+
+import static com.android.SdkConstants.CONSTRUCTOR_NAME;
+import static com.android.tools.lint.checks.SecureRandomDetector.OWNER_SECURE_RANDOM;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.ClassContext;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+import com.android.tools.lint.detector.api.Detector.ClassScanner;
+import com.android.tools.lint.detector.api.Implementation;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.android.tools.lint.detector.api.Location;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.LdcInsnNode;
+import org.objectweb.asm.tree.MethodInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Checks for pseudo random number generator initialization issues
+ */
+public class SecureRandomGeneratorDetector extends Detector implements ClassScanner {
+
+ @SuppressWarnings("SpellCheckingInspection")
+ private static final String BLOG_URL
+ = "https://android-developers.blogspot.com/2013/08/some-securerandom-thoughts.html";
+
+ /** Whether the random number generator is initialized correctly */
+ public static final Issue ISSUE = Issue.create(
+ "TrulyRandom", //$NON-NLS-1$
+ "Weak RNG",
+ "Looks for calls to JCA primitives that may be affected by SecureRandom vulnerability",
+ "Key generation, signing, encryption, and random number generation may not " +
+ "receive cryptographically strong values due to improper initialization of " +
+ "the underlying PRNG on Android 4.3 and below.\n" +
+ "\n" +
+ "If your application relies on cryptographically secure random number generation " +
+ "you should apply the workaround described in " + BLOG_URL + " .\n" +
+ "\n" +
+ "This lint rule is mostly informational; it does not accurately detect whether " +
+ "cryptographically secure RNG is required, or whether the workaround has already " +
+ "been applied. After reading the blog entry and updating your code if necessary, " +
+ "you can disable this lint issue.",
+
+ Category.SECURITY,
+ 9,
+ Severity.WARNING,
+ new Implementation(
+ SecureRandomGeneratorDetector.class,
+ Scope.CLASS_FILE_SCOPE))
+ .addMoreInfo(BLOG_URL);
+
+ private static final String WRAP = "wrap"; //$NON-NLS-1$
+ private static final String UNWRAP = "unwrap"; //$NON-NLS-1$
+ private static final String INIT = "init"; //$NON-NLS-1$
+ private static final String INIT_SIGN = "initSign"; //$NON-NLS-1$
+ private static final String GET_INSTANCE = "getInstance"; //$NON-NLS-1$
+ private static final String FOR_NAME = "forName"; //$NON-NLS-1$
+ private static final String JAVA_LANG_CLASS = "java/lang/Class"; //$NON-NLS-1$
+ private static final String JAVAX_CRYPTO_KEY_GENERATOR = "javax/crypto/KeyGenerator";
+ private static final String JAVAX_CRYPTO_KEY_AGREEMENT = "javax/crypto/KeyAgreement";
+ private static final String JAVA_SECURITY_KEY_PAIR_GENERATOR =
+ "java/security/KeyPairGenerator";
+ private static final String JAVAX_CRYPTO_SIGNATURE = "javax/crypto/Signature";
+ private static final String JAVAX_CRYPTO_CIPHER = "javax/crypto/Cipher";
+ private static final String JAVAX_NET_SSL_SSLENGINE = "javax/net/ssl/SSLEngine";
+
+ /** Constructs a new {@link SecureRandomGeneratorDetector} */
+ public SecureRandomGeneratorDetector() {
+ }
+
+ // ---- Implements ClassScanner ----
+
+ @Nullable
+ @Override
+ public List<String> getApplicableCallOwners() {
+ return Arrays.asList(
+ JAVAX_CRYPTO_KEY_GENERATOR,
+ JAVA_SECURITY_KEY_PAIR_GENERATOR,
+ JAVAX_CRYPTO_KEY_AGREEMENT,
+ OWNER_SECURE_RANDOM,
+ JAVAX_NET_SSL_SSLENGINE,
+ JAVAX_CRYPTO_SIGNATURE,
+ JAVAX_CRYPTO_CIPHER
+ );
+ }
+
+ @Nullable
+ @Override
+ public List<String> getApplicableCallNames() {
+ return Collections.singletonList(FOR_NAME);
+ }
+
+ /** Location of first call to key generator (etc), if any */
+ private Location mLocation;
+
+ /** Whether the issue should be ignored (because we have a workaround, or because
+ * we're only targeting correct implementations, etc */
+ private boolean mIgnore;
+
+ @Override
+ public void checkCall(@NonNull ClassContext context, @NonNull ClassNode classNode,
+ @NonNull MethodNode method, @NonNull MethodInsnNode call) {
+ if (mIgnore) {
+ return;
+ }
+
+ String owner = call.owner;
+ String name = call.name;
+
+ // Look for the workaround code: if we see a Class.forName on the harmony NativeCrypto,
+ // we'll consider that a sign.
+
+ if (name.equals(FOR_NAME)) {
+ if (call.getOpcode() != Opcodes.INVOKESTATIC ||
+ !owner.equals(JAVA_LANG_CLASS)) {
+ return;
+ }
+ AbstractInsnNode prev = LintUtils.getPrevInstruction(call);
+ if (prev instanceof LdcInsnNode) {
+ Object cst = ((LdcInsnNode)prev).cst;
+ //noinspection SpellCheckingInspection
+ if (cst instanceof String &&
+ "org.apache.harmony.xnet.provider.jsse.NativeCrypto".equals(cst)) {
+ mIgnore = true;
+ }
+ }
+ return;
+ }
+
+ // Look for calls that probably require a properly initialized random number generator.
+ assert owner.equals(JAVAX_CRYPTO_KEY_GENERATOR)
+ || owner.equals(JAVA_SECURITY_KEY_PAIR_GENERATOR)
+ || owner.equals(JAVAX_CRYPTO_KEY_AGREEMENT)
+ || owner.equals(OWNER_SECURE_RANDOM)
+ || owner.equals(JAVAX_CRYPTO_CIPHER)
+ || owner.equals(JAVAX_CRYPTO_SIGNATURE)
+ || owner.equals(JAVAX_NET_SSL_SSLENGINE) : owner;
+ boolean warn = false;
+
+ if (owner.equals(JAVAX_CRYPTO_SIGNATURE)) {
+ warn = name.equals(INIT_SIGN);
+ } else if (owner.equals(JAVAX_CRYPTO_CIPHER)) {
+ if (name.equals(INIT)) {
+ int arity = getDescArity(call.desc);
+ AbstractInsnNode node = call;
+ for (int i = 0; i < arity; i++) {
+ node = LintUtils.getPrevInstruction(node);
+ if (node == null) {
+ break;
+ }
+ }
+ if (node != null) {
+ int opcode = node.getOpcode();
+ if (opcode == Opcodes.ICONST_3 || // Cipher.WRAP_MODE
+ opcode == Opcodes.ICONST_1) { // Cipher.ENCRYPT_MODE
+ warn = true;
+ }
+ }
+ }
+ } else if (name.equals(GET_INSTANCE) || name.equals(CONSTRUCTOR_NAME)
+ || name.equals(WRAP) || name.equals(UNWRAP)) { // For SSLEngine
+ warn = true;
+ }
+
+ if (warn) {
+ if (mLocation != null) {
+ return;
+ }
+ if (context.getMainProject().getMinSdk() > 18) {
+ // Fix no longer needed
+ mIgnore = true;
+ return;
+ }
+
+ if (context.getDriver().isSuppressed(ISSUE, classNode, method, call)) {
+ mIgnore = true;
+ } else {
+ mLocation = context.getLocation(call);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ static int getDescArity(String desc) {
+ int arity = 0;
+ // For example, (ILjava/security/Key;)V => 2
+ for (int i = 1, max = desc.length(); i < max; i++) {
+ char c = desc.charAt(i);
+ if (c == ')') {
+ break;
+ } else if (c == 'L') {
+ arity++;
+ i = desc.indexOf(';', i);
+ assert i != -1 : desc;
+ } else {
+ arity++;
+ }
+ }
+
+ return arity;
+ }
+
+ @Override
+ public void afterCheckProject(@NonNull Context context) {
+ if (mLocation != null && !mIgnore) {
+ String message = "Potentially insecure random numbers on Android 4.3 and older. "
+ + "Read " + BLOG_URL + " for more info.";
+ context.report(ISSUE, mLocation, message, null);
+ }
+ }
+}
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ServiceCastDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ServiceCastDetector.java
index 619766b..4af96c0 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ServiceCastDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ServiceCastDetector.java
@@ -24,10 +24,8 @@
import com.android.tools.lint.detector.api.Implementation;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.JavaContext;
-import com.android.tools.lint.detector.api.LintUtils;
import com.android.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
-import com.android.utils.SdkUtils;
import com.google.common.collect.Maps;
import java.io.File;
@@ -37,19 +35,9 @@
import lombok.ast.AstVisitor;
import lombok.ast.Cast;
-import lombok.ast.ConstructorDeclaration;
import lombok.ast.Expression;
-import lombok.ast.ForwardingAstVisitor;
-import lombok.ast.MethodDeclaration;
import lombok.ast.MethodInvocation;
-import lombok.ast.Node;
-import lombok.ast.NormalTypeBody;
-import lombok.ast.Return;
-import lombok.ast.Select;
import lombok.ast.StrictListAccessor;
-import lombok.ast.VariableDeclaration;
-import lombok.ast.VariableDefinition;
-import lombok.ast.VariableReference;
/**
* Detector looking for casts on th result of context.getSystemService which are suspect
@@ -109,7 +97,8 @@
return;
}
- String message = String.format("Suspicious cast to %1$s for a %2$s: expected %3$s",
+ String message = String.format(
+ "Suspicious cast to %1$s for a %2$s: expected %3$s",
stripPackage(castType), name, stripPackage(expectedClass));
context.report(ISSUE, node, context.getLocation(cast), message, null);
}
@@ -134,7 +123,7 @@
}
@Nullable
- private String getExpectedType(@NonNull String value) {
+ private static String getExpectedType(@NonNull String value) {
return getServiceMap().get(value);
}
@@ -175,7 +164,7 @@
"android.view.textservice.TextServicesManager");
sServiceMap.put("UI_MODE_SERVICE", "android.app.UiModeManager");
sServiceMap.put("UI_MODE_SERVICE", "android.app.UiModeManager");
- sServiceMap.put("USB_SERVICE", "android.harware.usb.UsbManager");
+ sServiceMap.put("USB_SERVICE", "android.hardware.usb.UsbManager");
sServiceMap.put("USER_SERVICE", "android.os.UserManager");
sServiceMap.put("VIBRATOR_SERVICE", "android.os.Vibrator");
sServiceMap.put("WALLPAPER_SERVICE", "com.android.server.WallpaperService");
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TextViewDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TextViewDetector.java
index be57a2a..95120ab 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TextViewDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TextViewDetector.java
@@ -150,7 +150,8 @@
&& !element.hasAttributeNS(ANDROID_URI, ATTR_TEXT_IS_SELECTABLE)
&& !element.hasAttributeNS(ANDROID_URI, ATTR_VISIBILITY)
&& !element.hasAttributeNS(ANDROID_URI, ATTR_ON_CLICK)
- && context.getMainProject().getTargetSdk() >= 11) {
+ && context.getMainProject().getTargetSdk() >= 11
+ && context.isEnabled(SELECTABLE)) {
context.report(SELECTABLE, element, context.getLocation(element),
"Consider making the text value selectable by specifying " +
"android:textIsSelectable=\"true\"", null);
@@ -161,7 +162,7 @@
for (int i = 0, n = attributes.getLength(); i < n; i++) {
Attr attribute = (Attr) attributes.item(i);
String name = attribute.getLocalName();
- if (name == null) {
+ if (name == null || name.isEmpty()) {
// Attribute not in a namespace; we only care about the android: ones
continue;
}
@@ -219,7 +220,7 @@
}
}
- if (isEditAttribute && ANDROID_URI.equals(attribute.getNamespaceURI())) {
+ if (isEditAttribute && ANDROID_URI.equals(attribute.getNamespaceURI()) && context.isEnabled(ISSUE)) {
Location location = context.getLocation(attribute);
String message;
String view = element.getTagName();
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TypoDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TypoDetector.java
index 87e0b32..67acd5f 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TypoDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/TypoDetector.java
@@ -17,6 +17,7 @@
package com.android.tools.lint.checks;
import static com.android.SdkConstants.ATTR_LOCALE;
+import static com.android.SdkConstants.ATTR_TRANSLATABLE;
import static com.android.SdkConstants.FD_RES_VALUES;
import static com.android.SdkConstants.TAG_STRING;
import static com.android.SdkConstants.TOOLS_URI;
@@ -30,6 +31,7 @@
import com.android.tools.lint.detector.api.Context;
import com.android.tools.lint.detector.api.Implementation;
import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Location;
import com.android.tools.lint.detector.api.ResourceXmlDetector;
import com.android.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
@@ -39,6 +41,7 @@
import com.google.common.base.Charsets;
import com.google.common.base.Splitter;
+import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
@@ -229,31 +232,32 @@
return;
}
- visit(context, element);
+ visit(context, element, element);
}
- private void visit(XmlContext context, Node node) {
+ private void visit(XmlContext context, Element parent, Node node) {
if (node.getNodeType() == Node.TEXT_NODE) {
// TODO: Figure out how to deal with entities
- check(context, node, node.getNodeValue());
+ check(context, parent, node, node.getNodeValue());
} else {
NodeList children = node.getChildNodes();
for (int i = 0, n = children.getLength(); i < n; i++) {
- visit(context, children.item(i));
+ visit(context, parent, children.item(i));
}
}
}
- private void check(XmlContext context, Node node, String text) {
+ private void check(XmlContext context, Element element, Node node, String text) {
int max = text.length();
int index = 0;
+ int lastWordBegin = -1;
+ int lastWordEnd = -1;
boolean checkedTypos = false;
while (index < max) {
for (; index < max; index++) {
char c = text.charAt(index);
if (c == '\\') {
index++;
- continue;
} else if (Character.isLetter(c)) {
break;
}
@@ -275,13 +279,13 @@
// If we've already checked words we may have reported typos
// so create a substring from the current word and on.
byte[] utf8Text = text.substring(begin).getBytes(Charsets.UTF_8);
- check(context, node, utf8Text, 0, utf8Text.length, text, begin);
+ check(context, element, node, utf8Text, 0, utf8Text.length, text, begin);
} else {
// If all we've done so far is skip whitespace (common scenario)
// then no need to substring the text, just re-search with the
// UTF-8 routines
byte[] utf8Text = text.getBytes(Charsets.UTF_8);
- check(context, node, utf8Text, 0, utf8Text.length, text, 0);
+ check(context, element, node, utf8Text, 0, utf8Text.length, text, 0);
}
return;
}
@@ -289,17 +293,53 @@
int end = index;
checkedTypos = true;
+ assert mLookup != null;
List<String> replacements = mLookup.getTypos(text, begin, end);
- if (replacements != null) {
+ if (replacements != null && isTranslatable(element)) {
reportTypo(context, node, text, begin, replacements);
}
+ checkRepeatedWords(context, element, node, text, lastWordBegin, lastWordEnd, begin,
+ end);
+
+ lastWordBegin = begin;
+ lastWordEnd = end;
index = end + 1;
}
}
- private void check(XmlContext context, Node node, byte[] utf8Text,
+ private static void checkRepeatedWords(XmlContext context, Element element, Node node,
+ String text, int lastWordBegin, int lastWordEnd, int begin, int end) {
+ if (lastWordBegin != -1 && end - begin == lastWordEnd - lastWordBegin
+ && end - begin > 1) {
+ // See whether we have a repeated word
+ boolean different = false;
+ for (int i = lastWordBegin, j = begin; i < lastWordEnd; i++, j++) {
+ if (text.charAt(i) != text.charAt(j)) {
+ different = true;
+ break;
+ }
+ }
+ if (!different && onlySpace(text, lastWordEnd, begin) && isTranslatable(element)) {
+ reportRepeatedWord(context, node, text, lastWordBegin, begin, end);
+ }
+ }
+ }
+
+ private static boolean onlySpace(String text, int fromInclusive, int toExclusive) {
+ for (int i = fromInclusive; i < toExclusive; i++) {
+ if (!Character.isWhitespace(text.charAt(i))) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private void check(XmlContext context, Element element, Node node, byte[] utf8Text,
int byteStart, int byteEnd, String text, int charStart) {
+ int lastWordBegin = -1;
+ int lastWordEnd = -1;
int index = byteStart;
while (index < byteEnd) {
// Find beginning of word
@@ -353,14 +393,24 @@
int end = index;
List<String> replacements = mLookup.getTypos(utf8Text, begin, end);
- if (replacements != null) {
+ if (replacements != null && isTranslatable(element)) {
reportTypo(context, node, text, charStart, replacements);
}
+ checkRepeatedWords(context, element, node, text, lastWordBegin, lastWordEnd, charStart,
+ charEnd);
+
+ lastWordBegin = charStart;
+ lastWordEnd = charEnd;
charStart = charEnd;
}
}
+ private static boolean isTranslatable(Element element) {
+ Attr translatable = element.getAttributeNode(ATTR_TRANSLATABLE);
+ return translatable == null || Boolean.valueOf(translatable.getValue());
+ }
+
/** Report the typo found at the given offset and suggest the given replacements */
private static void reportTypo(XmlContext context, Node node, String text, int begin,
List<String> replacements) {
@@ -411,6 +461,17 @@
context.report(ISSUE, node, context.getLocation(node, begin, end), message, null);
}
+ /** Reports a repeated word */
+ private static void reportRepeatedWord(XmlContext context, Node node, String text,
+ int lastWordBegin,
+ int begin, int end) {
+ String message = String.format(
+ "Repeated word \"%1$s\" in message: possible typo",
+ text.substring(begin, end));
+ Location location = context.getLocation(node, lastWordBegin, end);
+ context.report(ISSUE, node, location, message, null);
+ }
+
/** Returns the suggested replacements, if any, for the given typo. The error
* message <b>must</b> be one supplied by lint.
*
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UnusedResourceDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UnusedResourceDetector.java
index caa4cb6..974c108 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UnusedResourceDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UnusedResourceDetector.java
@@ -32,6 +32,7 @@
import static com.android.SdkConstants.R_ID_PREFIX;
import static com.android.SdkConstants.R_PREFIX;
import static com.android.SdkConstants.TAG_ARRAY;
+import static com.android.SdkConstants.TAG_INTEGER_ARRAY;
import static com.android.SdkConstants.TAG_ITEM;
import static com.android.SdkConstants.TAG_PLURALS;
import static com.android.SdkConstants.TAG_RESOURCES;
@@ -372,6 +373,7 @@
TAG_RESOURCES,
TAG_ARRAY,
TAG_STRING_ARRAY,
+ TAG_INTEGER_ARRAY,
TAG_PLURALS
);
}
@@ -428,6 +430,7 @@
assert TAG_STYLE.equals(element.getTagName())
|| TAG_ARRAY.equals(element.getTagName())
|| TAG_PLURALS.equals(element.getTagName())
+ || TAG_INTEGER_ARRAY.equals(element.getTagName())
|| TAG_STRING_ARRAY.equals(element.getTagName());
for (Element item : LintUtils.getChildren(element)) {
checkChildRefs(item);
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WakelockDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WakelockDetector.java
index 5e1c41c..9807e0a 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WakelockDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WakelockDetector.java
@@ -306,7 +306,7 @@
* @return true if the target was reached
* XXX RETURN VALUES ARE WRONG AS OF RIGHT NOW
*/
- protected int dfs(ControlFlowGraph.Node node) {
+ protected static int dfs(ControlFlowGraph.Node node) {
AbstractInsnNode instruction = node.instruction;
if (instruction.getType() == AbstractInsnNode.JUMP_INSN) {
int opcode = instruction.getOpcode();
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongCaseDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongCaseDetector.java
index 0c2a045..40cd5d6 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongCaseDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/WrongCaseDetector.java
@@ -34,7 +34,7 @@
/**
* Check which looks for missing wrong case usage for certain layout tags.
*
- * @todo Generalize this to handling spelling errors in general.
+ * TODO: Generalize this to handling spelling errors in general.
*/
public class WrongCaseDetector extends LayoutDetector {
/** Using the wrong case for layout tags */
diff --git a/manifest-merger/build.gradle b/manifest-merger/build.gradle
deleted file mode 100644
index f0cc1f1..0000000
--- a/manifest-merger/build.gradle
+++ /dev/null
@@ -1,63 +0,0 @@
-group = 'com.android.tools.build'
-archivesBaseName = 'manifest-merger'
-
-dependencies {
- compile project(':common')
- compile project(':sdklib')
- compile 'kxml2:kxml2:2.3.0'
-
- testCompile project(':sdklib').sourceSets.test.output
- testCompile 'junit:junit:3.8.1'
-}
-
-sourceSets {
- main.resources.srcDir 'src/main/java'
- test.resources.srcDir 'src/test/java'
-}
-
-jar {
- from 'NOTICE'
-}
-
-uploadArchives {
- repositories {
- mavenDeployer {
- beforeDeployment { MavenDeployment deployment ->
- if (!project.has("release")) {
- throw new StopExecutionException("uploadArchives must be called with the release.gradle init script")
- }
-
- signing.signPom(deployment)
- }
-
- repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
- authentication(userName: project.ext.sonatypeUsername, password: project.ext.sonatypePassword)
- }
-
- pom.project {
- name 'Android Tools Manifest Merger library'
- description 'A Library to merge Android manifests.'
- url 'http://tools.android.com'
- inceptionYear '2007'
-
- licenses {
- license {
- name 'The Apache Software License, Version 2.0'
- url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
- distribution 'repo'
- }
- }
-
- scm {
- url "https://android.googlesource.com/platform/tools/base"
- connection "git://android.googlesource.com/platform/tools/base.git"
- }
- developers {
- developer {
- name 'The Android Open Source Project'
- }
- }
- }
- }
- }
-}
diff --git a/manifest-merger/src/main/java/com/android/manifmerger/ManifestMerger.java b/manifest-merger/src/main/java/com/android/manifmerger/ManifestMerger.java
deleted file mode 100755
index 93409f5..0000000
--- a/manifest-merger/src/main/java/com/android/manifmerger/ManifestMerger.java
+++ /dev/null
@@ -1,1596 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.manifmerger;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.manifmerger.IMergerLog.FileAndLine;
-import com.android.manifmerger.IMergerLog.Severity;
-import com.android.utils.XmlUtils;
-import com.android.xml.AndroidXPathFactory;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import javax.xml.xpath.XPath;
-import javax.xml.xpath.XPathConstants;
-import javax.xml.xpath.XPathExpressionException;
-
-/**
- * Merges a library manifest into a main application manifest.
- * <p/>
- * To use, create with {@link ManifestMerger#ManifestMerger(IMergerLog, ICallback)} then
- * call {@link ManifestMerger#process(File, File, File[], Map)}.
- * <p/>
- * <pre> Merge operations:
- * - root manifest: attributes ignored, warn if defined.
- * - application:
- * G- {@code @attributes}: most attributes are ignored in libs
- * except: application:name if defined, it must match.
- * except: application:agentBackup if defined, it must match.
- * (these represent class names and we don't want a lib to assume their app or backup
- * classes are being used when that will never be the case.)
- * C- activity / activity-alias / service / receiver / provider
- * => Merge as-is. Error if exists in the destination (same {@code @name})
- * unless the definitions are exactly the same.
- * New elements are always merged at the end of the application element.
- * => Indicate if there's a dup.
- * D- uses-library
- * => Merge. OK if already exists same {@code @name}.
- * => Merge {@code @required}: true>false.
- * C- meta-data
- * => Merge as-is. Error if exists in the destination (same {@code @name})
- * unless the definitions are exactly the same.
- * New elements are always merged at the end of the application element.
- * => Indicate if there's a dup.
- * A- instrumentation:
- * => Do not merge. ignore the ones from libs.
- * C- permission / permission-group / permission-tree:
- * => Merge as-is. Error if exists in the destination (same {@code @name})
- * unless the definitions are exactly the same.
- * C- uses-permission:
- * => Add. OK if already defined.
- * E- uses-sdk:
- * {@code @minSdkVersion}: error if dest<lib. Never automatically change dest minsdk.
- * Codenames are accepted if we can resolve their API level.
- * {@code @targetSdkVersion}: warning if dest<lib.
- * Never automatically change dest targetsdk.
- * {@code @maxSdkVersion}: obsolete, ignored. Not used in comparisons and not merged.
- * D- uses-feature with {@code @name}:
- * => Merge with same {@code @name}
- * => Merge {@code @required}: true>false.
- * - Do not merge any {@code @glEsVersion} attribute at this point.
- * F- uses-feature with {@code @glEsVersion}:
- * => Error if defined in lib+dest with dest<lib. Never automatically change dest.
- * B- uses-configuration:
- * => There can be many. Error if source defines one that is not an exact match in dest.
- * (e.g. right now app must manually define something that matches exactly each lib)
- * B- supports-screens / compatible-screens:
- * => Do not merge.
- * => Error (warn?) if defined in lib and not strictly the same as in dest.
- * B- supports-gl-texture:
- * => Do not merge. Can have more than one.
- * => Error (warn?) if defined in lib and not present as-is in dest.
- *
- * Strategies:
- * A = Ignore, do not merge (no-op).
- * B = Do not merge but if defined in both must match equally.
- * C = Must not exist in dest or be exactly the same (key is the {@code @name} attribute).
- * D = Add new or merge with same key {@code @name}, adjust {@code @required} true>false.
- * E, F, G = Custom strategies; see above.
- *
- * What happens when merging libraries with conflicting information?
- * Say for example a main manifest has a minSdkVersion of 3, whereas libraries have
- * a minSdkVersion of 4 and 11. We could have 2 point of views:
- * - Play it safe: If we have a library with a minSdkVersion of 11, it means this
- * library code knows it can't work reliably on a lower API level. So the safest end
- * result would be a merged manifest with the highest minSdkVersion of all libraries.
- * - Trust the main manifest: When an app declares a given minSdkVersion, it also expects
- * to run a given range of devices. If we change the final minSdkVersion, the app won't
- * be available on as many devices as the developer might expect. And as a counterpoint
- * to issue 1, the app may be careful and not call the library without checking the
- * necessary features or APIs are available before hand.
- * Both points of views are conflicting. The solution taken here is to be conservative
- * and generate an error rather than merge and change a value that might be surprising.
- * On the other hand this can be problematic and force a developer to keep the main
- * manifest in sync with the libraries ones, in essence reducing the usefulness of the
- * automated merge to pure trivial cases. The idea is to just start this way and enhance
- * or revisit the mechanism later.
- * </pre>
- */
-public class ManifestMerger {
-
- /** Logger object. Never null. */
- private final IMergerLog mLog;
- /** An optional callback that the merger can use to query the calling SDK. */
- private final ICallback mCallback;
- private XPath mXPath;
- private Document mMainDoc;
- /** Option to extract the package prefixes from the merged manifest. */
- private boolean mExtractPackagePrefix;
-
- /** Namespace for Android attributes in an AndroidManifest.xml */
- private static final String NS_URI = SdkConstants.NS_RESOURCES;
- /** Prefix for the Android namespace to use in XPath expressions. */
- private static final String NS_PREFIX = AndroidXPathFactory.DEFAULT_NS_PREFIX;
- /** Namespace used in XML files for Android Tooling attributes */
- private static final String TOOLS_URI = SdkConstants.TOOLS_URI;
- /** The name of the tool:merge attribute, to either override or ignore merges. */
- private static final String MERGE_ATTR = "merge"; //$NON-NLS-1$
- /** tool:merge="override" means to ignore what comes from libraries and only keep the
- * version from the main manifest. No conflict can be generated. */
- private static final String MERGE_OVERRIDE = "override"; //$NON-NLS-1$
- /** tool:merge="remove" means to remove a node and prevent merging -- not only is the
- * node from the libraries not merged, but the element is removed from the main manifest. */
- private static final String MERGE_REMOVE = "remove"; //$NON-NLS-1$
-
- /**
- * Sets of element/attribute that need to be treated as class names.
- * The attribute name must be the local name for the Android namespace.
- * For example "application/name" maps to <application android:name=...>.
- */
- private static final String[] sClassAttributes = {
- "application/name",
- "application/backupAgent",
- "activity/name",
- "activity-alias/name",
- "receiver/name",
- "service/name",
- "provider/name",
- "instrumentation/name"
- };
-
- /**
- * Creates a new {@link ManifestMerger}.
- *
- * @param log A non-null merger log to capture all warnings, errors and their location.
- * @param callback An optional callback that the merger can use to query the calling SDK.
- */
- public ManifestMerger(@NonNull IMergerLog log, @Nullable ICallback callback) {
- mLog = log;
- mCallback = callback;
- }
-
- /**
- * Sets whether the manifest merger should extract package prefixes.
- * <p/>
- * When true, the merged document is revisited and class names attributes
- * are shortened when possible, e.g. the package prefix is removed from the
- * class name if it matches.
- *
- * @param extract If true, extract package prefixes.
- * @return this, for constructor chaining
- */
- public ManifestMerger setExtractPackagePrefix(boolean extract) {
- mExtractPackagePrefix = extract;
- return this;
- }
-
- /**
- * Performs the merge operation.
- * <p/>
- * This does NOT stop on errors, in an attempt to accumulate as much
- * info as possible to return to the user.
- * Unless it failed to read the main manifest, a result file will be
- * created. However if process() returns false, the file should not
- * be used except for debugging purposes.
- *
- * @param outputFile The output path to generate. Can be the same as the main path.
- * @param mainFile The main manifest paths to read. What we merge into.
- * @param libraryFiles The library manifest paths to read. Must not be null.
- * @param injectAttributes A map of attributes to inject in the form [pseudo-xpath] => value.
- * The key is "/manifest/elements...|attribute-ns-uri attribute-local-name",
- * for example "/manifest/uses-sdk|http://schemas.android.com/apk/res/android minSdkVersion".
- * (note the space separator between the attribute URI and its local name.)
- * The elements will be created if they don't exists. Existing attributes will be modified.
- * The replacement is done on the main document <em>before</em> merging.
- * @param packageOverride an optional package override. This only affects the package attribute,
- * all components (activities, receivers, etc...) are not affected by this.
- * @return True if the merge was completed, false otherwise.
- */
- public boolean process(
- File outputFile,
- File mainFile,
- File[] libraryFiles,
- Map<String, String> injectAttributes,
- String packageOverride) {
- Document mainDoc = MergerXmlUtils.parseDocument(mainFile, mLog);
- if (mainDoc == null) {
- mLog.error(Severity.ERROR, new FileAndLine(mainFile.getAbsolutePath(), 0),
- "Failed to read manifest file.");
- return false;
- }
-
- boolean success = process(mainDoc, libraryFiles, injectAttributes, packageOverride);
-
- if (!MergerXmlUtils.printXmlFile(mainDoc, outputFile, mLog)) {
- mLog.error(Severity.ERROR, new FileAndLine(outputFile.getAbsolutePath(), 0),
- "Failed to write manifest file.");
- success = false;
- }
-
- return success;
- }
-
- /**
- * Performs the merge operation in-place in the given DOM.
- * <p/>
- * This does NOT stop on errors, in an attempt to accumulate as much
- * info as possible to return to the user.
- * <p/>
- * The method might modify the input XML document in-place for its own processing.
- *
- * @param mainDoc The document to merge into. Will be modified in-place.
- * @param libraryFiles The library manifest paths to read. Must not be null.
- * These will be modified in-place.
- * @param injectAttributes A map of attributes to inject in the form [pseudo-xpath] => value.
- * The key is "/manifest/elements...|attribute-ns-uri attribute-local-name",
- * for example "/manifest/uses-sdk|http://schemas.android.com/apk/res/android minSdkVersion".
- * (note the space separator between the attribute URI and its local name.)
- * The elements will be created if they don't exists. Existing attributes will be modified.
- * The replacement is done on the main document <em>before</em> merging.
- * @param packageOverride an optional package override. This only affects the package attribute,
- * all components (activities, receivers, etc...) are not affected by this.
- * @return True on success, false if any error occurred (printed to the {@link IMergerLog}).
- */
- public boolean process(
- Document mainDoc,
- File[] libraryFiles,
- Map<String, String> injectAttributes,
- String packageOverride) {
-
- boolean success = true;
- mMainDoc = mainDoc;
- MergerXmlUtils.decorateDocument(mainDoc, IMergerLog.MAIN_MANIFEST);
- MergerXmlUtils.injectAttributes(mainDoc, injectAttributes, mLog);
-
- String prefix = XmlUtils.lookupNamespacePrefix(mainDoc, SdkConstants.NS_RESOURCES);
- mXPath = AndroidXPathFactory.newXPath(prefix);
-
- expandFqcns(mainDoc);
- for (File libFile : libraryFiles) {
- Document libDoc = MergerXmlUtils.parseDocument(libFile, mLog);
- if (libDoc == null || !mergeLibDoc(cleanupToolsAttributes(libDoc))) {
- success = false;
- }
- }
-
- if (packageOverride != null) {
- MergerXmlUtils.injectAttributes(mainDoc,
- Collections.singletonMap("/manifest| package", packageOverride),
- mLog);
- }
-
- cleanupToolsAttributes(mainDoc);
-
- if (mExtractPackagePrefix) {
- extractFqcns(mainDoc);
- }
-
- mXPath = null;
- mMainDoc = null;
- return success;
- }
-
- /**
- * Performs the merge operation in-place in the given DOM.
- * <p/>
- * This does NOT stop on errors, in an attempt to accumulate as much
- * info as possible to return to the user.
- * <p/>
- * The method might modify the input XML documents in-place for its own processing.
- *
- * @param mainDoc The document to merge into. Will be modified in-place.
- * @param libraryDocs The library manifest documents to merge in. Must not be null.
- * These will be modified in-place.
- * @return True on success, false if any error occurred (printed to the {@link IMergerLog}).
- */
- public boolean process(@NonNull Document mainDoc, @NonNull Document... libraryDocs) {
-
- boolean success = true;
- mMainDoc = mainDoc;
- MergerXmlUtils.decorateDocument(mainDoc, IMergerLog.MAIN_MANIFEST);
-
- String prefix = XmlUtils.lookupNamespacePrefix(mainDoc, SdkConstants.NS_RESOURCES);
- mXPath = AndroidXPathFactory.newXPath(prefix);
-
- expandFqcns(mainDoc);
- for (Document libDoc : libraryDocs) {
- MergerXmlUtils.decorateDocument(libDoc, IMergerLog.LIBRARY);
- if (!mergeLibDoc(cleanupToolsAttributes(libDoc))) {
- success = false;
- }
- }
-
- cleanupToolsAttributes(mainDoc);
- mXPath = null;
- mMainDoc = null;
- return success;
- }
-
- // --------
-
- /**
- * Merges the given library manifest into the destination manifest.
- * See {@link ManifestMerger} for merge details.
- *
- * @param libDoc The library document to merge from. Must not be null.
- * @return True on success, false if any error occurred (printed to the {@link IMergerLog}).
- */
- private boolean mergeLibDoc(Document libDoc) {
-
- boolean err = false;
-
- expandFqcns(libDoc);
-
- // Strategy G (check <application> is compatible)
- err |= !checkApplication(libDoc);
-
- // Strategy B
- err |= !doNotMergeCheckEqual("/manifest/uses-configuration", libDoc); //$NON-NLS-1$
- err |= !doNotMergeCheckEqual("/manifest/supports-screens", libDoc); //$NON-NLS-1$
- err |= !doNotMergeCheckEqual("/manifest/compatible-screens", libDoc); //$NON-NLS-1$
- err |= !doNotMergeCheckEqual("/manifest/supports-gl-texture", libDoc); //$NON-NLS-1$
-
- boolean skipApplication = hasOverrideOrRemoveTag(
- findFirstElement(mMainDoc, "/manifest/application")); //$NON-NLS-1$
-
- // Strategy C
- if (!skipApplication) {
- err |= !mergeNewOrEqual(
- "/manifest/application/activity", //$NON-NLS-1$
- "name", //$NON-NLS-1$
- libDoc,
- true);
- err |= !mergeNewOrEqual(
- "/manifest/application/activity-alias", //$NON-NLS-1$
- "name", //$NON-NLS-1$
- libDoc,
- true);
- err |= !mergeNewOrEqual(
- "/manifest/application/service", //$NON-NLS-1$
- "name", //$NON-NLS-1$
- libDoc,
- true);
- err |= !mergeNewOrEqual(
- "/manifest/application/receiver", //$NON-NLS-1$
- "name", //$NON-NLS-1$
- libDoc,
- true);
- err |= !mergeNewOrEqual(
- "/manifest/application/provider", //$NON-NLS-1$
- "name", //$NON-NLS-1$
- libDoc,
- true);
- }
- err |= !mergeNewOrEqual(
- "/manifest/permission", //$NON-NLS-1$
- "name", //$NON-NLS-1$
- libDoc,
- false);
- err |= !mergeNewOrEqual(
- "/manifest/permission-group", //$NON-NLS-1$
- "name", //$NON-NLS-1$
- libDoc,
- false);
- err |= !mergeNewOrEqual(
- "/manifest/permission-tree", //$NON-NLS-1$
- "name", //$NON-NLS-1$
- libDoc,
- false);
- err |= !mergeNewOrEqual(
- "/manifest/uses-permission", //$NON-NLS-1$
- "name", //$NON-NLS-1$
- libDoc,
- false);
-
- // Strategy D
- if (!skipApplication) {
- err |= !mergeAdjustRequired(
- "/manifest/application/uses-library", //$NON-NLS-1$
- "name", //$NON-NLS-1$
- "required", //$NON-NLS-1$
- libDoc,
- null /*alternateKeyAttr*/);
- err |= !mergeNewOrEqual(
- "/manifest/application/meta-data", //$NON-NLS-1$
- "name", //$NON-NLS-1$
- libDoc,
- true);
- }
- err |= !mergeAdjustRequired(
- "/manifest/uses-feature", //$NON-NLS-1$
- "name", //$NON-NLS-1$
- "required", //$NON-NLS-1$
- libDoc,
- "glEsVersion" /*alternateKeyAttr*/);
-
- // Strategy E
- err |= !checkSdkVersion(libDoc);
-
- // Strategy F
- err |= !checkGlEsVersion(libDoc);
-
- return !err;
- }
-
- /**
- * Expand all possible class names attributes in the given document.
- * <p/>
- * Some manifest attributes represent class names. These can be specified as fully
- * qualified class names or use a short notation consisting of just the terminal
- * class simple name or a dot followed by a partial class name. Unfortunately this
- * makes textual comparison of the attributes impossible. To simplify this, we can
- * modify the document to fully expand all these class names. The list of elements
- * and attributes to process is listed by {@link #sClassAttributes} and the expansion
- * simply consists of appending the manifest' package if defined.
- *
- * @param doc The document in which to expand potential FQCNs.
- */
- private void expandFqcns(Document doc) {
- // Find the package attribute of the manifest.
- String pkg = null;
- Element manifest = findFirstElement(doc, "/manifest");
- if (manifest != null) {
- pkg = manifest.getAttribute("package");
- }
-
- if (pkg == null || pkg.length() == 0) {
- // We can't adjust FQCNs if we don't know the root package name.
- // It's not a proper manifest if this is missing anyway.
- assert manifest != null;
- mLog.error(Severity.WARNING,
- xmlFileAndLine(manifest),
- "Missing 'package' attribute in manifest.");
- return;
- }
-
- for (String elementAttr : sClassAttributes) {
- String[] names = elementAttr.split("/");
- if (names.length != 2) {
- continue;
- }
- String elemName = names[0];
- String attrName = names[1];
- NodeList elements = doc.getElementsByTagName(elemName);
- for (int i = 0; i < elements.getLength(); i++) {
- Node elem = elements.item(i);
- if (elem instanceof Element) {
- Attr attr = ((Element) elem).getAttributeNodeNS(NS_URI, attrName);
- if (attr != null) {
- String value = attr.getNodeValue();
-
- // We know it's a shortened FQCN if it starts with a dot
- // or does not contain any dot.
- if (value != null && value.length() > 0 &&
- (value.indexOf('.') == -1 || value.charAt(0) == '.')) {
- if (value.charAt(0) == '.') {
- value = pkg + value;
- } else {
- value = pkg + '.' + value;
- }
- attr.setNodeValue(value);
- }
- }
- }
- }
- }
- }
-
- /**
- * Extracts the fully qualified class names from the manifest and uses the
- * prefix notation relative to the manifest package. This basically reverses
- * the effects of {@link #expandFqcns(Document)}, though of course it may
- * also remove prefixes which were inlined in the original documents.
- *
- * @param doc the document in which to extract the FQCNs.
- */
- private void extractFqcns(Document doc) {
- // Find the package attribute of the manifest.
- String pkg = null;
- Element manifest = findFirstElement(doc, "/manifest");
- if (manifest != null) {
- pkg = manifest.getAttribute("package");
- }
-
- if (pkg == null || pkg.length() == 0) {
- return;
- }
-
- int pkgLength = pkg.length();
- for (String elementAttr : sClassAttributes) {
- String[] names = elementAttr.split("/");
- if (names.length != 2) {
- continue;
- }
- String elemName = names[0];
- String attrName = names[1];
- NodeList elements = doc.getElementsByTagName(elemName);
- for (int i = 0; i < elements.getLength(); i++) {
- Node elem = elements.item(i);
- if (elem instanceof Element) {
- Attr attr = ((Element) elem).getAttributeNodeNS(NS_URI, attrName);
- if (attr != null) {
- String value = attr.getNodeValue();
-
- // We know it's a shortened FQCN if it starts with a dot
- // or does not contain any dot.
- if (value != null && value.length() > pkgLength &&
- value.startsWith(pkg) && value.charAt(pkgLength) == '.') {
- value = value.substring(pkgLength);
- attr.setNodeValue(value);
- }
- }
- }
- }
- }
- }
-
- /**
- * Checks (but does not merge) the application attributes using the following rules:
- * <pre>
- * - {@code @name}: Ignore if empty. Warning if its expanded FQCN doesn't match the main doc.
- * - {@code @backupAgent}: Ignore if empty. Warning if its expanded FQCN doesn't match main doc.
- * - All other attributes are ignored.
- * </pre>
- * The name and backupAgent represent classes and the merger will warn since if a lib has
- * these defined they will never be used anyway.
- * @param libDoc The library document to merge from. Must not be null.
- * @return True on success, false if any error occurred (printed to the {@link IMergerLog}).
- */
- private boolean checkApplication(Document libDoc) {
-
- Element mainApp = findFirstElement(mMainDoc, "/manifest/application"); //$NON-NLS-1$
- Element libApp = findFirstElement(libDoc, "/manifest/application"); //$NON-NLS-1$
-
- // A manifest does not necessarily define an application.
- // If the lib has none, there's nothing to check for.
- if (libApp == null) {
- return true;
- }
- if (hasOverrideOrRemoveTag(mainApp)) {
- // Don't check the <application> element since it is tagged with override or remove.
- return true;
- }
-
- for (String attrName : new String[] { "name", "backupAgent" }) {
- String libValue = getAttributeValue(libApp, attrName);
- if (libValue == null || libValue.length() == 0) {
- // Nothing to do if the attribute is not defined in the lib.
- continue;
- }
- // The main doc does not have to have an application node.
- String mainValue = mainApp == null ? "" : getAttributeValue(mainApp, attrName);
- if (!libValue.equals(mainValue)) {
- assert mainApp != null;
- mLog.conflict(Severity.WARNING,
- xmlFileAndLine(mainApp),
- xmlFileAndLine(libApp),
- mainApp == null ?
- "Library has <application android:%1$s='%3$s'> but main manifest has no application element." :
- "Main manifest has <application android:%1$s='%2$s'> but library uses %1$s='%3$s'.",
- attrName,
- mainValue,
- libValue);
- }
- }
-
- return true;
- }
-
- /**
- * Do not merge anything. Instead it checks that the requested elements from the
- * given library are all present and equal in the destination and prints a warning
- * if it's not the case.
- * <p/>
- * For example if a library supports a given screen configuration, print a
- * warning if the main manifest doesn't indicate the app supports the same configuration.
- * We should not merge it since we don't want to silently give the impression an app
- * supports a configuration just because it uses a library which does.
- * On the other hand we don't want to silently ignore this fact.
- * <p/>
- * TODO there should be a way to silence this warning.
- * The current behavior is certainly arbitrary and needs to be tweaked somehow.
- *
- * @param path The XPath of the elements to merge from the library. Must not be null.
- * @param libDoc The library document to merge from. Must not be null.
- * @return True on success, false if any error occurred (printed to the {@link IMergerLog}).
- */
- private boolean doNotMergeCheckEqual(String path, Document libDoc) {
-
- for (Element src : findElements(libDoc, path)) {
-
- boolean found = false;
-
- for (Element dest : findElements(mMainDoc, path)) {
- if (hasOverrideOrRemoveTag(dest)) {
- continue;
- }
- if (compareElements(dest, src, false, null /*diff*/, null /*keyAttr*/)) {
- found = true;
- break;
- }
- }
-
- if (!found) {
- mLog.conflict(Severity.WARNING,
- xmlFileAndLine(mMainDoc),
- xmlFileAndLine(src),
- "%1$s defined in library, missing from main manifest:\n%2$s",
- path,
- MergerXmlUtils.dump(src, false /*nextSiblings*/));
- }
- }
-
- return true;
- }
-
- /**
- * Merges the requested elements from the library in the main document.
- * The key attribute name is used to identify the same elements.
- * Merged elements must either not exist in the destination or be identical.
- * <p/>
- * When merging, append to the end of the application element.
- * Also merges any preceding whitespace and up to one comment just prior to the merged element.
- *
- * @param path The XPath of the elements to merge from the library. Must not be null.
- * @param keyAttr The Android-namespace attribute used as key to identify similar elements.
- * E.g. "name" for "android:name"
- * @param libDoc The library document to merge from. Must not be null.
- * @param warnDups When true, will print a warning when a library definition is already
- * present in the destination and is equal.
- * @return True on success, false if any error occurred (printed to the {@link IMergerLog}).
- */
- private boolean mergeNewOrEqual(
- String path,
- String keyAttr,
- Document libDoc,
- boolean warnDups) {
-
- // The parent of XPath /p1/p2/p3 is /p1/p2. To find it, delete the last "/segment"
- int pos = path.lastIndexOf('/');
- assert pos > 1;
- String parentPath = path.substring(0, pos);
- Element parent = findFirstElement(mMainDoc, parentPath);
- assert parent != null;
- if (parent == null) {
- mLog.error(Severity.ERROR,
- xmlFileAndLine(mMainDoc),
- "Could not find element %1$s.",
- parentPath);
- return false;
- }
-
- boolean success = true;
-
- nextSource: for (Element src : findElements(libDoc, path)) {
- String name = getAttributeValue(src, keyAttr);
- if (name.length() == 0) {
- mLog.error(Severity.ERROR,
- xmlFileAndLine(src),
- "Undefined '%1$s' attribute in %2$s.",
- keyAttr, path);
- success = false;
- continue;
- }
-
- // Look for the same item in the destination
- List<Element> dests = findElements(mMainDoc, path, keyAttr, name);
- if (dests.size() > 1) {
- // This should not be happening. We'll just use the first one found in this case.
- mLog.error(Severity.WARNING,
- xmlFileAndLine(dests.get(0)),
- "Manifest has more than one %1$s[@%2$s=%3$s] element.",
- path, keyAttr, name);
- }
- boolean doMerge = true;
- for (Element dest : dests) {
- // Don't try to merge this element since it has tools:merge=override|remove.
- if (hasOverrideOrRemoveTag(dest)) {
- doMerge = false;
- continue;
- }
- // If there's already a similar node in the destination, check it's identical.
- StringBuilder diff = new StringBuilder();
- if (compareElements(dest, src, false, diff, keyAttr)) {
- // Same element. Skip.
- if (warnDups) {
- mLog.conflict(Severity.INFO,
- xmlFileAndLine(dest),
- xmlFileAndLine(src),
- "Skipping identical %1$s[@%2$s=%3$s] element.",
- path, keyAttr, name);
- }
- continue nextSource;
- } else {
- // Print the diff we got from the comparison.
- mLog.conflict(Severity.ERROR,
- xmlFileAndLine(dest),
- xmlFileAndLine(src),
- "Trying to merge incompatible %1$s[@%2$s=%3$s] element:\n%4$s",
- path, keyAttr, name, diff.toString());
- success = false;
- continue nextSource;
- }
- }
-
- if (doMerge) {
- // Ready to merge element src. Select which previous siblings to merge.
- Node start = selectPreviousSiblings(src);
-
- insertAtEndOf(parent, start, src);
- }
- }
-
- return success;
- }
-
- /**
- * Returns the value of the given "android:attribute" in the given element.
- *
- * @param element The non-null element where to extract the attribute.
- * @param attrName The local name of the attribute.
- * It must use the {@link #NS_URI} but no prefix should be specified here.
- * @return The value of the attribute or a non-null empty string if not found.
- */
- private String getAttributeValue(Element element, String attrName) {
- Attr attr = element.getAttributeNodeNS(NS_URI, attrName);
- String value = attr == null ? "" : attr.getNodeValue(); //$NON-NLS-1$
- return value;
- }
-
- /**
- * Merge elements as identified by their key name attribute.
- * The element must have an option boolean "required" attribute which can be either "true" or
- * "false". Default is true if the attribute is missing. When merging, a "false" is superseded
- * by a "true" (explicit or implicit).
- * <p/>
- * When merging, this does NOT merge any other attributes than {@code keyAttr} and
- * {@code requiredAttr}.
- *
- * @param path The XPath of the elements to merge from the library. Must not be null.
- * @param keyAttr The Android-namespace attribute used as key to identify similar elements.
- * E.g. "name" for "android:name"
- * @param requiredAttr The name of the Android-namespace boolean attribute that must be merged.
- * Typically should be "required".
- * @param libDoc The library document to merge from. Must not be null.
- * @param alternateKeyAttr When non-null, this is an alternate valid key attribute. If the
- * default key attribute is missing, we won't output a warning if the alternate one is
- * present.
- * @return True on success, false if any error occurred (printed to the {@link IMergerLog}).
- */
- private boolean mergeAdjustRequired(
- String path,
- String keyAttr,
- String requiredAttr,
- Document libDoc,
- @Nullable String alternateKeyAttr) {
-
- // The parent of XPath /p1/p2/p3 is /p1/p2. To find it, delete the last "/segment"
- int pos = path.lastIndexOf('/');
- assert pos > 1;
- String parentPath = path.substring(0, pos);
- Element parent = findFirstElement(mMainDoc, parentPath);
- assert parent != null;
- if (parent == null) {
- mLog.error(Severity.ERROR,
- xmlFileAndLine(mMainDoc),
- "Could not find element %1$s.",
- parentPath);
- return false;
- }
-
- boolean success = true;
-
- for (Element src : findElements(libDoc, path)) {
- Attr attr = src.getAttributeNodeNS(NS_URI, keyAttr);
- String name = attr == null ? "" : attr.getNodeValue().trim(); //$NON-NLS-1$
- if (name.length() == 0) {
- if (alternateKeyAttr != null) {
- attr = src.getAttributeNodeNS(NS_URI, alternateKeyAttr);
- String s = attr == null ? "" : attr.getNodeValue().trim(); //$NON-NLS-1$
- if (s.length() != 0) {
- // This element lacks the keyAttr but has the alternateKeyAttr. Skip it.
- continue;
- }
- }
-
- mLog.error(Severity.ERROR,
- xmlFileAndLine(src),
- "Undefined '%1$s' attribute in %2$s.",
- keyAttr, path);
- success = false;
- continue;
- }
-
- // Look for the same item in the destination
- List<Element> dests = findElements(mMainDoc, path, keyAttr, name);
- if (dests.size() > 1) {
- // This should not be happening. We'll just use the first one found in this case.
- mLog.error(Severity.WARNING,
- xmlFileAndLine(dests.get(0)),
- "Manifest has more than one %1$s[@%2$s=%3$s] element.",
- path, keyAttr, name);
- }
- if (dests.size() > 0) {
-
- attr = src.getAttributeNodeNS(NS_URI, requiredAttr);
- String value = attr == null ? "true" : attr.getNodeValue(); //$NON-NLS-1$
- if (value == null || !(value.equals("true") || value.equals("false"))) {
- mLog.error(Severity.WARNING,
- xmlFileAndLine(src),
- "Invalid attribute '%1$s' in %2$s[@%3$s=%4$s] element:\nExpected 'true' or 'false' but found '%5$s'.",
- requiredAttr, path, keyAttr, name, value);
- continue;
- }
- boolean boolE = Boolean.parseBoolean(value);
-
- for (Element dest : dests) {
- // Don't try to merge this element since it has tools:merge=override|remove.
- if (hasOverrideOrRemoveTag(dest)) {
- continue;
- }
-
- // Compare the required attributes.
- attr = dest.getAttributeNodeNS(NS_URI, requiredAttr);
- value = attr == null ? "true" : attr.getNodeValue(); //$NON-NLS-1$
- if (value == null || !(value.equals("true") || value.equals("false"))) {
- mLog.error(Severity.WARNING,
- xmlFileAndLine(dest),
- "Invalid attribute '%1$s' in %2$s[@%3$s=%4$s] element:\nExpected 'true' or 'false' but found '%5$s'.",
- requiredAttr, path, keyAttr, name, value);
- continue;
- }
- boolean boolD = Boolean.parseBoolean(value);
-
- if (!boolD && boolE) {
- // Required attributes differ: destination is false and source was true
- // so we need to change the destination to true.
-
- // If attribute was already in the destination, change it in place
- if (attr != null) {
- attr.setNodeValue("true"); //$NON-NLS-1$
- } else {
- // Otherwise, do nothing. The destination doesn't have the
- // required=true attribute, and true is the default value.
- // Consequently not setting is the right thing to do.
-
- // -- code snippet for reference --
- // If we wanted to create a new attribute, we'd use the code
- // below. There's a simpler call to d.setAttributeNS(ns, name, value)
- // but experience shows that it would create a new prefix out of the
- // blue instead of looking it up.
- //
- // Attr a=d.getOwnerDocument().createAttributeNS(NS_URI, requiredAttr);
- // String prefix = d.lookupPrefix(NS_URI);
- // if (prefix != null) {
- // a.setPrefix(prefix);
- // }
- // a.setValue("true"); //$NON-NLS-1$
- // d.setAttributeNodeNS(attr);
- }
- }
- }
- } else {
- // Destination doesn't exist. We simply merge the source element.
- // Select which previous siblings to merge.
- Node start = selectPreviousSiblings(src);
-
- Node node = insertAtEndOf(parent, start, src);
-
- NamedNodeMap attrs = node.getAttributes();
- if (attrs != null) {
- for (int i = 0; i < attrs.getLength(); i++) {
- Node a = attrs.item(i);
- if (a.getNodeType() == Node.ATTRIBUTE_NODE) {
- boolean keep = NS_URI.equals(a.getNamespaceURI());
- if (keep) {
- name = a.getLocalName();
- keep = keyAttr.equals(name) || requiredAttr.equals(name);
- }
- if (!keep) {
- attrs.removeNamedItemNS(NS_URI, name);
- // Restart the loop from index 0 since there's no
- // guarantee on the order of the nodes in the "map".
- // This makes it O(n+2n) at most, where n is [2..3] in
- // a typical case.
- i = -1;
- }
- }
- }
- }
- }
- }
-
- return success;
- }
-
-
-
- /**
- * Checks (but does not merge) uses-feature glEsVersion attribute using the following rules:
- * <pre>
- * - Error if defined in lib+dest with dest<lib.
- * - Never automatically change dest.
- * - Default implied value is 1.0 (0x00010000).
- * </pre>
- *
- * @param libDoc The library document to merge from. Must not be null.
- * @return True on success, false if any error occurred (printed to the {@link IMergerLog}).
- */
- private boolean checkGlEsVersion(Document libDoc) {
-
- String parentPath = "/manifest"; //$NON-NLS-1$
- Element parent = findFirstElement(mMainDoc, parentPath);
- assert parent != null;
- if (parent == null) {
- mLog.error(Severity.ERROR,
- xmlFileAndLine(mMainDoc),
- "Could not find element %1$s.",
- parentPath);
- return false;
- }
-
- // Find the max glEsVersion on the destination side
- String path = "/manifest/uses-feature"; //$NON-NLS-1$
- String keyAttr = "glEsVersion"; //$NON-NLS-1$
- long destGlEsVersion = 0x00010000L; // default minimum is 1.0
- Element destNode = null;
- boolean result = true;
- for (Element dest : findElements(mMainDoc, path)) {
- Attr attr = dest.getAttributeNodeNS(NS_URI, keyAttr);
- String value = attr == null ? "" : attr.getNodeValue().trim(); //$NON-NLS-1$
- if (value.length() != 0) {
- try {
- // Note that the value can be an hex number such as 0x00020001 so we
- // need Integer.decode instead of Integer.parseInt.
- // Note: Integer.decode cannot handle "ffffffff", see JDK issue 6624867
- // so we just treat the version as a long and test like this, ignoring
- // the fact that a value of 0xFFFF/.0xFFFF is probably invalid anyway
- // in the context of glEsVersion.
- long version = Long.decode(value);
- if (version >= destGlEsVersion) {
- destGlEsVersion = version;
- destNode = dest;
- } else if (version < 0x00010000) {
- mLog.error(Severity.WARNING,
- xmlFileAndLine(dest),
- "Ignoring <uses-feature android:glEsVersion='%1$s'> because it's smaller than 1.0.",
- value);
- }
- } catch (NumberFormatException e) {
- // Note: NumberFormatException.toString() has no interesting information
- // so we don't output it.
- mLog.error(Severity.ERROR,
- xmlFileAndLine(dest),
- "Failed to parse <uses-feature android:glEsVersion='%1$s'>: must be an integer in the form 0x00020001.",
- value);
- result = false;
- }
- }
- }
-
- // If we found at least one valid with no error, use that, otherwise bail out.
- if (!result && destNode == null) {
- return false;
- }
-
- // Now find the max glEsVersion on the source side.
-
- long srcGlEsVersion = 0x00010000L; // default minimum is 1.0
- Element srcNode = null;
- result = true;
- for (Element src : findElements(libDoc, path)) {
- Attr attr = src.getAttributeNodeNS(NS_URI, keyAttr);
- String value = attr == null ? "" : attr.getNodeValue().trim(); //$NON-NLS-1$
- if (value.length() != 0) {
- try {
- // See comment on Long.decode above.
- long version = Long.decode(value);
- if (version >= srcGlEsVersion) {
- srcGlEsVersion = version;
- srcNode = src;
- } else if (version < 0x00010000) {
- mLog.error(Severity.WARNING,
- xmlFileAndLine(src),
- "Ignoring <uses-feature android:glEsVersion='%1$s'> because it's smaller than 1.0.",
- value);
- }
- } catch (NumberFormatException e) {
- // Note: NumberFormatException.toString() has no interesting information
- // so we don't output it.
- mLog.error(Severity.ERROR,
- xmlFileAndLine(src),
- "Failed to parse <uses-feature android:glEsVersion='%1$s'>: must be an integer in the form 0x00020001.",
- value);
- result = false;
- }
- }
- }
-
- if (srcNode != null && destGlEsVersion < srcGlEsVersion) {
- mLog.conflict(Severity.WARNING,
- xmlFileAndLine(destNode == null ? mMainDoc : destNode),
- xmlFileAndLine(srcNode),
- "Main manifest has <uses-feature android:glEsVersion='0x%1$08x'> but library uses glEsVersion='0x%2$08x'%3$s",
- destGlEsVersion,
- srcGlEsVersion,
- destNode != null ? "" : //$NON-NLS-1$
- "\nNote: main manifest lacks a <uses-feature android:glEsVersion> declaration, and thus defaults to glEsVersion=0x00010000."
- );
- result = false;
- }
-
- return result;
- }
-
- /**
- * Checks (but does not merge) uses-sdk attributes using the following rules:
- * <pre>
- * - {@code @minSdkVersion}: error if dest<lib. Never automatically change dest minsdk.
- * - {@code @targetSdkVersion}: warning if dest<lib. Never automatically change destination.
- * - {@code @maxSdkVersion}: obsolete, ignored. Not used in comparisons and not merged.
- * - The API level can be a codename if we have a callback that can convert it to an integer.
- * </pre>
- * @param libDoc The library document to merge from. Must not be null.
- * @return True on success, false if any error occurred (printed to the {@link IMergerLog}).
- */
- private boolean checkSdkVersion(Document libDoc) {
-
- boolean result = true;
-
- Element destUsesSdk = findFirstElement(mMainDoc, "/manifest/uses-sdk"); //$NON-NLS-1$
-
- if (hasOverrideOrRemoveTag(destUsesSdk)) {
- // Don't try to check this element since it has tools:merge=override|remove.
- return true;
- }
-
- Element srcUsesSdk = findFirstElement(libDoc, "/manifest/uses-sdk"); //$NON-NLS-1$
-
- AtomicInteger destValue = new AtomicInteger(1);
- AtomicInteger srcValue = new AtomicInteger(1);
- AtomicBoolean destImplied = new AtomicBoolean(true);
- AtomicBoolean srcImplied = new AtomicBoolean(true);
-
- // Check minSdkVersion
- int destMinSdk = 1;
- result = extractSdkVersionAttribute(
- libDoc,
- destUsesSdk, srcUsesSdk,
- "min", //$NON-NLS-1$
- destValue, srcValue,
- destImplied, srcImplied);
-
- if (result) {
- // Make it an error for an application to use a library with a greater
- // minSdkVersion. This means the library code may crash unexpectedly.
- // TODO it would be nice to be able to work around this in case the
- // user think s/he knows what s/he's doing.
- // We could define a simple XML comment flag: <!-- @NoMinSdkVersionMergeError -->
-
- destMinSdk = destValue.get();
-
- if (destMinSdk < srcValue.get()) {
- mLog.conflict(Severity.ERROR,
- xmlFileAndLine(destUsesSdk == null ? mMainDoc : destUsesSdk),
- xmlFileAndLine(srcUsesSdk == null ? libDoc : srcUsesSdk),
- "Main manifest has <uses-sdk android:minSdkVersion='%1$d'> but library uses minSdkVersion='%2$d'%3$s",
- destMinSdk,
- srcValue.get(),
- !destImplied.get() ? "" : //$NON-NLS-1$
- "\nNote: main manifest lacks a <uses-sdk android:minSdkVersion> declaration, which defaults to value 1."
- );
- result = false;
- }
- }
-
- // Check targetSdkVersion.
-
- // Note that destValue/srcValue purposely defaults to whatever minSdkVersion was last read
- // since that's their definition when missing.
- destImplied.set(true);
- srcImplied.set(true);
-
- boolean result2 = extractSdkVersionAttribute(
- libDoc,
- destUsesSdk, srcUsesSdk,
- "target", //$NON-NLS-1$
- destValue, srcValue,
- destImplied, srcImplied);
-
- result &= result2;
- if (result2) {
- // Make it a warning for an application to use a library with a greater
- // targetSdkVersion.
-
- int destTargetSdk = destImplied.get() ? destMinSdk : destValue.get();
-
- if (destTargetSdk < srcValue.get()) {
- mLog.conflict(Severity.WARNING,
- xmlFileAndLine(destUsesSdk == null ? mMainDoc : destUsesSdk),
- xmlFileAndLine(srcUsesSdk == null ? libDoc : srcUsesSdk),
- "Main manifest has <uses-sdk android:targetSdkVersion='%1$d'> but library uses targetSdkVersion='%2$d'%3$s",
- destTargetSdk,
- srcValue.get(),
- !destImplied.get() ? "" : //$NON-NLS-1$
- "\nNote: main manifest lacks a <uses-sdk android:targetSdkVersion> declaration, which defaults to value minSdkVersion or 1."
- );
- result = false;
- }
- }
-
- return result;
- }
-
- /**
- * Implementation detail for {@link #checkSdkVersion(Document)}.
- * Note that the various atomic out-variables must be preset to their default before
- * the call.
- * <p/>
- * destValue/srcValue will be filled with the integer value of the field, if present
- * and a correct number, in which case destImplied/destImplied are also set to true.
- * Otherwise the values and the implied variables are left untouched.
- */
- private boolean extractSdkVersionAttribute(
- Document libDoc,
- Element destUsesSdk,
- Element srcUsesSdk,
- String attr,
- AtomicInteger destValue,
- AtomicInteger srcValue,
- AtomicBoolean destImplied,
- AtomicBoolean srcImplied) {
- String s = destUsesSdk == null ? "" //$NON-NLS-1$
- : destUsesSdk.getAttributeNS(NS_URI, attr + "SdkVersion"); //$NON-NLS-1$
-
- boolean result = true;
- assert s != null;
- s = s.trim();
- try {
- if (s.length() > 0) {
- destValue.set(Integer.parseInt(s));
- destImplied.set(false);
- }
- } catch (NumberFormatException e) {
- boolean error = true;
- if (mCallback != null) {
- // Versions can contain codenames such as "JellyBean".
- // We'll accept it only if have a callback that can give us the API level for it.
- int apiLevel = mCallback.queryCodenameApiLevel(s);
- if (apiLevel > ICallback.UNKNOWN_CODENAME) {
- destValue.set(apiLevel);
- destImplied.set(false);
- error = false;
- }
- }
- if (error) {
- // Note: NumberFormatException.toString() has no interesting information
- // so we don't output it.
- mLog.error(Severity.ERROR,
- xmlFileAndLine(destUsesSdk == null ? mMainDoc : destUsesSdk),
- "Failed to parse <uses-sdk %1$sSdkVersion='%2$s'>: must be an integer number or codename.",
- attr,
- s);
- result = false;
- }
- }
-
- s = srcUsesSdk == null ? "" //$NON-NLS-1$
- : srcUsesSdk.getAttributeNS(NS_URI, attr + "SdkVersion"); //$NON-NLS-1$
- assert s != null;
- s = s.trim();
- try {
- if (s.length() > 0) {
- srcValue.set(Integer.parseInt(s));
- srcImplied.set(false);
- }
- } catch (NumberFormatException e) {
- boolean error = true;
- if (mCallback != null) {
- // Versions can contain codenames such as "JellyBean".
- // We'll accept it only if have a callback that can give us the API level for it.
- int apiLevel = mCallback.queryCodenameApiLevel(s);
- if (apiLevel > ICallback.UNKNOWN_CODENAME) {
- srcValue.set(apiLevel);
- srcImplied.set(false);
- error = false;
- }
- }
- if (error) {
- mLog.error(Severity.ERROR,
- xmlFileAndLine(srcUsesSdk == null ? libDoc : srcUsesSdk),
- "Failed to parse <uses-sdk %1$sSdkVersion='%2$s'>: must be an integer number or codename.",
- attr,
- s);
- result = false;
- }
- }
-
- return result;
- }
-
-
- // -----
-
-
- /**
- * Given an element E, select which previous siblings we want to merge.
- * We want to include any whitespace up to the closing of the previous element.
- * We also want to include up preceding comment nodes and their preceding whitespace.
- * <p/>
- * This may returns either {@code end} or a previous sibling. Never returns null.
- */
- @NonNull
- private Node selectPreviousSiblings(Node end) {
-
- Node start = end;
- Node prev = start.getPreviousSibling();
- while (prev != null) {
- short t = prev.getNodeType();
- if (t == Node.TEXT_NODE) {
- String text = prev.getNodeValue();
- if (text == null || text.trim().length() != 0) {
- // Not whitespace, we don't want it.
- break;
- }
- } else if (t == Node.COMMENT_NODE) {
- // It's a comment. We'll take it.
- } else {
- // Not a comment node nor a whitespace text. We don't want it.
- break;
- }
- start = prev;
- prev = start.getPreviousSibling();
- }
-
- return start;
- }
-
- /**
- * Inserts all siblings from {@code start} to {@code end} at the end
- * of the given destination element.
- * <p/>
- * Implementation detail: this clones the source nodes into the destination.
- *
- * @param dest The destination at the end of which to insert. Cannot be null.
- * @param start The first element to insert. Must not be null.
- * @param end The last element to insert (included). Must not be null.
- * Must be a direct "next sibling" of the start node.
- * Can be equal to the start node to insert just that one node.
- * @return The copy of the {@code end} node in the destination document or null
- * if no such copy was created and added to the destination.
- */
- private Node insertAtEndOf(Element dest, Node start, Node end) {
- // Check whether we'll need to adjust URI prefixes
- String destPrefix = XmlUtils.lookupNamespacePrefix(mMainDoc, NS_URI);
- String srcPrefix = XmlUtils.lookupNamespacePrefix(start.getOwnerDocument(), NS_URI);
- boolean needPrefixChange = destPrefix != null && !destPrefix.equals(srcPrefix);
-
- // First let's figure out the insertion point.
- // We want the end of the last 'content' element of the
- // destination element and basically we want to insert right
- // before the last whitespace of the destination element.
- Node target = dest.getLastChild();
- while (target != null) {
- if (target.getNodeType() == Node.TEXT_NODE) {
- String text = target.getNodeValue();
- if (text == null || text.trim().length() != 0) {
- // Not whitespace, insert after.
- break;
- }
- } else {
- // Not text. Insert after
- break;
- }
- target = target.getPreviousSibling();
- }
- if (target != null) {
- target = target.getNextSibling();
- }
-
- // Destination and start..end must not be part of the same document
- // because we try to import below. If they were, it would mess the
- // structure.
- assert dest.getOwnerDocument() == mMainDoc;
- assert dest.getOwnerDocument() != start.getOwnerDocument();
- assert start.getOwnerDocument() == end.getOwnerDocument();
-
- while (start != null) {
- Node node = mMainDoc.importNode(start, true /*deep*/);
- if (needPrefixChange) {
- changePrefix(node, srcPrefix, destPrefix);
- }
- dest.insertBefore(node, target);
-
- if (start == end) {
- return node;
- }
- start = start.getNextSibling();
- }
- return null;
- }
-
- /**
- * Changes the namespace prefix of all nodes, recursively.
- *
- * @param node The node to process, as well as all it's descendants. Can be null.
- * @param srcPrefix The prefix to match.
- * @param destPrefix The new prefix to replace with.
- */
- private void changePrefix(Node node, String srcPrefix, String destPrefix) {
- for (; node != null; node = node.getNextSibling()) {
- if (srcPrefix.equals(node.getPrefix())) {
- node.setPrefix(destPrefix);
- }
- Node child = node.getFirstChild();
- if (child != null) {
- changePrefix(child, srcPrefix, destPrefix);
- }
- }
- }
-
- /**
- * Compares two {@link Element}s recursively.
- * They must be identical with the same structure.
- * Order should not matter.
- * Whitespace and comments are ignored.
- *
- * @param expected The first element to compare.
- * @param actual The second element to compare with.
- * @param nextSiblings If true, will also compare the following siblings.
- * If false, it will just compare the given node.
- * @param diff An optional {@link StringBuilder} where to accumulate a diff output.
- * @param keyAttr An optional key attribute to always add to elements when dumping a diff.
- * @return True if {@code e1} and {@code e2} are equal.
- */
- private boolean compareElements(
- @NonNull Node expected,
- @NonNull Node actual,
- boolean nextSiblings,
- @Nullable StringBuilder diff,
- @Nullable String keyAttr) {
- Map<String, String> nsPrefixE = new HashMap<String, String>();
- Map<String, String> nsPrefixA = new HashMap<String, String>();
- String sE = MergerXmlUtils.printElement(expected, nsPrefixE, ""); //$NON-NLS-1$
- String sA = MergerXmlUtils.printElement(actual, nsPrefixA, ""); //$NON-NLS-1$
- if (sE.equals(sA)) {
- return true;
- } else {
- if (diff != null) {
- MergerXmlUtils.printXmlDiff(diff, sE, sA, nsPrefixE, nsPrefixA, NS_URI + ':' + keyAttr);
- }
- return false;
- }
- }
-
- /**
- * Finds the first element matching the given XPath expression in the given document.
- *
- * @param doc The document where to find the expression.
- * @param path The XPath expression. It must yield an {@link Element} node type.
- * @return The {@link Element} found or null.
- */
- @Nullable
- private Element findFirstElement(
- @NonNull Document doc,
- @NonNull String path) {
- Node result;
- try {
- result = (Node) mXPath.evaluate(path, doc, XPathConstants.NODE);
- if (result instanceof Element) {
- return (Element) result;
- }
-
- if (result != null) {
- mLog.error(Severity.ERROR,
- xmlFileAndLine(doc),
- "Unexpected Node type %s when evaluating %s", //$NON-NLS-1$
- result.getClass().getName(), path);
- }
- } catch (XPathExpressionException e) {
- mLog.error(Severity.ERROR,
- xmlFileAndLine(doc),
- "XPath error on expr %s: %s", //$NON-NLS-1$
- path, e.toString());
- }
- return null;
- }
-
- /**
- * Finds zero or more elements matching the given XPath expression in the given document.
- *
- * @param doc The document where to find the expression.
- * @param path The XPath expression. Only {@link Element}s nodes will be returned.
- * @return A list of {@link Element} found, possibly empty but never null.
- */
- private List<Element> findElements(
- @NonNull Document doc,
- @NonNull String path) {
- return findElements(doc, path, null, null);
- }
-
-
- /**
- * Finds zero or more elements matching the given XPath expression in the given document.
- * <p/>
- * Furthermore, the elements must have an attribute matching the given attribute name
- * and value if provided. (If you don't need to match an attribute, use the other version.)
- * <p/>
- * Note that if you provide {@code attrName} as non-null then the {@code attrValue}
- * must be non-null too. In this case the XPath expression will be modified to add
- * the check by naively appending a "[name='value']" filter.
- *
- * @param doc The document where to find the expression.
- * @param path The XPath expression. Only {@link Element}s nodes will be returned.
- * @param attrName The name of the optional attribute to match. Can be null.
- * @param attrValue The value of the optional attribute to match.
- * Can be null if {@code attrName} is null, otherwise must be non-null.
- * @return A list of {@link Element} found, possibly empty but never null.
- *
- * @see #findElements(Document, String)
- */
- private List<Element> findElements(
- @NonNull Document doc,
- @NonNull String path,
- @Nullable String attrName,
- @Nullable String attrValue) {
- List<Element> elements = new ArrayList<Element>();
-
- if (attrName != null) {
- assert attrValue != null;
- // Generate expression /manifest/application/activity[@android:name='my.fqcn']
- path = String.format("%1$s[@%2$s:%3$s='%4$s']", //$NON-NLS-1$
- path, NS_PREFIX, attrName, attrValue);
- }
-
- try {
- NodeList results = (NodeList) mXPath.evaluate(path, doc, XPathConstants.NODESET);
- if (results != null && results.getLength() > 0) {
- for (int i = 0; i < results.getLength(); i++) {
- Node n = results.item(i);
- assert n instanceof Element;
- if (n instanceof Element) {
- elements.add((Element) n);
- } else {
- mLog.error(Severity.ERROR,
- xmlFileAndLine(doc),
- "Unexpected Node type %s when evaluating %s", //$NON-NLS-1$
- n.getClass().getName(), path);
- }
- }
- }
-
- } catch (XPathExpressionException e) {
- mLog.error(Severity.ERROR,
- xmlFileAndLine(doc),
- "XPath error on expr %s: %s", //$NON-NLS-1$
- path, e.toString());
- }
-
- return elements;
- }
-
- /**
- * Returns a new {@link FileAndLine} structure that identifies
- * the base filename & line number from which the XML node was parsed.
- * <p/>
- * When the line number is unknown (e.g. if a {@link Document} instance is given)
- * then line number 0 will be used.
- *
- * @param node The node or document where the error occurs. Must not be null.
- * @return A new non-null {@link FileAndLine} combining the file name and line number.
- */
- @NonNull
- private FileAndLine xmlFileAndLine(@NonNull Node node) {
- return MergerXmlUtils.xmlFileAndLine(node);
- }
-
- /**
- * Checks whether the given element has a tools:merge=override or tools:merge=remove attribute.
- * @param node The node to check.
- * @return True if the element has a tools:merge=override or tools:merge=remove attribute.
- */
- private boolean hasOverrideOrRemoveTag(@Nullable Node node) {
- if (node == null || node.getNodeType() != Node.ELEMENT_NODE) {
- return false;
- }
- NamedNodeMap attrs = node.getAttributes();
- Node merge = attrs.getNamedItemNS(TOOLS_URI, MERGE_ATTR);
- String value = merge == null ? null : merge.getNodeValue();
- return MERGE_OVERRIDE.equals(value) || MERGE_REMOVE.equals(value);
- }
-
- /**
- * Cleans up all tools attributes from the given node hierarchy.
- * <p/>
- * If an element is marked with tools:merge=override, this attribute is removed.
- * If an element is marked with tools:merge=remove, the <em>whole</em> element is removed.
- *
- * @param root The root node to parse and edit, recursively.
- */
- private void cleanupToolsAttributes(@Nullable Node root) {
- if (root == null) {
- return;
- }
- NamedNodeMap attrs = root.getAttributes();
- if (attrs != null) {
- for (int i = attrs.getLength() - 1; i >= 0; i--) {
- Node attr = attrs.item(i);
- if (SdkConstants.XMLNS_URI.equals(attr.getNamespaceURI()) &&
- TOOLS_URI.equals(attr.getNodeValue())) {
- attrs.removeNamedItem(attr.getNodeName());
- } else if (TOOLS_URI.equals(attr.getNamespaceURI()) &&
- MERGE_ATTR.equals(attr.getLocalName())) {
- attrs.removeNamedItem(attr.getNodeName());
- }
- }
- assert attrs.getNamedItemNS(TOOLS_URI, MERGE_ATTR) == null;
- }
-
- for (Node child = root.getFirstChild(); child != null; ) {
- if (child.getNodeType() != Node.ELEMENT_NODE) {
- child = child.getNextSibling();
- continue;
- }
- attrs = child.getAttributes();
- Node merge = attrs == null ? null : attrs.getNamedItemNS(TOOLS_URI, MERGE_ATTR);
- String value = merge == null ? null : merge.getNodeValue();
- Node sibling = child.getNextSibling();
- if (MERGE_REMOVE.equals(value)) {
- // Note: save the previous sibling since removing the child will clear its siblings.
- Node prev = child.getPreviousSibling();
- root.removeChild(child);
- // If there's some whitespace just before that element, clean it up too.
- while (prev != null && prev.getNodeType() == Node.TEXT_NODE) {
- if (prev.getNodeValue().trim().length() == 0) {
- Node prevPrev = prev.getPreviousSibling();
- root.removeChild(prev);
- prev = prevPrev;
- } else {
- break;
- }
- }
- } else {
- cleanupToolsAttributes(child);
- }
- child = sibling;
- }
- }
-
- /**
- * @see #cleanupToolsAttributes(Node)
- */
- private Document cleanupToolsAttributes(@NonNull Document doc) {
- cleanupToolsAttributes(doc.getFirstChild());
- return doc;
- }
-}
diff --git a/manifest-merger/src/main/java/com/android/manifmerger/MergerXmlUtils.java b/manifest-merger/src/main/java/com/android/manifmerger/MergerXmlUtils.java
deleted file mode 100755
index 97291b2..0000000
--- a/manifest-merger/src/main/java/com/android/manifmerger/MergerXmlUtils.java
+++ /dev/null
@@ -1,915 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.manifmerger;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.manifmerger.IMergerLog.FileAndLine;
-import com.android.manifmerger.IMergerLog.Severity;
-import com.android.utils.ILogger;
-import com.android.utils.XmlUtils;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.xml.sax.ErrorHandler;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXParseException;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
-import java.io.Reader;
-import java.io.StringReader;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.transform.OutputKeys;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.dom.DOMSource;
-import javax.xml.transform.stream.StreamResult;
-
-/**
- * A few XML handling utilities.
- */
-class MergerXmlUtils {
-
- private static final String DATA_ORIGIN_FILE = "manif.merger.file"; //$NON-NLS-1$
- private static final String DATA_FILE_NAME = "manif.merger.filename"; //$NON-NLS-1$
- private static final String DATA_LINE_NUMBER = "manif.merger.line#"; //$NON-NLS-1$
-
- /**
- * Parses the given XML file as a DOM document.
- * The parser does not validate the DTD nor any kind of schema.
- * It is namespace aware.
- * <p/>
- * This adds a user tag with the original {@link File} to the returned document.
- * You can retrieve this file later by using {@link #extractXmlFilename(Node)}.
- *
- * @param xmlFile The XML {@link File} to parse. Must not be null.
- * @param log An {@link ILogger} for reporting errors. Must not be null.
- * @return A new DOM {@link Document}, or null.
- */
- @Nullable
- static Document parseDocument(@NonNull final File xmlFile, @NonNull final IMergerLog log) {
- try {
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- Reader reader = new BufferedReader(new FileReader(xmlFile));
- InputSource is = new InputSource(reader);
- factory.setNamespaceAware(true);
- factory.setValidating(false);
- DocumentBuilder builder = factory.newDocumentBuilder();
-
- // We don't want the default handler which prints errors to stderr.
- builder.setErrorHandler(new ErrorHandler() {
- @Override
- public void warning(SAXParseException e) {
- log.error(Severity.WARNING,
- new FileAndLine(xmlFile.getAbsolutePath(), 0),
- "Warning when parsing: %1$s",
- e.toString());
- }
- @Override
- public void fatalError(SAXParseException e) {
- log.error(Severity.ERROR,
- new FileAndLine(xmlFile.getAbsolutePath(), 0),
- "Fatal error when parsing: %1$s",
- xmlFile.getName(), e.toString());
- }
- @Override
- public void error(SAXParseException e) {
- log.error(Severity.ERROR,
- new FileAndLine(xmlFile.getAbsolutePath(), 0),
- "Error when parsing: %1$s",
- e.toString());
- }
- });
-
- Document doc = builder.parse(is);
- doc.setUserData(DATA_ORIGIN_FILE, xmlFile, null /*handler*/);
- findLineNumbers(doc, 1);
-
- return doc;
-
- } catch (FileNotFoundException e) {
- log.error(Severity.ERROR,
- new FileAndLine(xmlFile.getAbsolutePath(), 0),
- "XML file not found");
-
- } catch (Exception e) {
- log.error(Severity.ERROR,
- new FileAndLine(xmlFile.getAbsolutePath(), 0),
- "Failed to parse XML file: %1$s",
- e.toString());
- }
-
- return null;
- }
-
- /**
- * Parses the given XML string as a DOM document.
- * The parser does not validate the DTD nor any kind of schema.
- * It is namespace aware.
- *
- * @param xml The XML string to parse. Must not be null.
- * @param log An {@link ILogger} for reporting errors. Must not be null.
- * @return A new DOM {@link Document}, or null.
- */
- @Nullable
- static Document parseDocument(@NonNull String xml,
- @NonNull IMergerLog log,
- @NonNull FileAndLine errorContext) {
- try {
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- InputSource is = new InputSource(new StringReader(xml));
- factory.setNamespaceAware(true);
- factory.setValidating(false);
- DocumentBuilder builder = factory.newDocumentBuilder();
- Document doc = builder.parse(is);
- findLineNumbers(doc, 1);
- return doc;
- } catch (Exception e) {
- log.error(Severity.ERROR, errorContext, "Failed to parse XML string");
- }
-
- return null;
- }
-
- /**
- * Decorates the document with the specified file name, which can be
- * retrieved later by calling {@link #extractLineNumber(Node)}.
- * <p/>
- * It also tries to add line number information, with the caveat that the
- * current implementation is a gross approximation.
- * <p/>
- * There is no need to call this after calling one of the {@code parseDocument()}
- * methods since they already decorated their own document.
- *
- * @param doc The document to decorate.
- * @param fileName The name to retrieve later for that document.
- */
- static void decorateDocument(@NonNull Document doc, @NonNull String fileName) {
- doc.setUserData(DATA_FILE_NAME, fileName, null /*handler*/);
- findLineNumbers(doc, 1);
- }
-
- /**
- * Returns a new {@link FileAndLine} structure that identifies
- * the base filename & line number from which the XML node was parsed.
- * <p/>
- * When the line number is unknown (e.g. if a {@link Document} instance is given)
- * then line number 0 will be used.
- *
- * @param node The node or document where the error occurs. Must not be null.
- * @return A new non-null {@link FileAndLine} combining the file name and line number.
- */
- @NonNull
- static FileAndLine xmlFileAndLine(@NonNull Node node) {
- String name = extractXmlFilename(node);
- int line = extractLineNumber(node); // 0 in case of error or unknown
- return new FileAndLine(name, line);
- }
-
- /**
- * Extracts the origin {@link File} that {@link #parseDocument(File, IMergerLog)}
- * added to the XML document or the string added by
- *
- * @param xmlNode Any node from a document returned by {@link #parseDocument(File, IMergerLog)}.
- * @return The {@link File} object used to create the document or null.
- */
- @Nullable
- static String extractXmlFilename(@Nullable Node xmlNode) {
- if (xmlNode != null && xmlNode.getNodeType() != Node.DOCUMENT_NODE) {
- xmlNode = xmlNode.getOwnerDocument();
- }
- if (xmlNode != null) {
- Object data = xmlNode.getUserData(DATA_ORIGIN_FILE);
- if (data instanceof File) {
- return ((File) data).getName();
- }
- data = xmlNode.getUserData(DATA_FILE_NAME);
- if (data instanceof String) {
- return (String) data;
- }
- }
-
- return null;
- }
-
- /**
- * This is a CRUDE INEXACT HACK to decorate the DOM with some kind of line number
- * information for elements. It's inexact because by the time we get the DOM we
- * already have lost all the information about whitespace between attributes.
- * <p/>
- * Also we don't even try to deal with \n vs \r vs \r\n insanity. This only counts
- * the \n occurring in text nodes to determine line advances, which is clearly flawed.
- * <p/>
- * However it's good enough for testing, and we'll replace it by a PositionXmlParser
- * once it's moved into com.android.util.
- */
- private static int findLineNumbers(Node node, int line) {
- for (; node != null; node = node.getNextSibling()) {
- node.setUserData(DATA_LINE_NUMBER, Integer.valueOf(line), null /*handler*/);
-
- if (node.getNodeType() == Node.TEXT_NODE) {
- String text = node.getNodeValue();
- if (text.length() > 0) {
- for (int pos = 0; (pos = text.indexOf('\n', pos)) != -1; pos++) {
- ++line;
- }
- }
- }
-
- Node child = node.getFirstChild();
- if (child != null) {
- line = findLineNumbers(child, line);
- }
- }
- return line;
- }
-
- /**
- * Extracts the line number that {@link #findLineNumbers} added to the XML nodes.
- *
- * @param xmlNode Any node from a document returned by {@link #parseDocument(File, IMergerLog)}.
- * @return The line number if found or 0.
- */
- static int extractLineNumber(@Nullable Node xmlNode) {
- if (xmlNode != null) {
- Object data = xmlNode.getUserData(DATA_LINE_NUMBER);
- if (data instanceof Integer) {
- return ((Integer) data).intValue();
- }
- }
-
- return 0;
- }
-
- /**
- * Outputs the given XML {@link Document} to the file {@code outFile}.
- *
- * TODO right now reformats the document. Needs to output as-is, respecting white-space.
- *
- * @param doc The document to output. Must not be null.
- * @param outFile The {@link File} where to write the document.
- * @param log A log in case of error.
- * @return True if the file was written, false in case of error.
- */
- static boolean printXmlFile(
- @NonNull Document doc,
- @NonNull File outFile,
- @NonNull IMergerLog log) {
- // Quick thing based on comments from http://stackoverflow.com/questions/139076
- try {
- Transformer tf = TransformerFactory.newInstance().newTransformer();
- tf.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); //$NON-NLS-1$
- tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); //$NON-NLS-1$
- tf.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$
- tf.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", //$NON-NLS-1$
- "4"); //$NON-NLS-1$
- tf.transform(new DOMSource(doc), new StreamResult(outFile));
- return true;
- } catch (TransformerException e) {
- log.error(Severity.ERROR,
- new FileAndLine(outFile.getName(), 0),
- "Failed to write XML file: %1$s",
- e.toString());
- return false;
- }
- }
-
- /**
- * Outputs the given XML {@link Document} as a string.
- *
- * TODO right now reformats the document. Needs to output as-is, respecting white-space.
- *
- * @param doc The document to output. Must not be null.
- * @param log A log in case of error.
- * @return A string representation of the XML. Null in case of error.
- */
- static String printXmlString(
- @NonNull Document doc,
- @NonNull IMergerLog log) {
- try {
- Transformer tf = TransformerFactory.newInstance().newTransformer();
- tf.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); //$NON-NLS-1$
- tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); //$NON-NLS-1$
- tf.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$
- tf.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", //$NON-NLS-1$
- "4"); //$NON-NLS-1$
- StringWriter sw = new StringWriter();
- tf.transform(new DOMSource(doc), new StreamResult(sw));
- return sw.toString();
- } catch (TransformerException e) {
- log.error(Severity.ERROR,
- new FileAndLine(extractXmlFilename(doc), 0),
- "Failed to write XML file: %1$s",
- e.toString());
- return null;
- }
- }
-
- /**
- * Dumps the structure of the DOM to a simple text string.
- *
- * @param node The first node to dump (recursively). Can be null.
- * @param nextSiblings If true, will also dump the following siblings.
- * If false, it will just process the given node.
- * @return A string representation of the Node structure, useful for debugging.
- */
- @NonNull
- static String dump(@Nullable Node node, boolean nextSiblings) {
- return dump(node, 0 /*offset*/, nextSiblings, true /*deep*/, null /*keyAttr*/);
- }
-
-
- /**
- * Dumps the structure of the DOM to a simple text string.
- * Each line is terminated with a \n separator.
- *
- * @param node The first node to dump. Can be null.
- * @param offsetIndex The offset to add at the begining of each line. Each offset is
- * converted into 2 space characters.
- * @param nextSiblings If true, will also dump the following siblings.
- * If false, it will just process the given node.
- * @param deep If true, this will recurse into children.
- * @param keyAttr An optional attribute *local* name to insert when writing an element.
- * For example when writing an Activity, it helps to always insert "name" attribute.
- * @return A string representation of the Node structure, useful for debugging.
- */
- @NonNull
- static String dump(
- @Nullable Node node,
- int offsetIndex,
- boolean nextSiblings,
- boolean deep,
- @Nullable String keyAttr) {
- StringBuilder sb = new StringBuilder();
-
- String offset = ""; //$NON-NLS-1$
- for (int i = 0; i < offsetIndex; i++) {
- offset += " "; //$NON-NLS-1$
- }
-
- if (node == null) {
- sb.append(offset).append("(end reached)\n");
-
- } else {
- for (; node != null; node = node.getNextSibling()) {
- String type = null;
- short t = node.getNodeType();
- switch(t) {
- case Node.ELEMENT_NODE:
- String attr = "";
- if (keyAttr != null) {
- for (Node a : sortedAttributeList(node.getAttributes())) {
- if (a != null && keyAttr.equals(a.getLocalName())) {
- attr = String.format(" %1$s=%2$s",
- a.getNodeName(), a.getNodeValue());
- break;
- }
- }
- }
- sb.append(String.format("%1$s<%2$s%3$s>\n",
- offset, node.getNodeName(), attr));
- break;
- case Node.COMMENT_NODE:
- sb.append(String.format("%1$s<!-- %2$s -->\n",
- offset, node.getNodeValue()));
- break;
- case Node.TEXT_NODE:
- String txt = node.getNodeValue().trim();
- if (txt.length() == 0) {
- // Keep this for debugging. TODO make it a flag
- // to dump whitespace on debugging. Otherwise ignore it.
- // txt = "[whitespace]";
- break;
- }
- sb.append(String.format("%1$s%2$s\n", offset, txt));
- break;
- case Node.ATTRIBUTE_NODE:
- sb.append(String.format("%1$s @%2$s = %3$s\n",
- offset, node.getNodeName(), node.getNodeValue()));
- break;
- case Node.CDATA_SECTION_NODE:
- type = "cdata"; //$NON-NLS-1$
- break;
- case Node.DOCUMENT_NODE:
- type = "document"; //$NON-NLS-1$
- break;
- case Node.PROCESSING_INSTRUCTION_NODE:
- type = "PI"; //$NON-NLS-1$
- break;
- default:
- type = Integer.toString(t);
- }
-
- if (type != null) {
- sb.append(String.format("%1$s[%2$s] <%3$s> %4$s\n",
- offset, type, node.getNodeName(), node.getNodeValue()));
- }
-
- if (deep) {
- for (Attr attr : sortedAttributeList(node.getAttributes())) {
- sb.append(String.format("%1$s @%2$s = %3$s\n",
- offset, attr.getNodeName(), attr.getNodeValue()));
- }
-
- Node child = node.getFirstChild();
- if (child != null) {
- sb.append(dump(child, offsetIndex+1, true, true, keyAttr));
- }
- }
-
- if (!nextSiblings) {
- break;
- }
- }
- }
- return sb.toString();
- }
-
- /**
- * Returns a sorted list of attributes.
- * The list is never null and does not contain null items.
- *
- * @param attrMap A Node map as returned by {@link Node#getAttributes()}.
- * Can be null, in which case an empty list is returned.
- * @return A non-null, possible empty, list of all nodes that are actual {@link Attr},
- * sorted by increasing attribute name.
- */
- @NonNull
- static List<Attr> sortedAttributeList(@Nullable NamedNodeMap attrMap) {
- List<Attr> list = new ArrayList<Attr>();
-
- if (attrMap != null) {
- for (int i = 0; i < attrMap.getLength(); i++) {
- Node attr = attrMap.item(i);
- if (attr instanceof Attr) {
- list.add((Attr) attr);
- }
- }
- }
-
- if (list.size() > 1) {
- // Sort it by attribute name
- Collections.sort(list, getAttrComparator());
- }
-
- return list;
- }
-
- /**
- * Returns a comparator for {@link Attr}, alphabetically sorted by name.
- * The "name" attribute is special and always sorted to the front.
- */
- @NonNull
- static Comparator<? super Attr> getAttrComparator() {
- return new Comparator<Attr>() {
- @Override
- public int compare(Attr a1, Attr a2) {
- String s1 = a1 == null ? "" : a1.getNodeName(); //$NON-NLS-1$
- String s2 = a2 == null ? "" : a2.getNodeName(); //$NON-NLS-1$
-
- boolean name1 = s1.equals("name"); //$NON-NLS-1$
- boolean name2 = s2.equals("name"); //$NON-NLS-1$
-
- if (name1 && name2) {
- return 0;
- } else if (name1) {
- return -1; // name is always first
- } else if (name2) {
- return 1; // name is always first
- } else {
- return s1.compareTo(s2);
- }
- }
- };
- }
-
- /**
- * Inject attributes into an existing document.
- * <p/>
- * The map keys are "/manifest/elements...|attribute-ns-uri attribute-local-name",
- * for example "/manifest/uses-sdk|http://schemas.android.com/apk/res/android minSdkVersion".
- * (note the space separator between the attribute URI and its local name.)
- * The elements will be created if they don't exists. Existing attributes will be modified.
- * The replacement is done on the main document <em>before</em> merging.
- * The value can be null to remove an existing attribute.
- *
- * @param doc The document to modify in-place.
- * @param attributeMap A map of attributes to inject in the form [pseudo-xpath] => value.
- * @param log A log in case of error.
- */
- static void injectAttributes(
- @Nullable Document doc,
- @Nullable Map<String, String> attributeMap,
- @NonNull IMergerLog log) {
- if (doc == null || attributeMap == null || attributeMap.isEmpty()) {
- return;
- }
-
- // 1=path 2=URI 3=local name
- final Pattern keyRx = Pattern.compile("^/([^\\|]+)\\|([^ ]*) +(.+)$"); //$NON-NLS-1$
- final FileAndLine docInfo = xmlFileAndLine(doc);
-
- nextAttribute: for (Entry<String, String> entry : attributeMap.entrySet()) {
- String key = entry.getKey();
- String value = entry.getValue();
- if (key == null || key.isEmpty()) {
- continue;
- }
-
- Matcher m = keyRx.matcher(key);
- if (!m.matches()) {
- log.error(Severity.WARNING, docInfo, "Invalid injected attribute key: %s", key);
- continue;
- }
- String path = m.group(1);
- String attrNsUri = m.group(2);
- String attrName = m.group(3);
-
- String[] segment = path.split(Pattern.quote("/")); //$NON-NLS-1$
-
- // Get the path elements. Create them as needed if they don't exist.
- Node element = doc;
- nextSegment: for (int i = 0; i < segment.length; i++) {
- // Find a child with the segment's name
- String name = segment[i];
- for (Node child = element.getFirstChild();
- child != null;
- child = child.getNextSibling()) {
- if (child.getNodeType() == Node.ELEMENT_NODE &&
- child.getNamespaceURI() == null &&
- child.getNodeName().equals(name)) {
- // Found it. Continue to the next inner segment.
- element = child;
- continue nextSegment;
- }
- }
- // No such element. Create it.
- if (value == null) {
- // If value is null, we want to remove, not create and if can't find the
- // element, then we're done: there's no such attribute to remove.
- break nextAttribute;
- }
-
- Element child = doc.createElement(name);
- element = element.insertBefore(child, element.getFirstChild());
- }
-
- if (element == null) {
- log.error(Severity.WARNING, docInfo, "Invalid injected attribute path: %s", path);
- return;
- }
-
- NamedNodeMap attrs = element.getAttributes();
- if (attrs != null) {
-
-
- if (attrNsUri != null && attrNsUri.isEmpty()) {
- attrNsUri = null;
- }
- Node attr = attrs.getNamedItemNS(attrNsUri, attrName);
-
- if (value == null) {
- // We want to remove the attribute from the attribute map.
- if (attr != null) {
- attrs.removeNamedItemNS(attrNsUri, attrName);
- }
-
- } else {
- // We want to add or replace the attribute.
- if (attr == null) {
- attr = doc.createAttributeNS(attrNsUri, attrName);
- if (attrNsUri != null) {
- attr.setPrefix(XmlUtils.lookupNamespacePrefix(element, attrNsUri));
- }
- attrs.setNamedItemNS(attr);
- }
- attr.setNodeValue(value);
- }
- }
- }
- }
-
- // -------
-
- /**
- * Flatten the element to a string. This "pretty prints" the XML tree starting
- * from the given node and all its children and attributes.
- * <p/>
- * The output is designed to be printed using {@link #printXmlDiff}.
- *
- * @param node The root node to print.
- * @param nsPrefix A map that is filled with all the URI=>prefix found.
- * The internal string only contains the expanded URIs but this is rather verbose
- * so when printing the diff these will be replaced by the prefixes collected here.
- * @param prefix A "space" prefix added at the beginning of each line for indentation
- * purposes. The diff printer later relies on this to find out the structure.
- */
- @NonNull
- static String printElement(
- @NonNull Node node,
- @NonNull Map<String, String> nsPrefix,
- @NonNull String prefix) {
- StringBuilder sb = new StringBuilder();
- sb.append(prefix).append('<');
- String uri = node.getNamespaceURI();
- if (uri != null) {
- sb.append(uri).append(':');
- nsPrefix.put(uri, node.getPrefix());
- }
- sb.append(node.getLocalName());
- printAttributes(sb, node, nsPrefix, prefix);
- sb.append(">\n"); //$NON-NLS-1$
- printChildren(sb, node.getFirstChild(), true, nsPrefix, prefix + " "); //$NON-NLS-1$
-
- sb.append(prefix).append("</"); //$NON-NLS-1$
- if (uri != null) {
- sb.append(uri).append(':');
- }
- sb.append(node.getLocalName());
- sb.append(">\n"); //$NON-NLS-1$
-
- return sb.toString();
- }
-
- /**
- * Flatten several children elements to a string.
- * This is an implementation detail for {@link #printElement(Node, Map, String)}.
- * <p/>
- * If {@code nextSiblings} is false, the string conversion takes only the given
- * child element and stops there.
- * <p/>
- * If {@code nextSiblings} is true, the string conversion also takes _all_ the siblings
- * after the given element. The idea is the caller can call this with the first child
- * of a parent and get a string showing all the children at the same time. They are
- * sorted to avoid the ordering issue.
- */
- @NonNull
- private static StringBuilder printChildren(
- @NonNull StringBuilder sb,
- @NonNull Node child,
- boolean nextSiblings,
- @NonNull Map<String, String> nsPrefix,
- @NonNull String prefix) {
- ArrayList<String> children = new ArrayList<String>();
-
- boolean hasText = false;
- for (; child != null; child = child.getNextSibling()) {
- short t = child.getNodeType();
- if (nextSiblings && t == Node.TEXT_NODE) {
- // We don't typically have meaningful text nodes in an Android manifest.
- // If there are, just dump them as-is into the element representation.
- // We do trim whitespace and ignore all-whitespace or empty text nodes.
- String s = child.getNodeValue().trim();
- if (s.length() > 0) {
- sb.append(s);
- hasText = true;
- }
- } else if (t == Node.ELEMENT_NODE) {
- children.add(printElement(child, nsPrefix, prefix));
- if (!nextSiblings) {
- break;
- }
- }
- }
-
- if (hasText) {
- sb.append('\n');
- }
-
- if (!children.isEmpty()) {
- Collections.sort(children);
- for (String s : children) {
- sb.append(s);
- }
- }
-
- return sb;
- }
-
- /**
- * Flatten several attributes to a string using their alphabetical order.
- * This is an implementation detail for {@link #printElement(Node, Map, String)}.
- */
- @NonNull
- private static StringBuilder printAttributes(
- @NonNull StringBuilder sb,
- @NonNull Node node,
- @NonNull Map<String, String> nsPrefix,
- @NonNull String prefix) {
- ArrayList<String> attrs = new ArrayList<String>();
-
- NamedNodeMap attrMap = node.getAttributes();
- if (attrMap != null) {
- StringBuilder sb2 = new StringBuilder();
- for (int i = 0; i < attrMap.getLength(); i++) {
- Node attr = attrMap.item(i);
- if (attr instanceof Attr) {
- sb2.setLength(0);
- sb2.append('@');
- String uri = attr.getNamespaceURI();
- if (uri != null) {
- sb2.append(uri).append(':');
- nsPrefix.put(uri, attr.getPrefix());
- }
- sb2.append(attr.getLocalName());
- sb2.append("=\"").append(attr.getNodeValue()).append('\"'); //$NON-NLS-1$
- attrs.add(sb2.toString());
- }
- }
- }
-
- Collections.sort(attrs);
-
- for(String attr : attrs) {
- sb.append('\n');
- sb.append(prefix).append(" ").append(attr); //$NON-NLS-1$
- }
- return sb;
- }
-
- //------------
-
- /**
- * Computes a quick diff between two strings generated by
- * {@link #printElement(Node, Map, String)}.
- * <p/>
- * This is a <em>not</em> designed to be a full contextual diff.
- * It just stops at the first difference found, printing up to 3 lines of diff
- * and backtracking to add prior contextual information to understand the
- * structure of the element where the first diff line occurred (by printing
- * each parent found till the root one as well as printing the attribute
- * named by {@code keyAttr}).
- *
- * @param sb The string builder where to output is written.
- * @param expected The expected XML tree (as generated by {@link #printElement}.)
- * For best result this would be the "destination" XML we're merging into,
- * e.g. the main manifest.
- * @param actual The actual XML tree (as generated by {@link #printElement}.)
- * For best result this would be the "source" XML we're merging from,
- * e.g. a library manifest.
- * @param nsPrefixE The map of URI=>prefix for the expected XML tree.
- * @param nsPrefixA The map of URI=>prefix for the actual XML tree.
- * @param keyAttr An optional attribute *full* name (uri:local name) to always
- * insert when writing the contextual lines before a diff line.
- * For example when writing an Activity, it helps to always insert
- * the "name" attribute since that's the key element to help the user
- * identify which node is being dumped.
- */
- static void printXmlDiff(
- StringBuilder sb,
- String expected,
- String actual,
- Map<String, String> nsPrefixE,
- Map<String, String> nsPrefixA,
- String keyAttr) {
- String[] aE = expected.split("\n");
- String[] aA = actual.split("\n");
- int lE = aE.length;
- int lA = aA.length;
- int lm = lE < lA ? lA : lE;
- boolean eofE = false;
- boolean eofA = false;
- boolean contextE = true;
- boolean contextA = true;
- int numDiff = 0;
-
- StringBuilder sE = new StringBuilder();
- StringBuilder sA = new StringBuilder();
-
- outerLoop: for (int i = 0, iE = 0, iA = 0; i < lm; i++) {
- if (iE < lE && iA < lA && aE[iE].equals(aA[iA])) {
- if (numDiff > 0) {
- // If we found a difference, stop now.
- break outerLoop;
- }
- iE++;
- iA++;
- continue;
- } else {
- // Try to print some context for each side based on previous lines's space prefix.
- if (contextE) {
- if (iE > 0) {
- String p = diffGetPrefix(aE[iE]);
- for (int kE = iE-1; kE >= 0; kE--) {
- if (!aE[kE].startsWith(p)) {
- sE.insert(0, '\n').insert(0, diffReplaceNs(aE[kE], nsPrefixE)).insert(0, " ");
- if (p.length() == 0) {
- break;
- }
- p = diffGetPrefix(aE[kE]);
- } else if (aE[kE].contains(keyAttr) || kE == 0) {
- sE.insert(0, '\n').insert(0, diffReplaceNs(aE[kE], nsPrefixE)).insert(0, " ");
- }
- }
- }
- contextE = false;
- }
- if (iE >= lE) {
- if (!eofE) {
- sE.append("--(end reached)\n");
- eofE = true;
- }
- } else {
- sE.append("--").append(diffReplaceNs(aE[iE++], nsPrefixE)).append('\n');
- }
-
- if (contextA) {
- if (iA > 0) {
- String p = diffGetPrefix(aA[iA]);
- for (int kA = iA-1; kA >= 0; kA--) {
- if (!aA[kA].startsWith(p)) {
- sA.insert(0, '\n').insert(0, diffReplaceNs(aA[kA], nsPrefixA)).insert(0, " ");
- p = diffGetPrefix(aA[kA]);
- if (p.length() == 0) {
- break;
- }
- } else if (aA[kA].contains(keyAttr) || kA == 0) {
- sA.insert(0, '\n').insert(0, diffReplaceNs(aA[kA], nsPrefixA)).insert(0, " ");
- }
- }
- }
- contextA = false;
- }
- if (iA >= lA) {
- if (!eofA) {
- sA.append("++(end reached)\n");
- eofA = true;
- }
- } else {
- sA.append("++").append(diffReplaceNs(aA[iA++], nsPrefixA)).append('\n');
- }
-
- // Dump up to 3 lines of difference
- numDiff++;
- if (numDiff == 3) {
- break outerLoop;
- }
- }
- }
-
- sb.append(sE);
- sb.append(sA);
- }
-
- /**
- * Returns all the whitespace at the beginning of a string.
- * Implementation details for {@link #printXmlDiff} used to find the "parent"
- * element and include it in the context of the diff.
- */
- private static String diffGetPrefix(String str) {
- int pos = 0;
- int len = str.length();
- while (pos < len && str.charAt(pos) == ' ') {
- pos++;
- }
- return str.substring(0, pos);
- }
-
- /**
- * Simplifies a diff line by replacing NS URIs by their prefix.
- * Implementation details for {@link #printXmlDiff}.
- */
- private static String diffReplaceNs(String str, Map<String, String> nsPrefix) {
- for (Entry<String, String> entry : nsPrefix.entrySet()) {
- String uri = entry.getKey();
- String prefix = entry.getValue();
- if (prefix != null && str.contains(uri)) {
- str = str.replaceAll(Pattern.quote(uri), Matcher.quoteReplacement(prefix));
- }
- }
- return str;
- }
-
-}
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/ManifestMergerTest.java b/manifest-merger/src/test/java/com/android/manifmerger/ManifestMergerTest.java
deleted file mode 100755
index 851fa1b..0000000
--- a/manifest-merger/src/test/java/com/android/manifmerger/ManifestMergerTest.java
+++ /dev/null
@@ -1,625 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.manifmerger;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.manifmerger.IMergerLog.FileAndLine;
-import com.android.sdklib.mock.MockLog;
-
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestSuite;
-
-import org.w3c.dom.Document;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.UnsupportedEncodingException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
-/**
- * Some utilities to reduce repetitions in the {@link ManifestMergerTest}s.
- * <p/>
- * See {@link #loadTestData(String)} for an explanation of the data file format.
- */
-public class ManifestMergerTest extends TestCase {
-
- /**
- * Delimiter that indicates the test must fail.
- * An XML output and errors are still generated and checked.
- */
- private static final String DELIM_FAILS = "fails";
- /**
- * Delimiter that starts a library XML content.
- * The delimiter name must be in the form {@code @libSomeName} and it will be
- * used as the base for the test file name. Using separate lib names is encouraged
- * since it makes the error output easier to read.
- */
- private static final String DELIM_LIB = "lib";
- /**
- * Delimiter that starts the main manifest XML content.
- */
- private static final String DELIM_MAIN = "main";
- /**
- * Delimiter that starts the resulting XML content, whatever is generated by the merge.
- */
- private static final String DELIM_RESULT = "result";
- /**
- * Delimiter that starts the SdkLog output.
- * The logger prints each entry on its lines, prefixed with E for errors,
- * W for warnings and P for regular printfs.
- */
- private static final String DELIM_ERRORS = "errors";
- /**
- * Delimiter for starts a section that declares how to inject an attribute.
- * The section is composed of one or more lines with the
- * syntax: "/node/node|attr-URI attrName=attrValue".
- * This is essentially a pseudo XPath-like expression that is described in
- * {@link ManifestMerger#process(Document, File[], Map, String)}.
- */
- private static final String DELIM_INJECT_ATTR = "inject";
- /**
- * Delimiter for a section that declares how to toggle a ManifMerger option.
- * The section is composed of one or more lines with the
- * syntax: "functionName=false|true".
- */
- private static final String DELIM_FEATURES = "features";
-
- /**
- * Delimiter for a section that declares how to override the package.
- * The section is composed of one line containing the new package name.
- */
- private static final String DELIM_PACKAGE = "package";
-
-
- /*
- * Wait, I hear you, where are the tests?
- *
- * processTestFiles() uses loadTestData(), which uses one of the data filename
- * indicated below.
- *
- * We could simplify this even further by dynamically finding the data
- * files to use; however there's some value in having tests break when out
- * of sync with the known data file set.
- */
- private static String[] sDataFiles = new String[] {
- "00_noop",
- "01_ignore_app_attr",
- "02_ignore_instrumentation",
- "03_inject_attributes",
- "04_inject_attributes",
- "05_inject_package",
- "10_activity_merge",
- "11_activity_dup",
- "12_alias_dup",
- "13_service_dup",
- "14_receiver_dup",
- "15_provider_dup",
- "16_fqcn_merge",
- "17_fqcn_conflict",
- "20_uses_lib_merge",
- "21_uses_lib_errors",
- "25_permission_merge",
- "26_permission_dup",
- "28_uses_perm_merge",
- "30_uses_sdk_ok",
- "32_uses_sdk_minsdk_ok",
- "33_uses_sdk_minsdk_conflict",
- "36_uses_sdk_targetsdk_warning",
- "40_uses_feat_merge",
- "41_uses_feat_errors",
- "45_uses_feat_gles_once",
- "47_uses_feat_gles_conflict",
- "50_uses_conf_warning",
- "52_support_screens_warning",
- "54_compat_screens_warning",
- "56_support_gltext_warning",
- "60_merge_order",
- "65_override_app",
- "66_remove_app",
- "67_override_activities",
- "68_override_uses",
- "69_remove_uses",
- "70_expand_fqcns",
- "71_extract_package_prefix",
- "75_app_metadata_merge",
- "76_app_metadata_ignore",
- "77_app_metadata_conflict",
- };
-
- /**
- * This overrides the default test suite created by junit.
- * The test suite is a bland TestSuite with a dedicated name.
- * We inject as many instances of {@link ManifestMergerTest} in the suite
- * as we have declared data files above.
- *
- * @return A new {@link TestSuite}.
- */
- public static Test suite() {
- TestSuite suite = new TestSuite();
- // Give a non-generic name to our test suite, for better unit reports.
- suite.setName("ManifestMergerTestSuite");
-
- for (String fileName : sDataFiles) {
- suite.addTest(TestSuite.createTest(ManifestMergerTest.class, fileName));
- }
-
- return suite;
- }
-
-
- /**
- * Default constructor invoked by {@link TestSuite#createTest(Class, String)}.
- *
- * @param testName The test name provided to {@code TestSuite.createTest()}.
- * This is later accessible via {@link #getName()}.
- */
- public ManifestMergerTest(String testName) {
- super(testName);
- }
-
- /**
- * Invoked by the test framework to run the specific test which name
- * has been passed to the constructor.
- * Note that we create one instance of this class per test to run in
- * the associated {@link TestSuite}.
- */
- @Override
- protected void runTest() throws Throwable {
- String testName = getName();
- assertNotNull(testName);
- processTestFiles(loadTestData(testName));
- }
-
-
- static class TestFiles {
- private final File mMain;
- private final File[] mLibs;
- private final Map<String, String> mInjectAttributes;
- private final String mPackageOverride;
- private final File mActualResult;
- private final String mExpectedResult;
- private final String mExpectedErrors;
- private final boolean mShouldFail;
- private final Map<String, Boolean> mFeatures;
-
- /** Files used by a given test case. */
- public TestFiles(
- boolean shouldFail,
- @NonNull File main,
- @NonNull File[] libs,
- @NonNull Map<String, Boolean> features,
- @NonNull Map<String, String> injectAttributes,
- @Nullable String packageOverride,
- @NonNull File actualResult,
- @NonNull String expectedResult,
- @NonNull String expectedErrors) {
- mShouldFail = shouldFail;
- mMain = main;
- mLibs = libs;
- mFeatures = features;
- mPackageOverride = packageOverride;
- mInjectAttributes = injectAttributes;
- mActualResult = actualResult;
- mExpectedResult = expectedResult;
- mExpectedErrors = expectedErrors;
- }
-
- public boolean getShouldFail() {
- return mShouldFail;
- }
-
- @NonNull
- public File getMain() {
- return mMain;
- }
-
- @NonNull
- public File[] getLibs() {
- return mLibs;
- }
-
- @NonNull
- public Map<String, Boolean> getFeatures() {
- return mFeatures;
- }
-
- @NonNull
- public Map<String, String> getInjectAttributes() {
- return mInjectAttributes;
- }
-
- @Nullable
- public String getPackageOverride() {
- return mPackageOverride;
- }
-
- @NonNull
- public File getActualResult() {
- return mActualResult;
- }
-
- @NonNull
- public String getExpectedResult() {
- return mExpectedResult;
- }
-
- public String getExpectedErrors() {
- return mExpectedErrors;
- }
-
- // Try to delete any temp file potentially created.
- public void cleanup() {
- if (mMain != null && mMain.isFile()) {
- mMain.delete();
- }
-
- if (mActualResult != null && mActualResult.isFile()) {
- mActualResult.delete();
- }
-
- for (File f : mLibs) {
- if (f != null && f.isFile()) {
- f.delete();
- }
- }
- }
- }
-
- /**
- * Calls {@link #loadTestData(String)} by
- * inferring the data filename from the caller's method name.
- * <p/>
- * The caller method name must be composed of "test" + the leaf filename.
- * Extensions ".xml" or ".txt" are implied.
- * <p/>
- * E.g. to use the data file "12_foo.xml", simply call this from a method
- * named "test12_foo".
- *
- * @return A new {@link TestFiles} instance. Never null.
- * @throws Exception when things go wrong.
- * @see #loadTestData(String)
- */
- @NonNull
- TestFiles loadTestData() throws Exception {
- StackTraceElement[] stack = Thread.currentThread().getStackTrace();
- for (int i = 0, n = stack.length; i < n; i++) {
- StackTraceElement caller = stack[i];
- String name = caller.getMethodName();
- if (name.startsWith("test")) {
- return loadTestData(name.substring(4));
- }
- }
-
- throw new IllegalArgumentException("No caller method found which name started with 'test'");
- }
-
- /**
- * Loads test data for a given test case.
- * The input (main + libs) are stored in temp files.
- * A new destination temp file is created to store the actual result output.
- * The expected result is actually kept in a string.
- * <p/>
- * Data File Syntax:
- * <ul>
- * <li> Lines starting with # are ignored (anywhere, as long as # is the first char).
- * <li> Lines before the first {@code @delimiter} are ignored.
- * <li> Empty lines just after the {@code @delimiter}
- * and before the first < XML line are ignored.
- * <li> Valid delimiters are {@code @main} for the XML of the main app manifest.
- * <li> Following delimiters are {@code @libXYZ}, read in the order of definition.
- * The name can be anything as long as it starts with "{@code @lib}".
- * </ul>
- *
- * @param filename The test data filename. If no extension is provided, this will
- * try with .xml or .txt. Must not be null.
- * @return A new {@link TestFiles} instance. Must not be null.
- * @throws Exception when things fail to load properly.
- */
- @NonNull
- TestFiles loadTestData(@NonNull String filename) throws Exception {
-
- String resName = "data" + File.separator + filename;
- InputStream is = null;
- BufferedReader reader = null;
- BufferedWriter writer = null;
-
- try {
- is = this.getClass().getResourceAsStream(resName);
- if (is == null && !filename.endsWith(".xml")) {
- String resName2 = resName + ".xml";
- is = this.getClass().getResourceAsStream(resName2);
- if (is != null) {
- filename = resName2;
- }
- }
- if (is == null && !filename.endsWith(".txt")) {
- String resName3 = resName + ".txt";
- is = this.getClass().getResourceAsStream(resName3);
- if (is != null) {
- filename = resName3;
- }
- }
- assertNotNull("Test data file not found for " + filename, is);
-
- reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
-
- // Get the temporary directory to use. Just create a temp file, extracts its
- // directory and remove the file.
- File tempFile = File.createTempFile(this.getClass().getSimpleName(), ".tmp");
- File tempDir = tempFile.getParentFile();
- if (!tempFile.delete()) {
- tempFile.deleteOnExit();
- }
-
- String line = null;
- String delimiter = null;
- boolean skipEmpty = true;
-
- boolean shouldFail = false;
- Map<String, Boolean> features = new HashMap<String, Boolean>();
- String packageOverride = null;
- Map<String, String> injectAttributes = new HashMap<String, String>();
- StringBuilder expectedResult = new StringBuilder();
- StringBuilder expectedErrors = new StringBuilder();
- File mainFile = null;
- File actualResultFile = null;
- List<File> libFiles = new ArrayList<File>();
- int tempIndex = 0;
-
- while ((line = reader.readLine()) != null) {
- if (skipEmpty && line.trim().isEmpty()) {
- continue;
- }
- if (!line.isEmpty() && line.charAt(0) == '#') {
- continue;
- }
- if (!line.isEmpty() && line.charAt(0) == '@') {
- delimiter = line.substring(1);
- assertTrue(
- "Unknown delimiter @" + delimiter + " in " + filename,
- delimiter.startsWith(DELIM_LIB) ||
- delimiter.equals(DELIM_MAIN) ||
- delimiter.equals(DELIM_RESULT) ||
- delimiter.equals(DELIM_ERRORS) ||
- delimiter.equals(DELIM_FAILS) ||
- delimiter.equals(DELIM_FEATURES) ||
- delimiter.equals(DELIM_INJECT_ATTR) ||
- delimiter.equals(DELIM_PACKAGE));
-
- skipEmpty = true;
-
- if (writer != null) {
- try {
- writer.close();
- } catch (IOException ignore) {}
- writer = null;
- }
-
- if (delimiter.equals(DELIM_FAILS)) {
- shouldFail = true;
-
- } else if (!delimiter.equals(DELIM_ERRORS) &&
- !delimiter.equals(DELIM_FEATURES) &&
- !delimiter.equals(DELIM_INJECT_ATTR) &&
- !delimiter.equals(DELIM_PACKAGE)) {
- tempFile = new File(tempDir, String.format("%1$s%2$d_%3$s.xml",
- this.getClass().getSimpleName(),
- tempIndex++,
- delimiter.replaceAll("[^a-zA-Z0-9_-]", "")
- ));
- tempFile.deleteOnExit();
-
- if (delimiter.startsWith(DELIM_LIB)) {
- libFiles.add(tempFile);
-
- } else if (delimiter.equals(DELIM_MAIN)) {
- mainFile = tempFile;
-
- } else if (delimiter.equals(DELIM_RESULT)) {
- actualResultFile = tempFile;
-
- } else {
- fail("Unexpected data file delimiter @" + delimiter +
- " in " + filename);
- }
-
- if (!delimiter.equals(DELIM_RESULT)) {
- writer = new BufferedWriter(new FileWriter(tempFile));
- }
- }
-
- continue;
- }
- if (delimiter != null &&
- skipEmpty &&
- !line.isEmpty() &&
- line.charAt(0) != '#' &&
- line.charAt(0) != '@') {
- skipEmpty = false;
- }
- if (writer != null) {
- writer.write(line);
- writer.write('\n');
- } else if (DELIM_RESULT.equals(delimiter)) {
- expectedResult.append(line).append('\n');
- } else if (DELIM_ERRORS.equals(delimiter)) {
- expectedErrors.append(line).append('\n');
- } else if (DELIM_INJECT_ATTR.equals(delimiter)) {
- String[] in = line.split("=");
- if (in != null && in.length == 2) {
- injectAttributes.put(in[0], "null".equals(in[1]) ? null : in[1]);
- }
- } else if (DELIM_FEATURES.equals(delimiter)) {
- String[] in = line.split("=");
- if (in != null && in.length == 2) {
- features.put(in[0], Boolean.parseBoolean(in[1]));
- }
- } else if (DELIM_PACKAGE.equals(delimiter)) {
- if (packageOverride == null) {
- packageOverride = line;
- }
- }
- }
-
- assertNotNull("Missing @" + DELIM_MAIN + " in " + filename, mainFile);
- assertNotNull("Missing @" + DELIM_RESULT + " in " + filename, actualResultFile);
-
- assert mainFile != null;
- assert actualResultFile != null;
-
- Collections.sort(libFiles);
-
- return new TestFiles(
- shouldFail,
- mainFile,
- libFiles.toArray(new File[libFiles.size()]),
- features,
- injectAttributes,
- packageOverride,
- actualResultFile,
- expectedResult.toString(),
- expectedErrors.toString());
-
- } catch (UnsupportedEncodingException e) {
- // BufferedReader failed to decode UTF-8, O'RLY?
- throw e;
-
- } finally {
- if (writer != null) {
- try {
- writer.close();
- } catch (IOException ignore) {}
- }
- if (reader != null) {
- try {
- reader.close();
- } catch (IOException ignore) {}
- }
- if (is != null) {
- try {
- is.close();
- } catch (IOException ignore) {}
- }
- }
- }
-
-// /**
-// * Loads the data test files using {@link #loadTestData()} and then
-// * invokes {@link #processTestFiles(TestFiles)} to test them.
-// *
-// * @see #loadTestData()
-// * @see #processTestFiles(TestFiles)
-// */
-// void processTestFiles() throws Exception {
-// processTestFiles(loadTestData());
-// }
-
- /**
- * Processes the data from the given {@link TestFiles} by
- * invoking {@link ManifestMerger#process(File, File, File[], Map, String)}:
- * the given library files are applied consecutively to the main XML
- * document and the output is generated.
- * <p/>
- * Then the expected and actual outputs are loaded into a DOM,
- * dumped again to a String using an XML transform and compared.
- * This makes sure only the structure is checked and that any
- * formatting is ignored in the comparison.
- *
- * @param testFiles The test files to process. Must not be null.
- * @throws Exception when this go wrong.
- */
- void processTestFiles(TestFiles testFiles) throws Exception {
- MockLog log = new MockLog();
- IMergerLog mergerLog = MergerLog.wrapSdkLog(log);
- ManifestMerger merger = new ManifestMerger(mergerLog, new ICallback() {
- @Override
- public int queryCodenameApiLevel(@NonNull String codename) {
- if ("ApiCodename1".equals(codename)) {
- return 1;
- } else if ("ApiCodename10".equals(codename)) {
- return 10;
- }
- return ICallback.UNKNOWN_CODENAME;
- }
- });
-
- for (Entry<String, Boolean> feature : testFiles.getFeatures().entrySet()) {
- Method m = merger.getClass().getMethod(
- feature.getKey(),
- new Class<?>[] { boolean.class } );
- m.invoke(merger, new Object[] { feature.getValue() } );
- }
-
- boolean processOK = merger.process(testFiles.getActualResult(),
- testFiles.getMain(),
- testFiles.getLibs(),
- testFiles.getInjectAttributes(),
- testFiles.getPackageOverride());
-
- String expectedErrors = testFiles.getExpectedErrors().trim();
- StringBuilder actualErrors = new StringBuilder();
- for (String s : log.getMessages()) {
- actualErrors.append(s);
- if (!s.endsWith("\n")) {
- actualErrors.append('\n');
- }
- }
- assertEquals("Error generated during merging",
- expectedErrors, actualErrors.toString().trim());
-
- if (testFiles.getShouldFail()) {
- assertFalse("Merge process() returned true, expected false", processOK);
- } else {
- assertTrue("Merge process() returned false, expected true", processOK);
- }
-
- // Test result XML. There should always be one created
- // since the process action does not stop on errors.
- log.clear();
- Document document = MergerXmlUtils.parseDocument(testFiles.getActualResult(), mergerLog);
- assertNotNull(document);
- assert document != null; // for Eclipse null analysis
- String actual = MergerXmlUtils.printXmlString(document, mergerLog);
- assertEquals("Error parsing actual result XML", "[]", log.toString());
- log.clear();
- document = MergerXmlUtils.parseDocument(
- testFiles.getExpectedResult(),
- mergerLog,
- new FileAndLine("<expected-result>", 0));
- assertNotNull("Failed to parse result document: " + testFiles.getExpectedResult(),document);
- assert document != null;
- String expected = MergerXmlUtils.printXmlString(document, mergerLog);
- assertEquals("Error parsing expected result XML", "[]", log.toString());
- assertEquals("Error comparing expected to actual result", expected, actual);
-
- testFiles.cleanup();
- }
-
-}
diff --git a/manifest-merger/src/test/java/com/android/manifmerger/data/16_fqcn_merge.xml b/manifest-merger/src/test/java/com/android/manifmerger/data/16_fqcn_merge.xml
deleted file mode 100755
index 8414a3c..0000000
--- a/manifest-merger/src/test/java/com/android/manifmerger/data/16_fqcn_merge.xml
+++ /dev/null
@@ -1,129 +0,0 @@
-#
-# Test how FQCN class names are expanded and handled:
-# - A library application can be merged doesn't have an app class name.
-# - A library application can be merged if it has the same class name as the app.
-# - A partial class name is expanded using the package name in a library or app.
-#
-
-@main
-
-<manifest
- xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.app1"
- android:versionCode="100"
- android:versionName="1.0.0">
-
- <application
- android:name="TheApp"
- android:backupAgent=".MyBackupAgent" >
- <activity android:name=".MainActivity" />
- <receiver android:name="AppReceiver" />
- <activity android:name="com.example.lib2.LibActivity" />
- </application>
-</manifest>
-
-
-@lib1_widget
-
-<manifest
- xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.lib1">
-
- <application android:name="com.example.app1.TheApp" >
- <activity android:name=".WidgetLibrary" />
- <receiver android:name=".WidgetReceiver" />
- <service android:name="AppService" />
- <activity android:name="com.example.lib1.WidgetConfigurationUI" />
- </application>
-</manifest>
-
-
-@lib2_activity
-
-<manifest
- xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.lib2">
-
- <application>
- <!-- This won't be merged because there's already an identical definition in the main. -->
- <activity android:name="LibActivity" />
-
- <!-- Provider extracted from ApiDemos -->
- <provider android:name=".app.LoaderThrottle$SimpleProvider" />
-
- <!-- This one does not conflict with the main -->
- <activity android:name="com.example.lib2.LibActivity2" />
-
- </application>
-</manifest>
-
-
-@lib3_alias
-
-<manifest
- xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.lib3" >
- <!-- This manifest has a 'package' attribute and FQCNs get resolved. -->
-
- <application
- android:name="com.example.app1.TheApp"
- android:backupAgent="com.example.app1.MyBackupAgent">
- <activity-alias android:name="com.example.lib3.MyActivity"
- android:targetActivity="com.example.app1.MainActivity" />
-
- <!-- This is a dup of the 2nd activity in lib2 -->
- <activity android:name="com.example.lib2.LibActivity2" />
-
- <!-- These class name should be expanded. -->
- <activity android:name=".LibActivity3" />
- <service android:name=".LibService3" />
- <receiver android:name=".LibReceiver3" />
- <provider android:name=".LibProvider3" />
-
- </application>
-
-</manifest>
-
-
-@result
-
-<manifest
- xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.app1"
- android:versionCode="100"
- android:versionName="1.0.0">
-
- <application
- android:name="com.example.app1.TheApp"
- android:backupAgent="com.example.app1.MyBackupAgent" >
- <activity android:name="com.example.app1.MainActivity" />
- <receiver android:name="com.example.app1.AppReceiver" />
- <activity android:name="com.example.lib2.LibActivity" />
-# from @lib1_widget
- <activity android:name="com.example.lib1.WidgetLibrary" />
- <activity android:name="com.example.lib1.WidgetConfigurationUI" />
- <service android:name="com.example.lib1.AppService" />
- <receiver android:name="com.example.lib1.WidgetReceiver" />
-
-# from @lib2_activity
- <!-- This one does not conflict with the main -->
- <activity android:name="com.example.lib2.LibActivity2" />
-
- <!-- Provider extracted from ApiDemos -->
- <provider android:name="com.example.lib2.app.LoaderThrottle$SimpleProvider" />
-
-# from @lib3_alias
- <!-- These class name should be expanded. -->
- <activity android:name="com.example.lib3.LibActivity3" />
- <activity-alias android:name="com.example.lib3.MyActivity"
- android:targetActivity="com.example.app1.MainActivity" />
- <service android:name="com.example.lib3.LibService3" />
- <receiver android:name="com.example.lib3.LibReceiver3" />
- <provider android:name="com.example.lib3.LibProvider3" />
- </application>
-</manifest>
-
-@errors
-
-P [ManifestMergerTest0_main.xml:6, ManifestMergerTest2_lib2_activity.xml:5] Skipping identical /manifest/application/activity[@name=com.example.lib2.LibActivity] element.
-P [ManifestMergerTest0_main.xml, ManifestMergerTest3_lib3_alias.xml:8] Skipping identical /manifest/application/activity[@name=com.example.lib2.LibActivity2] element.
diff --git a/misc/distrib_plugins/build.gradle b/misc/distrib_plugins/build.gradle
index ef15e44..81e96c1 100644
--- a/misc/distrib_plugins/build.gradle
+++ b/misc/distrib_plugins/build.gradle
@@ -1,6 +1,5 @@
apply plugin: 'java'
apply plugin: 'clone-artifacts'
-apply plugin: 'maven'
cloneArtifacts {
mainRepo = "$rootDir/../../../../prebuilts/tools/common/gradle-plugins/repository"
diff --git a/misc/distrib_plugins/buildSrc/src/main/groovy/com/android/build/gradle/buildsrc/ArtifactDownloader.groovy b/misc/distrib_plugins/buildSrc/src/main/groovy/com/android/build/gradle/buildsrc/ArtifactDownloader.groovy
index 365ffce..001873f 100644
--- a/misc/distrib_plugins/buildSrc/src/main/groovy/com/android/build/gradle/buildsrc/ArtifactDownloader.groovy
+++ b/misc/distrib_plugins/buildSrc/src/main/groovy/com/android/build/gradle/buildsrc/ArtifactDownloader.groovy
@@ -15,7 +15,6 @@
*/
package com.android.build.gradle.buildsrc
-
import com.google.common.base.Charsets
import com.google.common.collect.Sets
import com.google.common.hash.HashCode
@@ -29,7 +28,6 @@
import org.gradle.api.artifacts.ModuleVersionSelector
import org.gradle.api.artifacts.result.*
import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
-import org.gradle.api.tasks.TaskAction
class ArtifactDownloader {
@@ -136,7 +134,7 @@
for (ModuleVersionIdentifier id : gradleRepoList) {
pullArtifact(repoUrls, id, secondaryRepo, downloadedSet)
}
- } catch (IOException e) {
+ } catch (Throwable e) {
e.printStackTrace()
}
}
@@ -161,7 +159,8 @@
private void pullArtifact(String[] repoUrls, ModuleVersionIdentifier artifact,
File rootDestination, Set<ModuleVersionIdentifier> downloadedSet) throws IOException {
// ignore all android artifacts and already downloaded artifacts
- if (artifact.group.startsWith("com.android") || BaseTask.isLocalArtifact(artifact)) {
+ if ((artifact.group.startsWith("com.android") && !artifact.group.startsWith("com.android.tools.external.")) ||
+ BaseTask.isLocalArtifact(artifact)) {
return
}
diff --git a/misc/screenshot2/build.gradle b/misc/screenshot2/build.gradle
index 8e47bd0..7e597c4 100644
--- a/misc/screenshot2/build.gradle
+++ b/misc/screenshot2/build.gradle
@@ -1,3 +1,6 @@
+apply plugin: 'java'
+apply plugin: 'distrib'
+
group = 'com.android.tools'
archivesBaseName = 'screenshot2'
@@ -12,3 +15,5 @@
// configure the manifest of the buildDistributionJar task.
buildDistributionJar.manifest.attributes("Main-Class": "com.android.screenshot.Screenshot")
+
+apply from: '../../baseVersion.gradle'
\ No newline at end of file
diff --git a/ninepatch/build.gradle b/ninepatch/build.gradle
index daa99ef..c1109ff 100644
--- a/ninepatch/build.gradle
+++ b/ninepatch/build.gradle
@@ -1,3 +1,6 @@
+apply plugin: 'java'
+apply plugin: 'distrib'
+
group = 'com.android.tools'
archivesBaseName = 'ninepatch'
@@ -10,3 +13,4 @@
test.resources.srcDir 'src/test/java'
}
+apply from: '../baseVersion.gradle'
diff --git a/perflib/src/main/java/com/android/tools/perflib/vmtrace/Call.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/Call.java
index 9144ea5..406ab92 100644
--- a/perflib/src/main/java/com/android/tools/perflib/vmtrace/Call.java
+++ b/perflib/src/main/java/com/android/tools/perflib/vmtrace/Call.java
@@ -17,26 +17,45 @@
package com.android.tools.perflib.vmtrace;
import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
+import com.google.common.primitives.UnsignedInts;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Iterator;
import java.util.List;
+import java.util.Stack;
+import java.util.concurrent.TimeUnit;
public class Call {
private final long mMethodId;
+ /**
+ * Note: The thread entry and exit times are stored as unsigned integers in the trace data.
+ * In this model, they are stored as integers, but the getters for all of these time values
+ * convert them into longs.
+ */
private final int mEntryThreadTime;
private final int mEntryGlobalTime;
private final int mExitGlobalTime;
private final int mExitThreadTime;
+ private final long mInclusiveThreadTimeInCallees;
+ private final long mInclusiveGlobalTimeInCallees;
+
private final int mDepth;
+ /**
+ * Indicates whether the current call is recursive. A call is recursive if the same method
+ * is present in its backstack.
+ */
+ private final boolean mIsRecursive;
+
private final List<Call> mCallees;
- private Call(Builder builder) {
+ private Call(@NonNull Builder builder, @NonNull Stack<Long> backStack) {
mMethodId = builder.mMethodId;
mEntryThreadTime = builder.mEntryThreadTime;
@@ -44,17 +63,32 @@
mExitThreadTime = builder.mExitThreadTime;
mExitGlobalTime = builder.mExitGlobalTime;
- mDepth = builder.mDepth;
+ mDepth = backStack.size();
+
+ mIsRecursive = backStack.contains(mMethodId);
+
if (builder.mCallees == null) {
mCallees = Collections.emptyList();
} else {
+ backStack.push(mMethodId);
List<Call> callees = new ArrayList<Call>(builder.mCallees.size());
for (Builder b : builder.mCallees) {
- b.setStackDepth(mDepth + 1);
- callees.add(b.build());
+ callees.add(b.build(backStack));
}
+ backStack.pop();
mCallees = new ImmutableList.Builder<Call>().addAll(callees).build();
}
+
+ mInclusiveThreadTimeInCallees = sumInclusiveTimes(mCallees, ClockType.THREAD);
+ mInclusiveGlobalTimeInCallees = sumInclusiveTimes(mCallees, ClockType.GLOBAL);
+ }
+
+ private long sumInclusiveTimes(@NonNull List<Call> callees, ClockType clockType) {
+ long sum = 0;
+ for (Call c : callees) {
+ sum += c.getInclusiveTime(clockType, TimeUnit.MICROSECONDS);
+ }
+ return sum;
}
public long getMethodId() {
@@ -70,12 +104,39 @@
return mDepth;
}
- public int getEntryThreadTime() {
- return mEntryThreadTime;
+ /**
+ * Returns true if the call is recursive (another call of the same method id is present
+ * in its backstack)
+ */
+ public boolean isRecursive() {
+ return mIsRecursive;
}
- public int getExitThreadTime() {
- return mExitThreadTime;
+ public long getEntryTime(ClockType clockType, TimeUnit units) {
+ long entryTime = clockType == ClockType.THREAD ?
+ UnsignedInts.toLong(mEntryThreadTime) : UnsignedInts.toLong(mEntryGlobalTime);
+ return units.convert(entryTime, VmTraceData.getDefaultTimeUnits());
+ }
+
+ public long getExitTime(ClockType clockType, TimeUnit units) {
+ long exitTime = clockType == ClockType.THREAD ?
+ UnsignedInts.toLong(mExitThreadTime) : UnsignedInts.toLong(mExitGlobalTime);
+ return units.convert(exitTime, VmTraceData.getDefaultTimeUnits());
+ }
+
+ public long getInclusiveTime(ClockType clockType, TimeUnit units) {
+ long inclusiveTime = clockType == ClockType.THREAD ?
+ UnsignedInts.toLong(mExitThreadTime - mEntryThreadTime) :
+ UnsignedInts.toLong(mExitGlobalTime - mEntryGlobalTime);
+ return units.convert(inclusiveTime, VmTraceData.getDefaultTimeUnits());
+ }
+
+ public long getExclusiveTime(ClockType clockType, TimeUnit units) {
+ long inclusiveTimeInCallees = clockType == ClockType.THREAD ?
+ mInclusiveThreadTimeInCallees : mInclusiveGlobalTimeInCallees;
+ long exclusiveTime = getInclusiveTime(clockType, VmTraceData.getDefaultTimeUnits()) -
+ inclusiveTimeInCallees;
+ return units.convert(exclusiveTime, VmTraceData.getDefaultTimeUnits());
}
public static class Builder {
@@ -86,8 +147,6 @@
private int mExitGlobalTime;
private int mExitThreadTime;
- private int mDepth = 0;
-
private List<Builder> mCallees = null;
public Builder(long methodId) {
@@ -115,12 +174,30 @@
mCallees.add(c);
}
- public void setStackDepth(int depth) {
- mDepth = depth;
+ @Nullable
+ public List<Builder> getCallees() {
+ return mCallees;
}
- public Call build() {
- return new Call(this);
+ public int getMethodEntryThreadTime() {
+ return mEntryThreadTime;
+ }
+
+ public int getMethodEntryGlobalTime() {
+ return mEntryGlobalTime;
+ }
+
+ public int getMethodExitThreadTime() {
+ return mExitThreadTime;
+ }
+
+ public int getMethodExitGlobalTime() {
+ return mExitGlobalTime;
+ }
+
+ @NonNull
+ public Call build(@NonNull Stack<Long> backStack) {
+ return new Call(this, backStack);
}
}
@@ -138,7 +215,7 @@
String format(Call c);
}
- private static Formatter METHOD_ID_FORMATTER = new Formatter() {
+ private static final Formatter METHOD_ID_FORMATTER = new Formatter() {
@Override
public String format(Call c) {
return Long.toString(c.getMethodId());
@@ -169,4 +246,46 @@
callee.printCallHierarchy(sb, formatter);
}
}
+
+ @NonNull
+ public Iterator<Call> getCallHierarchyIterator() {
+ return new CallHierarchyIterator(this);
+ }
+
+ /**
+ * An iterator for a call hierarchy. The iteration order matches the order in which the calls
+ * were invoked.
+ */
+ private static class CallHierarchyIterator implements Iterator<Call> {
+ private final Stack<Call> mCallStack = new Stack<Call>();
+
+ public CallHierarchyIterator(@NonNull Call top) {
+ mCallStack.push(top);
+ }
+
+ @Override
+ public boolean hasNext() {
+ return !mCallStack.isEmpty();
+ }
+
+ @Override
+ public Call next() {
+ if (mCallStack.isEmpty()) {
+ return null;
+ }
+
+ Call top = mCallStack.pop();
+
+ for (int i = top.getCallees().size() - 1; i >= 0; i--) {
+ mCallStack.push(top.getCallees().get(i));
+ }
+
+ return top;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
}
diff --git a/perflib/src/main/java/com/android/tools/perflib/vmtrace/CallStackReconstructor.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/CallStackReconstructor.java
index e241db8..2af6be0 100644
--- a/perflib/src/main/java/com/android/tools/perflib/vmtrace/CallStackReconstructor.java
+++ b/perflib/src/main/java/com/android/tools/perflib/vmtrace/CallStackReconstructor.java
@@ -16,7 +16,7 @@
package com.android.tools.perflib.vmtrace;
-import com.google.common.collect.ImmutableList;
+import com.android.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
@@ -27,13 +27,25 @@
* trace events (method entry/exit events).
*/
public class CallStackReconstructor {
+ /** Method id corresponding to the top level call under which all calls are nested. */
+ private final long mTopLevelCallId;
+
/** List of calls currently assumed to be at stack depth 0 (called from the top level) */
- private List<Call.Builder> mTopLevelCalls = new ArrayList<Call.Builder>();
+ private final List<Call.Builder> mTopLevelCalls = new ArrayList<Call.Builder>();
/** Current call stack based on the sequence of received trace events. */
- private Stack<Call.Builder> mCallStack = new Stack<Call.Builder>();
+ private final Stack<Call.Builder> mCallStack = new Stack<Call.Builder>();
- private List<Call> mTopLevelCallees;
+ /** The single top level call under which the entire reconstructed call stack nests. */
+ private Call mTopLevelCall;
+
+ /**
+ * Constructs a call stack reconstructor with the method id under which
+ * the entire call stack should nest.
+ * */
+ public CallStackReconstructor(long topLevelCallId) {
+ mTopLevelCallId = topLevelCallId;
+ }
public void addTraceAction(long methodId, TraceAction action, int threadTime, int globalTime) {
if (action == TraceAction.METHOD_ENTER) {
@@ -72,7 +84,6 @@
// We are exiting out of a method that was entered into before tracing was started.
// In such a case, create this method
Call.Builder c = new Call.Builder(methodId);
- c.setMethodExitTime(threadTime, globalTime);
// All the previous calls at the top level are now assumed to have been called from
// this method. So mark this method as having called all of those methods, and reset
@@ -82,28 +93,79 @@
}
mTopLevelCalls.clear();
mTopLevelCalls.add(c);
+
+ c.setMethodExitTime(threadTime, globalTime);
+
+ // We don't know this method's entry times, so we try to guess:
+ // If it has atleast 1 callee, then we know it must've been atleast before that callee's
+ // start time. If there are no callees, then we just assume that it was just before its
+ // exit times.
+ int entryThreadTime = threadTime - 1;
+ int entryGlobalTime = globalTime - 1;
+
+ if (c.getCallees() != null && !c.getCallees().isEmpty()) {
+ Call.Builder callee = c.getCallees().get(0);
+ entryThreadTime = Math.max(callee.getMethodEntryThreadTime() - 1, 0);
+ entryGlobalTime = Math.max(callee.getMethodEntryGlobalTime() - 1, 0);
+ }
+ c.setMethodEntryTime(entryThreadTime, entryGlobalTime);
}
}
+ /**
+ * Generates a trace action equivalent to exiting from the given method
+ * @param methoId id of the method from which we are exiting
+ * @param entryThreadTime method's thread entry time
+ * @param entryGlobalTime method's global entry time
+ * @param callees from the method that we are exiting
+ */
+ private void exitMethod(long methoId, int entryThreadTime, int entryGlobalTime,
+ @Nullable List<Call.Builder> callees) {
+ int lastExitThreadTime;
+ int lastExitGlobalTime;
+
+ if (callees == null || callees.isEmpty()) {
+ // if the call doesn't have any callees, we assume that it just ran for 1 unit of time
+ lastExitThreadTime = entryThreadTime + 1;
+ lastExitGlobalTime = entryGlobalTime + 1;
+ } else {
+ // if it did call other methods, we assume that this call exited 1 unit of time after
+ // its last callee exited
+ Call.Builder last = callees.get(callees.size() - 1);
+ lastExitThreadTime = last.getMethodExitThreadTime() + 1;
+ lastExitGlobalTime = last.getMethodExitGlobalTime() + 1;
+ }
+
+ exitMethod(methoId, lastExitThreadTime, lastExitGlobalTime);
+ }
+
private void fixupCallStacks() {
- if (mTopLevelCallees != null) {
+ if (mTopLevelCall != null) {
return;
}
+ // If there are any methods still on the call stack, then the trace doesn't have
+ // exit trace action for them, so clean those up
+ while (!mCallStack.isEmpty()) {
+ Call.Builder cb = mCallStack.peek();
+ exitMethod(cb.getMethodId(), cb.getMethodEntryThreadTime(),
+ cb.getMethodEntryGlobalTime(), cb.getCallees());
+ }
+
+ // Now that we have parsed the entire call stack, let us move all of it under a single
+ // top level call.
+ exitMethod(mTopLevelCallId, 0, 0, mTopLevelCalls);
+
// TODO: use global / thread times to infer context switches
// Build calls from their respective builders
- List<Call> topLevelCallees = new ArrayList<Call>(mTopLevelCalls.size());
- for (Call.Builder b : mTopLevelCalls) {
- b.setStackDepth(0);
- topLevelCallees.add(b.build());
- }
-
- mTopLevelCallees = new ImmutableList.Builder<Call>().addAll(topLevelCallees).build();
+ // Now that we've added the top level call, there should be only 1 top level call
+ assert mTopLevelCalls.size() == 1;
+ mTopLevelCall = mTopLevelCalls.get(0).build(new Stack<Long>());
}
- public List<Call> getTopLevelCallees() {
+ public Call getTopLevel() {
fixupCallStacks();
- return mTopLevelCallees;
+ return mTopLevelCall;
}
}
diff --git a/perflib/src/main/java/com/android/tools/perflib/vmtrace/ClockType.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/ClockType.java
new file mode 100644
index 0000000..0a059f1
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/vmtrace/ClockType.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.vmtrace;
+
+public enum ClockType {
+ THREAD, // thread time or cpu time
+ GLOBAL, // global time or wall-clock time
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/vmtrace/MethodInfo.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/MethodInfo.java
index c2b0edb..6356a5d 100644
--- a/perflib/src/main/java/com/android/tools/perflib/vmtrace/MethodInfo.java
+++ b/perflib/src/main/java/com/android/tools/perflib/vmtrace/MethodInfo.java
@@ -16,6 +16,8 @@
package com.android.tools.perflib.vmtrace;
+import com.android.annotations.NonNull;
+
import java.util.Locale;
public class MethodInfo {
@@ -26,11 +28,13 @@
public final String srcPath;
public final int srcLineNumber;
+ private MethodProfileData mProfileData;
+
private String mFullName;
private String mShortName;
- public MethodInfo(long id, String className, String methodName, String signature, String srcPath,
- int srcLineNumber) {
+ public MethodInfo(long id, String className, String methodName, String signature,
+ String srcPath, int srcLineNumber) {
this.id = id;
this.className = className;
this.methodName = methodName;
@@ -61,4 +65,13 @@
}
return cn;
}
+
+ @NonNull
+ public MethodProfileData getProfileData() {
+ return mProfileData;
+ }
+
+ public void setProfileData(@NonNull MethodProfileData profileData) {
+ mProfileData = profileData;
+ }
}
diff --git a/perflib/src/main/java/com/android/tools/perflib/vmtrace/MethodProfileData.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/MethodProfileData.java
new file mode 100644
index 0000000..25dca83
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/vmtrace/MethodProfileData.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.vmtrace;
+
+import com.android.annotations.Nullable;
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableTable;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Table;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/** Statistics per method. */
+public class MethodProfileData {
+ /** {@link TimeUnit} for all time values stored in this model. */
+ private static final TimeUnit DATA_TIME_UNITS = TimeUnit.NANOSECONDS;
+
+ /** Stats maintained per thread. Key is thread id. */
+ private final Map<Integer, MethodStats> mPerThreadCumulativeStats;
+
+ /** Stats maintained per thread and per callee. */
+ private final Table<Integer, Long, MethodStats> mPerThreadStatsByCallee;
+
+ /** Stats maintained per thread and per caller. */
+ private final Table<Integer, Long, MethodStats> mPerThreadStatsByCaller;
+
+ /** Indicates whether this method was ever used recursively. */
+ private final boolean mIsRecursive;
+
+ private MethodProfileData(Builder b) {
+ mPerThreadCumulativeStats = ImmutableMap.copyOf(b.mPerThreadCumulativeStats);
+ mPerThreadStatsByCallee = ImmutableTable.copyOf(b.mPerThreadStatsByCallee);
+ mPerThreadStatsByCaller = ImmutableTable.copyOf(b.mPerThreadStatsByCaller);
+ mIsRecursive = b.mRecursive;
+ }
+
+ /** Returns the number of invocations of this method in a given thread. */
+ public long getInvocationCount(ThreadInfo thread) {
+ MethodStats stats = mPerThreadCumulativeStats.get(thread.getId());
+ return getInvocationCount(stats);
+ }
+
+ public long getInvocationCountFromCaller(ThreadInfo thread, Long callerId) {
+ MethodStats stats = mPerThreadStatsByCaller.get(thread.getId(), callerId);
+ return getInvocationCount(stats);
+ }
+
+ /** Returns whether this method was ever called recursively. */
+ public boolean isRecursive() {
+ return mIsRecursive;
+ }
+
+ /** Returns the exclusive time of this method in a particular thread in the given time units. */
+ public long getExclusiveTime(ThreadInfo thread, ClockType clockType, TimeUnit unit) {
+ MethodStats stats = mPerThreadCumulativeStats.get(thread.getId());
+ return getExclusiveTime(stats, clockType, unit);
+ }
+
+ /** Returns the inclusive time of this method in a particular thread in the given time units. */
+ public long getInclusiveTime(ThreadInfo thread, ClockType clockType, TimeUnit unit) {
+ MethodStats stats = mPerThreadCumulativeStats.get(thread.getId());
+ return getInclusiveTime(stats, clockType, unit);
+ }
+
+ /** Returns the callers for this method in a given thread. (across all its invocations). */
+ public Set<Long> getCallers(ThreadInfo thread) {
+ Map<Long, MethodStats> perCallerStats = mPerThreadStatsByCaller.row(thread.getId());
+ return perCallerStats.keySet();
+ }
+
+ /** Returns the callees from this method in a given thread. (across all its invocations). */
+ public Set<Long> getCallees(ThreadInfo thread) {
+ Map<Long, MethodStats> perCalleeStats = mPerThreadStatsByCallee.row(thread.getId());
+ return perCalleeStats.keySet();
+ }
+
+ /** Returns the exclusive time of this method when called from the given caller method. */
+ public long getExclusiveTimeByCaller(ThreadInfo thread, Long callerId,
+ ClockType clockType, TimeUnit unit) {
+ MethodStats stats = mPerThreadStatsByCaller.get(thread.getId(), callerId);
+ return getExclusiveTime(stats, clockType, unit);
+ }
+
+ /** Returns the inclusive time of this method when called from the given caller method. */
+ public long getInclusiveTimeByCaller(ThreadInfo thread, Long callerId,
+ ClockType clockType, TimeUnit unit) {
+ MethodStats stats = mPerThreadStatsByCaller.get(thread.getId(), callerId);
+ return getInclusiveTime(stats, clockType, unit);
+ }
+
+ /** Returns the inclusive time of the callee when called from this method. */
+ public long getInclusiveTimeByCallee(ThreadInfo thread, Long calleeId,
+ ClockType clockType, TimeUnit unit) {
+ MethodStats stats = mPerThreadStatsByCallee.get(thread.getId(), calleeId);
+ return getInclusiveTime(stats, clockType, unit);
+ }
+
+ private long getExclusiveTime(@Nullable MethodStats stats, ClockType clockType, TimeUnit unit) {
+ return stats != null ? stats.getExclusiveTime(clockType, unit) : 0;
+ }
+
+ private long getInclusiveTime(@Nullable MethodStats stats, ClockType clockType,
+ TimeUnit unit) {
+ return stats != null ? stats.getInclusiveTime(clockType, unit) : 0;
+ }
+
+ private long getInvocationCount(MethodStats stats) {
+ return stats != null ? stats.getInvocationCount() : 0;
+ }
+
+ private static class MethodStats {
+ private long mInclusiveThreadTime;
+ private long mExclusiveThreadTime;
+
+ private long mInclusiveGlobalTime;
+ private long mExclusiveGlobalTime;
+
+ private long mInvocationCount;
+
+ public long getInclusiveTime(ClockType clockType, TimeUnit unit) {
+ long time = clockType == ClockType.THREAD ? mInclusiveThreadTime : mInclusiveGlobalTime;
+ return unit.convert(time, DATA_TIME_UNITS);
+ }
+
+ public long getExclusiveTime(ClockType clockType, TimeUnit unit) {
+ long time = clockType == ClockType.THREAD ? mExclusiveThreadTime : mExclusiveGlobalTime;
+ return unit.convert(time, DATA_TIME_UNITS);
+ }
+
+ private long getInvocationCount() {
+ return mInvocationCount;
+ }
+ }
+
+ public static class Builder {
+ private final Map<Integer, MethodStats> mPerThreadCumulativeStats = Maps.newHashMap();
+ private final Table<Integer, Long, MethodStats> mPerThreadStatsByCaller =
+ HashBasedTable.create();
+ private final Table<Integer, Long, MethodStats> mPerThreadStatsByCallee =
+ HashBasedTable.create();
+
+ private boolean mRecursive;
+
+ public void addCallTime(Call call, Call parent, ThreadInfo thread) {
+ for (ClockType type: ClockType.values()) {
+ addExclusiveTime(call, parent, thread, type);
+
+ if (!call.isRecursive()) {
+ addInclusiveTime(call, parent, thread, type);
+ }
+ }
+ }
+
+ private void addExclusiveTime(Call call, Call parent, ThreadInfo thread, ClockType type) {
+ long time = call.getExclusiveTime(type, DATA_TIME_UNITS);
+
+ addExclusiveTime(getPerThreadStats(thread), time, type);
+ if (parent != null) {
+ addExclusiveTime(getPerCallerStats(thread, parent), time, type);
+ }
+ }
+
+ private void addInclusiveTime(Call call, Call parent, ThreadInfo thread, ClockType type) {
+ long time = call.getInclusiveTime(type, DATA_TIME_UNITS);
+
+ addInclusiveTime(getPerThreadStats(thread), time, type);
+ if (parent != null) {
+ addInclusiveTime(getPerCallerStats(thread, parent), time, type);
+ }
+ for (Call callee: call.getCallees()) {
+ addInclusiveTime(getPerCalleeStats(thread, callee),
+ callee.getInclusiveTime(type, DATA_TIME_UNITS), type);
+ }
+ }
+
+ private void addInclusiveTime(MethodStats stats, long time, ClockType type) {
+ if (type == ClockType.THREAD) {
+ stats.mInclusiveThreadTime += time;
+ } else {
+ stats.mInclusiveGlobalTime += time;
+ }
+ }
+
+ private void addExclusiveTime(MethodStats stats, long time, ClockType type) {
+ if (type == ClockType.THREAD) {
+ stats.mExclusiveThreadTime += time;
+ } else {
+ stats.mExclusiveGlobalTime += time;
+ }
+ }
+
+ private MethodStats getPerThreadStats(ThreadInfo thread) {
+ MethodStats stats = mPerThreadCumulativeStats.get(thread.getId());
+ if (stats == null) {
+ stats = new MethodStats();
+ mPerThreadCumulativeStats.put(thread.getId(), stats);
+ }
+ return stats;
+ }
+
+ private MethodStats getPerCallerStats(ThreadInfo thread, Call parent) {
+ return getMethodStatsFromTable(thread.getId(), parent.getMethodId(),
+ mPerThreadStatsByCaller);
+ }
+
+ private MethodStats getPerCalleeStats(ThreadInfo thread, Call callee) {
+ return getMethodStatsFromTable(thread.getId(), callee.getMethodId(),
+ mPerThreadStatsByCallee);
+ }
+
+ private MethodStats getMethodStatsFromTable(Integer threadId, Long methodId,
+ Table<Integer, Long, MethodStats> statsTable) {
+ MethodStats stats = statsTable.get(threadId, methodId);
+ if (stats == null) {
+ stats = new MethodStats();
+ statsTable.put(threadId, methodId, stats);
+ }
+ return stats;
+ }
+
+ public void incrementInvocationCount(Call c, Call parent, ThreadInfo thread) {
+ getPerThreadStats(thread).mInvocationCount++;
+ if (parent != null) {
+ getPerCallerStats(thread, parent).mInvocationCount++;
+ }
+ for (Call callee: c.getCallees()) {
+ getPerCalleeStats(thread, callee).mInvocationCount++;
+ }
+ }
+
+ public MethodProfileData build() {
+ return new MethodProfileData(this);
+ }
+
+ public void setRecursive() {
+ mRecursive = true;
+ }
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/vmtrace/SearchResult.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/SearchResult.java
new file mode 100644
index 0000000..6b76a4d
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/vmtrace/SearchResult.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.vmtrace;
+
+import com.android.annotations.NonNull;
+
+import java.util.Set;
+
+public class SearchResult {
+ private final Set<MethodInfo> mMethods;
+ private final Set<Call> mInstances;
+
+ public SearchResult(@NonNull Set<MethodInfo> methods, @NonNull Set<Call> instances) {
+ mMethods = methods;
+ mInstances = instances;
+ }
+
+ @NonNull
+ public Set<MethodInfo> getMethods() {
+ return mMethods;
+ }
+
+ @NonNull
+ public Set<Call> getInstances() {
+ return mInstances;
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/vmtrace/ThreadInfo.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/ThreadInfo.java
new file mode 100644
index 0000000..44d44bb
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/vmtrace/ThreadInfo.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.vmtrace;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+public class ThreadInfo {
+ /** Thread id */
+ private final int mId;
+
+ /** Thread name */
+ private final String mName;
+
+ /** Top level call in this thread */
+ private final Call mTopLevelCall;
+
+ public ThreadInfo(int threadId, @NonNull String name, @Nullable Call topLevelCall) {
+ mId = threadId;
+ mName = name;
+ mTopLevelCall = topLevelCall;
+ }
+
+ public int getId() {
+ return mId;
+ }
+
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ @Nullable
+ public Call getTopLevelCall() {
+ return mTopLevelCall;
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/vmtrace/TimeSelector.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/TimeSelector.java
new file mode 100644
index 0000000..6c45a76
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/vmtrace/TimeSelector.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.vmtrace;
+
+import java.util.concurrent.TimeUnit;
+
+public abstract class TimeSelector {
+ public abstract long get(MethodInfo info, ThreadInfo thread, TimeUnit unit);
+
+ private static final TimeSelector sInclusiveThreadTimeSelector = new TimeSelector() {
+ @Override
+ public long get(MethodInfo info, ThreadInfo thread, TimeUnit unit) {
+ return info.getProfileData().getInclusiveTime(thread, ClockType.THREAD, unit);
+ }
+ };
+
+ private static final TimeSelector sInclusiveGlobalTimeSelector = new TimeSelector() {
+ @Override
+ public long get(MethodInfo info, ThreadInfo thread, TimeUnit unit) {
+ return info.getProfileData().getInclusiveTime(thread, ClockType.GLOBAL, unit);
+ }
+ };
+
+ private static final TimeSelector sExclusiveThreadTimeSelector = new TimeSelector() {
+ @Override
+ public long get(MethodInfo info, ThreadInfo thread, TimeUnit unit) {
+ return info.getProfileData().getExclusiveTime(thread, ClockType.THREAD, unit);
+ }
+ };
+
+ private static final TimeSelector sExclusiveGlobalTimeSelector = new TimeSelector() {
+ @Override
+ public long get(MethodInfo info, ThreadInfo thread, TimeUnit unit) {
+ return info.getProfileData().getExclusiveTime(thread, ClockType.GLOBAL, unit);
+ }
+ };
+
+ public static TimeSelector create(ClockType type, boolean useInclusiveTime) {
+ switch (type) {
+ case THREAD:
+ return useInclusiveTime ?
+ sInclusiveThreadTimeSelector : sExclusiveThreadTimeSelector;
+ case GLOBAL:
+ return useInclusiveTime ?
+ sInclusiveGlobalTimeSelector : sExclusiveGlobalTimeSelector;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/vmtrace/VmTraceData.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/VmTraceData.java
index b6f27c8..e9ca6a4 100644
--- a/perflib/src/main/java/com/android/tools/perflib/vmtrace/VmTraceData.java
+++ b/perflib/src/main/java/com/android/tools/perflib/vmtrace/VmTraceData.java
@@ -17,10 +17,22 @@
package com.android.tools.perflib.vmtrace;
import com.android.utils.SparseArray;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
/**
* The {@link VmTraceData} class stores all the information from a Dalvik method trace file.
@@ -28,36 +40,46 @@
* <ul>
* <li>A mapping from thread ids to thread names.</li>
* <li>A mapping from method ids to {@link MethodInfo}</li>
- * <li>A mapping from each thread to the list of call stacks ({@link Call}) on that thread.</li>
+ * <li>A mapping from each thread to the top level call on that thread.</li>
* </ul>
*/
public class VmTraceData {
- public static enum ClockType { THREAD_CPU, WALL, DUAL }
+ public static enum VmClockType { THREAD_CPU, WALL, DUAL }
private final int mVersion;
private final boolean mDataFileOverflow;
- private final ClockType mClockType;
+ private final VmClockType mVmClockType;
private final String mVm;
private final Map<String, String> mTraceProperties;
- /** Map from thread ids to thread names. */
- private final SparseArray<String> mThreads;
-
/** Map from method id to method info. */
private final Map<Long,MethodInfo> mMethods;
- /** Map from thread id to list of call stacks invoked in that thread */
- private final SparseArray<List<Call>> mCalls;
+ /** Map from thread name to thread info. */
+ private final Map<String, ThreadInfo> mThreadInfo;
private VmTraceData(Builder b) {
mVersion = b.mVersion;
mDataFileOverflow = b.mDataFileOverflow;
- mClockType = b.mClockType;
+ mVmClockType = b.mVmClockType;
mVm = b.mVm;
mTraceProperties = b.mProperties;
- mThreads = b.mThreads;
mMethods = b.mMethods;
- mCalls = b.mCalls;
+
+ mThreadInfo = Maps.newHashMapWithExpectedSize(b.mThreads.size());
+ for (int i = 0; i < b.mThreads.size(); i++) {
+ int id = b.mThreads.keyAt(i);
+ String name = b.mThreads.valueAt(i);
+
+ ThreadInfo info = mThreadInfo.get(name);
+ if (info != null) {
+ // there is alread a thread with the same name
+ name = String.format("%1$s-%2$d", name, id);
+ }
+
+ info = new ThreadInfo(id, name, b.mTopLevelCalls.get(id));
+ mThreadInfo.put(name, info);
+ }
}
public int getVersion() {
@@ -68,8 +90,8 @@
return mDataFileOverflow;
}
- public ClockType getClockType() {
- return mClockType;
+ public VmClockType getVmClockType() {
+ return mVmClockType;
}
public String getVm() {
@@ -80,8 +102,33 @@
return mTraceProperties;
}
- public SparseArray<String> getThreads() {
- return mThreads;
+ public static TimeUnit getDefaultTimeUnits() {
+ // The traces from the VM currently use microseconds.
+ // TODO: figure out if this can be obtained/inferred from the trace itself
+ return TimeUnit.MICROSECONDS;
+ }
+
+ public Collection<ThreadInfo> getThreads() {
+ return mThreadInfo.values();
+ }
+
+ public List<ThreadInfo> getThreads(boolean excludeThreadsWithNoActivity) {
+ Collection<ThreadInfo> allThreads = getThreads();
+ if (!excludeThreadsWithNoActivity) {
+ return ImmutableList.copyOf(allThreads);
+ }
+
+ return Lists.newArrayList(Iterables.filter(allThreads, new Predicate<ThreadInfo>() {
+ @Override
+ public boolean apply(
+ com.android.tools.perflib.vmtrace.ThreadInfo input) {
+ return input.getTopLevelCall() != null;
+ }
+ }));
+ }
+
+ public ThreadInfo getThread(String name) {
+ return mThreadInfo.get(name);
}
public Map<Long,MethodInfo> getMethods() {
@@ -92,8 +139,70 @@
return mMethods.get(methodId);
}
- public List<Call> getCalls(int threadId) {
- return mCalls.get(threadId);
+ /** Returns the duration of this call as a percentage of the duration of the top level call. */
+ public double getDurationPercentage(Call call, ThreadInfo thread, ClockType clockType,
+ boolean inclusiveTime) {
+ MethodInfo methodInfo = getMethod(call.getMethodId());
+ TimeSelector selector = TimeSelector.create(clockType, inclusiveTime);
+ long methodTime = selector.get(methodInfo, thread, TimeUnit.NANOSECONDS);
+ return getDurationPercentage(methodTime, thread, clockType);
+ }
+
+ /**
+ * Returns the given duration as a percentage of the duration of the top level call
+ * in given thread.
+ */
+ public double getDurationPercentage(long methodTime, ThreadInfo thread, ClockType clockType) {
+ Call topCall = getThread(thread.getName()).getTopLevelCall();
+ if (topCall == null) {
+ return 100.;
+ }
+
+ MethodInfo topInfo = getMethod(topCall.getMethodId());
+
+ // always use inclusive time to obtain the top level's time when computing percentages
+ TimeSelector selector = TimeSelector.create(clockType, true);
+ long topLevelTime = selector.get(topInfo, thread, TimeUnit.NANOSECONDS);
+
+ return (double) methodTime/topLevelTime * 100;
+ }
+
+ public SearchResult searchFor(String pattern, ThreadInfo thread) {
+ pattern = pattern.toLowerCase(Locale.US);
+
+ Set<MethodInfo> methods = new HashSet<MethodInfo>();
+ Set<Call> calls = new HashSet<Call>();
+
+ Call topLevelCall = getThread(thread.getName()).getTopLevelCall();
+ if (topLevelCall == null) {
+ // no matches
+ return new SearchResult(methods, calls);
+ }
+
+ // Find all methods matching given pattern called on given thread
+ for (MethodInfo method: getMethods().values()) {
+ String fullName = method.getFullName().toLowerCase(Locale.US);
+ if (fullName.contains(pattern)) { // method name matches
+ long inclusiveTime = method.getProfileData()
+ .getInclusiveTime(thread, ClockType.GLOBAL, TimeUnit.NANOSECONDS);
+ if (inclusiveTime > 0) {
+ // method was called in this thread
+ methods.add(method);
+ }
+ }
+ }
+
+ // Find all invocations of the matched methods
+ Iterator<Call> iterator = topLevelCall.getCallHierarchyIterator();
+ while (iterator.hasNext()) {
+ Call c = iterator.next();
+ MethodInfo method = getMethod(c.getMethodId());
+ if (methods.contains(method)) {
+ calls.add(c);
+ }
+ }
+
+ return new SearchResult(methods, calls);
}
public static class Builder {
@@ -101,7 +210,7 @@
private int mVersion;
private boolean mDataFileOverflow;
- private ClockType mClockType = ClockType.THREAD_CPU;
+ private VmClockType mVmClockType = VmClockType.THREAD_CPU;
private String mVm = "";
private final Map<String, String> mProperties = new HashMap<String, String>(10);
@@ -115,8 +224,8 @@
private final SparseArray<CallStackReconstructor> mStackReconstructors
= new SparseArray<CallStackReconstructor>(10);
- /** Map from thread id to list of call stacks invoked in that thread */
- private final SparseArray<List<Call>> mCalls = new SparseArray<List<Call>>(10);
+ /** Map from thread id to the top level call for that thread. */
+ private final SparseArray<Call> mTopLevelCalls = new SparseArray<Call>(10);
public void setVersion(int version) {
mVersion = version;
@@ -130,12 +239,12 @@
mDataFileOverflow = dataFileOverflow;
}
- public void setClockType(ClockType clockType) {
- mClockType = clockType;
+ public void setVmClockType(VmClockType vmClockType) {
+ mVmClockType = vmClockType;
}
- public ClockType getClockType() {
- return mClockType;
+ public VmClockType getVmClockType() {
+ return mVmClockType;
}
public void setProperty(String key, String value) {
@@ -156,8 +265,6 @@
public void addMethodAction(int threadId, long methodId, TraceAction methodAction,
int threadTime, int globalTime) {
- MethodInfo methodInfo = mMethods.get(methodId);
-
// create thread info if it doesn't exist
if (mThreads.get(threadId) == null) {
mThreads.put(threadId, String.format("Thread id: %1$d", threadId));
@@ -171,27 +278,35 @@
}
if (DEBUG) {
- System.out.println(
- methodId + ": " + methodAction + ": thread: " + mThreads.get(threadId)
- + ", method: "
- + methodInfo.className + "/" + methodInfo.methodName + ":"
- + methodInfo.signature);
+ MethodInfo methodInfo = mMethods.get(methodId);
+ System.out.printf("Thread %1$30s: (%2$8x) %3$-40s %4$20s\n",
+ mThreads.get(threadId), methodId, methodInfo.getShortName(), methodAction);
}
CallStackReconstructor reconstructor = mStackReconstructors.get(threadId);
if (reconstructor == null) {
- reconstructor = new CallStackReconstructor();
+ long topLevelCallId = createUniqueMethodIdForThread(threadId);
+ reconstructor = new CallStackReconstructor(topLevelCallId);
mStackReconstructors.put(threadId, reconstructor);
}
reconstructor.addTraceAction(methodId, methodAction, threadTime, globalTime);
}
+ private long createUniqueMethodIdForThread(int threadId) {
+ long id = Long.MAX_VALUE - mThreads.indexOfKey(threadId);
+ assert mMethods.get(id) == null :
+ "Unexpected error while attempting to create a unique key - key already exists";
+ MethodInfo info = new MethodInfo(id, mThreads.get(threadId), "", "", "", 0);
+ mMethods.put(id, info);
+ return id;
+ }
+
public VmTraceData build() {
for (int i = 0; i < mStackReconstructors.size(); i++) {
int threadId = mStackReconstructors.keyAt(i);
CallStackReconstructor reconstructor = mStackReconstructors.valueAt(i);
- mCalls.put(threadId, reconstructor.getTopLevelCallees());
+ mTopLevelCalls.put(threadId, reconstructor.getTopLevel());
}
return new VmTraceData(this);
diff --git a/perflib/src/main/java/com/android/tools/perflib/vmtrace/VmTraceParser.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/VmTraceParser.java
index 41a9087..4ed03e4 100644
--- a/perflib/src/main/java/com/android/tools/perflib/vmtrace/VmTraceParser.java
+++ b/perflib/src/main/java/com/android/tools/perflib/vmtrace/VmTraceParser.java
@@ -1,18 +1,40 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.tools.perflib.vmtrace;
+import com.android.annotations.NonNull;
import com.android.annotations.VisibleForTesting;
import com.google.common.base.Charsets;
+import com.google.common.collect.Maps;
import com.google.common.io.Closeables;
+import com.google.common.primitives.UnsignedInts;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
-import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
public class VmTraceParser {
private static final int TRACE_MAGIC = 0x574f4c53; // 'SLOW'
@@ -44,6 +66,7 @@
long headerLength = parseHeader(mTraceFile);
MappedByteBuffer buffer = mapFile(mTraceFile, headerLength);
parseData(buffer);
+ computeTimingStatistics();
}
public VmTraceData getTraceData() {
@@ -131,11 +154,11 @@
if (key.equals(KEY_CLOCK)) {
if (value.equals("thread-cpu")) {
- mTraceDataBuilder.setClockType(VmTraceData.ClockType.THREAD_CPU);
+ mTraceDataBuilder.setVmClockType(VmTraceData.VmClockType.THREAD_CPU);
} else if (value.equals("wall")) {
- mTraceDataBuilder.setClockType(VmTraceData.ClockType.WALL);
+ mTraceDataBuilder.setVmClockType(VmTraceData.VmClockType.WALL);
} else if (value.equals("dual")) {
- mTraceDataBuilder.setClockType(VmTraceData.ClockType.DUAL);
+ mTraceDataBuilder.setVmClockType(VmTraceData.VmClockType.DUAL);
}
} else if (key.equals(KEY_DATA_OVERFLOW)) {
mTraceDataBuilder.setDataFileOverflow(Boolean.parseBoolean(value));
@@ -158,8 +181,7 @@
int id = Integer.decode(line.substring(0, index));
String name = line.substring(index).trim();
mTraceDataBuilder.addThread(id, name);
- } catch (NumberFormatException e) {
- return;
+ } catch (NumberFormatException ignored) {
}
}
@@ -240,20 +262,20 @@
int methodId;
int threadId;
int version = mTraceDataBuilder.getVersion();
- VmTraceData.ClockType clockType = mTraceDataBuilder.getClockType();
+ VmTraceData.VmClockType vmClockType = mTraceDataBuilder.getVmClockType();
while (buffer.hasRemaining()) {
int threadTime;
int globalTime;
int positionStart = buffer.position();
- threadId = version == 1 ? buffer.getInt() : buffer.getShort();
+ threadId = version == 1 ? buffer.get() : buffer.getShort();
methodId = buffer.getInt();
- switch (clockType) {
+ switch (vmClockType) {
case WALL:
- threadTime = 0;
globalTime = buffer.getInt();
+ threadTime = globalTime;
break;
case DUAL:
threadTime = buffer.getInt();
@@ -262,7 +284,7 @@
case THREAD_CPU:
default:
threadTime = buffer.getInt();
- globalTime = 0;
+ globalTime = threadTime;
break;
}
@@ -290,7 +312,8 @@
}
methodId = methodId & ~0x03;
- mTraceDataBuilder.addMethodAction(threadId, methodId, methodAction, threadTime, globalTime);
+ mTraceDataBuilder.addMethodAction(threadId, UnsignedInts.toLong(methodId), methodAction,
+ threadTime, globalTime);
}
}
@@ -371,4 +394,60 @@
dataFile.close(); // this *also* closes the associated channel, fc
}
}
+
+ private void computeTimingStatistics() {
+ VmTraceData data = getTraceData();
+
+ ProfileDataBuilder builder = new ProfileDataBuilder();
+ for (ThreadInfo thread : data.getThreads()) {
+ Call c = thread.getTopLevelCall();
+ if (c == null) {
+ continue;
+ }
+
+ builder.computeCallStats(c, null, thread);
+ }
+
+ for (Long methodId : builder.getMethodsWithProfileData()) {
+ MethodInfo method = data.getMethod(methodId);
+ method.setProfileData(builder.getProfileData(methodId));
+ }
+ }
+
+ private static class ProfileDataBuilder {
+ /** Maps method ids to their corresponding method data builders */
+ private final Map<Long, MethodProfileData.Builder> mBuilderMap = Maps.newHashMap();
+
+ public void computeCallStats(Call c, Call parent, ThreadInfo thread) {
+ long methodId = c.getMethodId();
+ MethodProfileData.Builder builder = getProfileDataBuilder(methodId);
+ builder.addCallTime(c, parent, thread);
+ builder.incrementInvocationCount(c, parent, thread);
+ if (c.isRecursive()) {
+ builder.setRecursive();
+ }
+
+ for (Call callee: c.getCallees()) {
+ computeCallStats(callee, c, thread);
+ }
+ }
+
+ @NonNull
+ private MethodProfileData.Builder getProfileDataBuilder(long methodId) {
+ MethodProfileData.Builder builder = mBuilderMap.get(methodId);
+ if (builder == null) {
+ builder = new MethodProfileData.Builder();
+ mBuilderMap.put(methodId, builder);
+ }
+ return builder;
+ }
+
+ public Set<Long> getMethodsWithProfileData() {
+ return mBuilderMap.keySet();
+ }
+
+ public MethodProfileData getProfileData(Long methodId) {
+ return mBuilderMap.get(methodId).build();
+ }
+ }
}
diff --git a/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/CallHierarchyRenderer.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/CallHierarchyRenderer.java
new file mode 100644
index 0000000..6209153
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/CallHierarchyRenderer.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.vmtrace.viz;
+
+import com.android.annotations.NonNull;
+import com.android.tools.perflib.vmtrace.Call;
+import com.android.tools.perflib.vmtrace.ClockType;
+import com.android.tools.perflib.vmtrace.MethodInfo;
+import com.android.tools.perflib.vmtrace.ThreadInfo;
+import com.android.tools.perflib.vmtrace.TimeSelector;
+import com.android.tools.perflib.vmtrace.VmTraceData;
+import com.android.utils.HtmlBuilder;
+
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.text.DecimalFormat;
+import java.util.Iterator;
+import java.util.concurrent.TimeUnit;
+
+import static com.android.tools.perflib.vmtrace.ClockType.THREAD;
+import static com.android.tools.perflib.vmtrace.ClockType.GLOBAL;
+
+/** Renders the call hierarchy rooted at a given call that is part of the trace. */
+public class CallHierarchyRenderer {
+ /** Height in pixels for a single call instance. Its length is proportional to its duration. */
+ private static final int PER_LEVEL_HEIGHT_PX = 10;
+ private static final int PADDING = 1;
+
+ private static final int TEXT_HEIGHT = 6;
+ private static final int TEXT_LEFT_PADDING = 5;
+
+ private final VmTraceData mTraceData;
+ private final ThreadInfo mThread;
+ private final Call mTopCall;
+ private final int mYOffset;
+ private final TimeUnit mLayoutTimeUnits;
+ private final RenderContext mRenderContext;
+
+ private final Rectangle2D mLayout = new Rectangle2D.Double();
+ private final Point2D mTmpPoint1 = new Point2D.Double();
+ private final Point2D mTmpPoint2 = new Point2D.Double();
+
+ private Font mFont;
+
+ public CallHierarchyRenderer(@NonNull VmTraceData vmTraceData, @NonNull ThreadInfo thread,
+ int yOffset, TimeUnit defaultTimeUnits, RenderContext renderContext) {
+ mTraceData = vmTraceData;
+ mThread = thread;
+ mTopCall = thread.getTopLevelCall();
+ mYOffset = yOffset;
+ mLayoutTimeUnits = defaultTimeUnits;
+ mRenderContext = renderContext;
+ }
+
+ /**
+ * Renders the call hierarchy on a given graphics context.
+ * This essentially iterates through every single call in the hierarchy and renders it if it is
+ * visible in the current viewport.
+ */
+ public void render(Graphics2D g, AffineTransform viewPortTransform) {
+ Rectangle clip = g.getClipBounds();
+
+ Iterator<Call> it = mTopCall.getCallHierarchyIterator();
+ while (it.hasNext()) {
+ Call c = it.next();
+
+ // obtain layout in item space
+ fillLayoutBounds(c, mLayout);
+
+ // transform based on the current viewport (scale + translate)
+ transformRect(viewPortTransform, mLayout);
+
+ // no need to render if it is is not in the current viewport.
+ if (!clip.intersects(mLayout)) {
+ continue;
+ }
+
+ // no need to render if it is too small (arbitrarily assumed to be < 1 px wide)
+ if (mLayout.getWidth() < 1) {
+ continue;
+ }
+
+ // obtain the fill color based on its importance
+ Color fillColor = mRenderContext.getFillColor(c, mThread);
+ g.setColor(fillColor);
+ g.fill(mLayout);
+
+ // paint its name within the rectangle if possible
+ String name = getName(c);
+ drawString(g, name, mLayout, mRenderContext.getFontColor(c, mThread));
+ }
+ }
+
+ private Rectangle2D transformRect(AffineTransform viewPortTransform, Rectangle2D rect) {
+ mTmpPoint1.setLocation(rect.getX(), rect.getY());
+ mTmpPoint2.setLocation(rect.getWidth(), rect.getHeight());
+
+ viewPortTransform.transform(mTmpPoint1, mTmpPoint1);
+ viewPortTransform.deltaTransform(mTmpPoint2, mTmpPoint2);
+
+ rect.setRect(mTmpPoint1.getX(),
+ mTmpPoint1.getY(),
+ mTmpPoint2.getX(),
+ mTmpPoint2.getY());
+ return rect;
+ }
+
+ private void drawString(Graphics2D g, String name, Rectangle2D bounds, Color fontColor) {
+ if (mFont == null) {
+ mFont = g.getFont().deriveFont(8.0f);
+ }
+ g.setFont(mFont);
+ g.setColor(fontColor);
+
+ AffineTransform origTx = g.getTransform();
+
+ mTmpPoint1.setLocation(bounds.getX() + TEXT_LEFT_PADDING, bounds.getY() + TEXT_HEIGHT);
+
+ double availableWidth = g.getTransform().getScaleX() * bounds.getWidth();
+
+ // When drawing a string, we want its location to be transformed by the current viewport
+ // transform, but not the text itself (we don't want it zoomed out or in).
+ origTx.transform(mTmpPoint1, mTmpPoint1);
+ g.setTransform(new AffineTransform());
+
+ double stringWidth = g.getFontMetrics().stringWidth(name);
+ if (availableWidth > stringWidth) {
+ g.drawString(name, (float) mTmpPoint1.getX(), (float) mTmpPoint1.getY());
+ }
+
+ g.setTransform(origTx);
+ }
+
+ /** Fills the layout bounds corresponding to a given call in the given Rectangle object. */
+ private void fillLayoutBounds(Call c, Rectangle2D layoutBounds) {
+ ClockType renderClock = mRenderContext.getRenderClock();
+ double x = c.getEntryTime(renderClock, mLayoutTimeUnits)
+ - mTopCall.getEntryTime(renderClock, mLayoutTimeUnits)
+ + PADDING;
+ double y = c.getDepth() * PER_LEVEL_HEIGHT_PX + mYOffset + PADDING;
+ double width = c.getInclusiveTime(renderClock, mLayoutTimeUnits) - 2 * PADDING;
+ double height = PER_LEVEL_HEIGHT_PX - 2 * PADDING;
+ layoutBounds.setRect(x, y, width, height);
+ }
+
+ /** Get the tooltip corresponding to given location (in item coordinates). */
+ public String getToolTipFor(double x, double y) {
+ Iterator<Call> it = mTopCall.getCallHierarchyIterator();
+ while (it.hasNext()) {
+ Call c = it.next();
+
+ fillLayoutBounds(c, mLayout);
+ if (mLayout.contains(x, y)) {
+ return formatToolTip(c);
+ }
+ }
+
+ return null;
+ }
+
+ private static final DecimalFormat PERCENTAGE_FORMATTER = new DecimalFormat("#.##");
+
+ private String formatToolTip(Call c) {
+ HtmlBuilder htmlBuilder = new HtmlBuilder();
+ htmlBuilder.openHtmlBody();
+
+ htmlBuilder.addHeading(getMethodInfo(c).getFullName(), "black");
+
+ long span = c.getExitTime(GLOBAL, TimeUnit.NANOSECONDS) -
+ c.getEntryTime(GLOBAL, TimeUnit.NANOSECONDS);
+ TimeUnit unit = TimeUnit.NANOSECONDS;
+ String entryGlobal = TimeUtils.makeHumanReadable(c.getEntryTime(GLOBAL, unit), span, unit);
+ String entryThread = TimeUtils.makeHumanReadable(c.getEntryTime(THREAD, unit), span, unit);
+ String exitGlobal = TimeUtils.makeHumanReadable(c.getExitTime(GLOBAL, unit), span, unit);
+ String exitThread = TimeUtils.makeHumanReadable(c.getExitTime(THREAD, unit), span, unit);
+ String durationGlobal = TimeUtils.makeHumanReadable(
+ c.getExitTime(GLOBAL, unit) - c.getEntryTime(GLOBAL, unit), span, unit);
+ String durationThread = TimeUtils.makeHumanReadable(
+ c.getExitTime(THREAD, unit) - c.getEntryTime(THREAD, unit), span, unit);
+
+ htmlBuilder.beginTable();
+ htmlBuilder.addTableRow("Wallclock Time:", durationGlobal,
+ String.format("(from %s to %s)", entryGlobal, exitGlobal));
+ htmlBuilder.addTableRow("CPU Time:", durationThread,
+ String.format("(from %s to %s)", entryThread, exitThread));
+ htmlBuilder.endTable();
+
+ htmlBuilder.newline();
+ htmlBuilder.add("Inclusive Time: ");
+ htmlBuilder.beginBold();
+ double inclusivePercentage = mTraceData.getDurationPercentage(c, mThread,
+ mRenderContext.getRenderClock(), true /* use inclusive time */);
+ htmlBuilder.add(PERCENTAGE_FORMATTER.format(inclusivePercentage));
+ htmlBuilder.add("%");
+ htmlBuilder.endBold();
+
+ htmlBuilder.newline();
+ htmlBuilder.add("Exclusive Time: ");
+ htmlBuilder.beginBold();
+ double exclusivePercentage = mTraceData.getDurationPercentage(c, mThread,
+ mRenderContext.getRenderClock(), false /* don't use inclusive time */);
+ htmlBuilder.add(PERCENTAGE_FORMATTER.format(exclusivePercentage));
+ htmlBuilder.add("%");
+ htmlBuilder.endBold();
+
+ htmlBuilder.closeHtmlBody();
+ return htmlBuilder.getHtml();
+ }
+
+ @NonNull
+ private String getName(@NonNull Call c) {
+ return getMethodInfo(c).getShortName();
+ }
+
+ private MethodInfo getMethodInfo(@NonNull Call c) {
+ long methodId = c.getMethodId();
+ return mTraceData.getMethod(methodId);
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/RenderContext.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/RenderContext.java
new file mode 100644
index 0000000..1daa2fb
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/RenderContext.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.vmtrace.viz;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.perflib.vmtrace.Call;
+import com.android.tools.perflib.vmtrace.ClockType;
+import com.android.tools.perflib.vmtrace.MethodInfo;
+import com.android.tools.perflib.vmtrace.ThreadInfo;
+import com.android.tools.perflib.vmtrace.VmTraceData;
+
+import java.awt.Color;
+import java.util.Collections;
+import java.util.Set;
+
+public class RenderContext {
+ /** Fill color for methods that are highlighted, (as the result of a search, for instance) */
+ private static final Color HIGHLIGHTED_METHOD_COLOR = new Color(0x4863A0);
+
+ private final VmTraceData mTraceData;
+ private ClockType mRenderClock;
+ private boolean mUseInclusiveTimeForColorAssignment;
+
+ private Set<MethodInfo> mHighlightedMethods = Collections.emptySet();
+
+ public RenderContext(VmTraceData traceData, ClockType renderClock) {
+ mTraceData = traceData;
+ mRenderClock = renderClock;
+ }
+
+ public void setRenderClock(@NonNull ClockType type) {
+ mRenderClock = type;
+ }
+
+ public void setUseInclusiveTimeForColorAssignment(boolean en) {
+ mUseInclusiveTimeForColorAssignment = en;
+ }
+
+ @NonNull
+ public ClockType getRenderClock() {
+ return mRenderClock;
+ }
+
+ // Sequential color palette that works across both light and dark backgrounds
+ private static final Color[] QUANTIZED_COLORS = {
+ new Color(226, 230, 189),
+ new Color(235, 228, 139),
+ new Color(242, 221, 128),
+ new Color(246, 210, 119),
+ new Color(246, 197, 111),
+ new Color(242, 180, 104),
+ new Color(234, 161, 98),
+ new Color(223, 139, 91),
+ new Color(207, 115, 85),
+ new Color(188, 88, 77),
+ new Color(166, 57, 69),
+ new Color(142, 6, 59),
+ };
+
+ /** Index into {@link #QUANTIZED_COLORS} where the crossover from light to dark happens. */
+ private static final int BRIGHT_TO_DARK_CROSSOVER_INDEX = 9;
+
+ private int getColorIndex(double percent) {
+ int i = (int) (percent * QUANTIZED_COLORS.length / 100);
+ return i >= QUANTIZED_COLORS.length ? QUANTIZED_COLORS.length - 1 : i;
+ }
+
+ /**
+ * Returns the fill color for a particular call. The fill color is dependent on its
+ * inclusive thread percentage time.
+ */
+ @NonNull
+ public Color getFillColor(Call c, ThreadInfo thread) {
+ if (isHighlightedMethod(c)) {
+ return HIGHLIGHTED_METHOD_COLOR;
+ }
+
+ double percent = mTraceData.getDurationPercentage(c, thread, mRenderClock,
+ mUseInclusiveTimeForColorAssignment);
+ return QUANTIZED_COLORS[getColorIndex(percent)];
+ }
+
+ /** Denote the set of method ids as corresponding to the results of a search. */
+ public void setHighlightedMethods(@Nullable Set<MethodInfo> highlightedMethods) {
+ mHighlightedMethods = highlightedMethods;
+ }
+
+ private boolean isHighlightedMethod(Call c) {
+ MethodInfo method = mTraceData.getMethod(c.getMethodId());
+ return mHighlightedMethods != null && mHighlightedMethods.contains(method);
+ }
+
+ /**
+ * Returns the font color for a particular call. This returns a color complementary to
+ * {@link #getFillColor}, so that text rendered on top of that color is distinguishable
+ * from the background.
+ */
+ @NonNull
+ public Color getFontColor(Call c, ThreadInfo thread) {
+ double percent = mTraceData.getDurationPercentage(c, thread, mRenderClock,
+ mUseInclusiveTimeForColorAssignment);
+ return getColorIndex(percent) < BRIGHT_TO_DARK_CROSSOVER_INDEX ? Color.BLACK : Color.WHITE;
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/TimeScaleRenderer.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/TimeScaleRenderer.java
new file mode 100644
index 0000000..48c2baa
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/TimeScaleRenderer.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.vmtrace.viz;
+
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.NoninvertibleTransformException;
+import java.util.concurrent.TimeUnit;
+
+public class TimeScaleRenderer {
+ /** Offset of the horizontal line indicating the timeline from the top of the screen. */
+ private static final int TIMELINE_Y_OFFSET = 20;
+
+ /** Horizontal padding from a marker to where its corresponding time is drawn. */
+ public static final int TIMELINE_UNIT_HORIZONTAL_PADDING = 5;
+
+ /** Vertical padding from the horizontal timeline to where the time units are drawn. */
+ public static final int TIMELINE_UNIT_VERTICAL_PADDING = 5;
+
+ private final long mStartTime;
+ private final TimeUnit mTimeUnits;
+
+ private double[] mPoints = new double[8];
+
+ private AffineTransform mViewTransform;
+ private AffineTransform mViewTransformInverse;
+
+ public TimeScaleRenderer(long startTime, TimeUnit unit) {
+ mStartTime = startTime;
+ mTimeUnits = unit;
+ }
+
+ public void paint(Graphics2D g2d, AffineTransform viewPortTransform, int screenWidth) {
+ AffineTransform originalTransform = g2d.getTransform();
+
+ // draw the custom timeline for the current viewport transformation
+ drawTimeLine(g2d, viewPortTransform, screenWidth);
+
+ g2d.setTransform(originalTransform);
+ }
+
+ private void drawTimeLine(Graphics2D g2d, AffineTransform viewPortTransform, int screenWidth) {
+ createInverse(viewPortTransform);
+
+ // (0,y)
+ mPoints[0] = 0;
+ mPoints[1] = 0; // Note: We don't care about the y-coordinate here.
+
+ // (screenWidth, y)
+ mPoints[2] = screenWidth;
+ mPoints[3] = 0; // Note: We don't care about the y-coordinate here.
+
+ // The timeline goes from (0,y) to (screenWidth,y) in screen space. We apply the
+ // inverse of the view transform to convert these to item space.
+ mViewTransformInverse.transform(mPoints, 0, mPoints, 4, 2);
+
+ // Offset both the left and right end points by the start time.
+ long start = (long) mPoints[4] + mStartTime;
+ long end = (long) mPoints[6] + mStartTime;
+
+ g2d.setColor(Color.BLACK);
+
+ // draw the horizontal timeline
+ g2d.drawLine(0, TIMELINE_Y_OFFSET, screenWidth, TIMELINE_Y_OFFSET);
+
+ // draw the time at the leftmost end of the screen corresponding to (0,y)
+ String time = TimeUtils.makeHumanReadable(start, end - start, mTimeUnits);
+ g2d.drawString(time,
+ 0 + TIMELINE_UNIT_HORIZONTAL_PADDING,
+ TIMELINE_Y_OFFSET - TIMELINE_UNIT_VERTICAL_PADDING);
+
+ // draw the time at the leftmost end of the screen corresponding to (screen width,y)
+ time = TimeUtils.makeHumanReadable(end, end - start, mTimeUnits);
+ g2d.drawString(time,
+ screenWidth - g2d.getFontMetrics().stringWidth(time)
+ - TIMELINE_UNIT_HORIZONTAL_PADDING,
+ TIMELINE_Y_OFFSET - TIMELINE_UNIT_VERTICAL_PADDING);
+ }
+
+ public int getLayoutHeight() {
+ return TIMELINE_Y_OFFSET + 10;
+ }
+
+ private void createInverse(AffineTransform viewPortTransform) {
+ if (!viewPortTransform.equals(mViewTransform)) {
+ // cache source transformation matrix
+ mViewTransform = new AffineTransform(viewPortTransform);
+
+ try {
+ mViewTransformInverse = mViewTransform.createInverse();
+ } catch (NoninvertibleTransformException e) {
+ // This scenario should never occur since the viewport is only zoomed or panned,
+ // both of which are invertible.
+ mViewTransformInverse = new AffineTransform();
+ }
+ }
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/TimeUtils.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/TimeUtils.java
new file mode 100644
index 0000000..c9ea1c4
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/TimeUtils.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.vmtrace.viz;
+
+import java.text.DecimalFormat;
+import java.util.concurrent.TimeUnit;
+
+public class TimeUtils {
+ public static String makeHumanReadable(long time, long span, TimeUnit timeUnits) {
+ String units;
+ double scale;
+ if (timeUnits.toSeconds(span) > 0) {
+ units = "s";
+ scale = 1e-9;
+ } else if (timeUnits.toMillis(span) > 0) {
+ units = "ms";
+ scale = 1e-6;
+ } else {
+ units = "us";
+ scale = 1e-3;
+ }
+
+ return String.format("%1$s %2$s", formatTime(timeUnits.toNanos(time), scale), units);
+ }
+
+ private static final DecimalFormat TIME_FORMATTER = new DecimalFormat("#.###");
+
+ private static String formatTime(long nsecs, double scale) {
+ return TIME_FORMATTER.format(nsecs * scale);
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/TraceViewCanvas.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/TraceViewCanvas.java
new file mode 100644
index 0000000..9d48f4d
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/TraceViewCanvas.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.vmtrace.viz;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.tools.perflib.vmtrace.Call;
+import com.android.tools.perflib.vmtrace.ClockType;
+import com.android.tools.perflib.vmtrace.MethodInfo;
+import com.android.tools.perflib.vmtrace.ThreadInfo;
+import com.android.tools.perflib.vmtrace.VmTraceData;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.RenderingHints;
+import java.awt.event.HierarchyBoundsAdapter;
+import java.awt.event.HierarchyEvent;
+import java.awt.event.MouseEvent;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.NoninvertibleTransformException;
+import java.awt.geom.Point2D;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import javax.swing.JComponent;
+import javax.swing.ToolTipManager;
+import javax.swing.UIManager;
+
+/**
+ * A canvas that displays the call hierarchy for a single thread. The trace and the the thread to be
+ * displayed are specified using {@link #setTrace} and {@link #displayThread} methods.
+ */
+public class TraceViewCanvas extends JComponent {
+ private static final Color BACKGROUND_COLOR =
+ UIManager.getLookAndFeelDefaults().getColor("EditorPane.background");
+ private static final int TOOLTIP_OFFSET = 10;
+
+ /**
+ * The time unit to use for all operations. Changing this changes the minimum resolution
+ * that can be viewed on the canvas.
+ */
+ private static final TimeUnit DEFAULT_TIME_UNITS = TimeUnit.NANOSECONDS;
+
+ /**
+ * Interactor that listens to mouse events, interprets them as zoom/pan events, and provides the
+ * resultant viewport transform.
+ */
+ private final ZoomPanInteractor mZoomPanInteractor;
+
+ /** The viewport transform takes into account the current zoom and translation/pan values. */
+ private AffineTransform mViewPortTransform;
+
+ /** Inverse of {@link #mViewPortTransform}. */
+ private AffineTransform mViewPortInverseTransform;
+
+ private VmTraceData mTraceData;
+ private Call mTopLevelCall;
+ private RenderContext mRenderContext;
+
+ private TimeScaleRenderer mTimeScaleRenderer;
+ private CallHierarchyRenderer mCallHierarchyRenderer;
+
+ private final Point2D mTmpPoint = new Point2D.Double();
+
+ public TraceViewCanvas() {
+ mViewPortTransform = new AffineTransform();
+ mViewPortInverseTransform = new AffineTransform();
+
+ mZoomPanInteractor = new ZoomPanInteractor();
+ addMouseListener(mZoomPanInteractor);
+ addMouseMotionListener(mZoomPanInteractor);
+ addMouseWheelListener(mZoomPanInteractor);
+
+ mZoomPanInteractor.addViewTransformListener(new ZoomPanInteractor.ViewTransformListener() {
+ @Override
+ public void transformChanged(@NonNull AffineTransform transform) {
+ updateViewPortTransform(transform);
+ }
+ });
+
+ addMouseMotionListener(ToolTipManager.sharedInstance());
+
+ // Listen for the first hierarchy bounds change so as to get the initial width,
+ // and then zoom fit once we know the width.
+ addHierarchyBoundsListener(new HierarchyBoundsAdapter() {
+ @Override
+ public void ancestorMoved(HierarchyEvent e) {
+ }
+
+ @Override
+ public void ancestorResized(HierarchyEvent e) {
+ removeHierarchyBoundsListener(this);
+ zoomFit();
+ }
+ });
+ }
+
+ public void setTrace(@NonNull VmTraceData traceData, @NonNull ThreadInfo thread,
+ ClockType renderClock) {
+ mTraceData = traceData;
+ mRenderContext = new RenderContext(traceData, renderClock);
+ displayThread(thread);
+ }
+
+ public void displayThread(@NonNull ThreadInfo thread) {
+ mCallHierarchyRenderer = null;
+ mTimeScaleRenderer = null;
+
+ if (mTraceData == null) {
+ return;
+ }
+
+ mTopLevelCall = thread.getTopLevelCall();
+ if (mTopLevelCall == null) {
+ return;
+ }
+
+ mTimeScaleRenderer = new TimeScaleRenderer(
+ mTopLevelCall.getEntryTime(ClockType.GLOBAL, DEFAULT_TIME_UNITS),
+ DEFAULT_TIME_UNITS);
+ int yOffset = mTimeScaleRenderer.getLayoutHeight();
+ mCallHierarchyRenderer = new CallHierarchyRenderer(mTraceData, thread, yOffset,
+ DEFAULT_TIME_UNITS, mRenderContext);
+
+ zoomFit();
+ }
+
+ public void setRenderClock(ClockType clock) {
+ mRenderContext.setRenderClock(clock);
+ repaint();
+ }
+
+ public void setUseInclusiveTimeForColorAssignment(boolean en) {
+ mRenderContext.setUseInclusiveTimeForColorAssignment(en);
+ repaint();
+ }
+
+ public void setHighlightMethods(@Nullable Set<MethodInfo> methods) {
+ mRenderContext.setHighlightedMethods(methods);
+ repaint();
+ }
+
+ public void zoomFit() {
+ if (mTopLevelCall == null) {
+ return;
+ }
+
+ long start = mTopLevelCall.getEntryTime(ClockType.GLOBAL, DEFAULT_TIME_UNITS);
+ long end = mTopLevelCall.getExitTime(ClockType.GLOBAL, DEFAULT_TIME_UNITS);
+
+ // Scale so that the full trace occupies 90% of the screen width.
+ double width = getWidth();
+ double sx = width * .9f / (end - start);
+
+ // Guard against trying to zoom when the component doesn't know its width yet. Width is
+ // usually 0 in such cases, but we just make it slightly general and check for width < 10.
+ if (width < 10) {
+ sx = Math.max(sx, 0.2);
+ }
+
+ // Initialize display so that the full trace is visible and takes up most of the view.
+ mZoomPanInteractor.setToScaleX(sx, 1); // make everything visible
+ mZoomPanInteractor.translateBy(50, 0); // shift over the start of the trace
+ updateViewPortTransform(mZoomPanInteractor.getTransform());
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ Graphics2D g2d = (Graphics2D) g;
+ setRenderingHints(g2d);
+
+ // fill with background color
+ g2d.setColor(BACKGROUND_COLOR);
+ g2d.fillRect(0, 0, getWidth(), getHeight());
+
+ if (mTraceData == null) {
+ return;
+ }
+
+ // paint stack layout view
+ if (mCallHierarchyRenderer != null) {
+ mCallHierarchyRenderer.render(g2d, mViewPortTransform);
+ }
+
+ // paint timeline at top
+ if (mTimeScaleRenderer != null) {
+ mTimeScaleRenderer.paint(g2d, mViewPortTransform, getWidth());
+ }
+ }
+
+ private void setRenderingHints(Graphics2D g2d) {
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+ RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+ g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+ g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+ RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+ }
+
+ @Override
+ public String getToolTipText(MouseEvent event) {
+ if (mTraceData == null || mCallHierarchyRenderer == null) {
+ return null;
+ }
+
+ mTmpPoint.setLocation(event.getPoint());
+ mViewPortInverseTransform.transform(mTmpPoint, mTmpPoint);
+ return mCallHierarchyRenderer.getToolTipFor(mTmpPoint.getX(), mTmpPoint.getY());
+ }
+
+ @Override
+ public Point getToolTipLocation(MouseEvent event) {
+ return new Point(event.getX() + TOOLTIP_OFFSET, event.getY() + TOOLTIP_OFFSET);
+ }
+
+ private void updateViewPortTransform(AffineTransform tx) {
+ mViewPortTransform = new AffineTransform(tx);
+
+ try {
+ mViewPortInverseTransform = mViewPortTransform.createInverse();
+ } catch (NoninvertibleTransformException e) {
+ // This can't occur since we just do scale or pan, both of which are invertible
+ mViewPortInverseTransform = new AffineTransform();
+ }
+
+ repaint();
+ }
+}
diff --git a/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/ZoomPanInteractor.java b/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/ZoomPanInteractor.java
new file mode 100644
index 0000000..e4e925c
--- /dev/null
+++ b/perflib/src/main/java/com/android/tools/perflib/vmtrace/viz/ZoomPanInteractor.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.vmtrace.viz;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.VisibleForTesting;
+
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.event.MouseWheelEvent;
+import java.awt.event.MouseWheelListener;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.NoninvertibleTransformException;
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * {@link ZoomPanInteractor} listens to mouse events, interprets dragging the mouse as an attempt
+ * to pan the canvas, and mouse wheel rotation as an attempt to zoom in/out the canvas.
+ * After such events, it updates its listeners with the updated transformation matrix corresponding
+ * to the zoom & pan values.
+ */
+public class ZoomPanInteractor implements MouseListener, MouseMotionListener, MouseWheelListener {
+ /**
+ * The values from {@link java.awt.event.MouseWheelEvent#getWheelRotation()} are quite high even
+ * for a small amount of scrolling. This is an arbitrary scale factor used to go from the wheel
+ * rotation value to a zoom by factor. The scale is negated to take care of the common
+ * expectation that scrolling down should zoom out, not zoom in.
+ */
+ private static final double WHEEL_UNIT_SCALE = -0.1;
+
+ private final AffineTransform mTransform = new AffineTransform();
+ private AffineTransform mInverseTransform;
+
+ private final Point2D mTmpPoint = new Point2D.Double();
+
+ private int mLastX;
+ private int mLastY;
+
+ private final List<ViewTransformListener> mListeners = new ArrayList<ViewTransformListener>();
+
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ }
+
+ @Override
+ public void mousePressed(MouseEvent e) {
+ mLastX = e.getX();
+ mLastY = e.getY();
+ }
+
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ int deltaX = e.getX() - mLastX;
+ int deltaY = e.getY() - mLastY;
+
+ translateBy(deltaX, deltaY);
+
+ notifyTransformChange();
+
+ mLastX = e.getX();
+ mLastY = e.getY();
+ }
+
+ @VisibleForTesting
+ void translateBy(int deltaX, int deltaY) {
+ // Transform pixels by the current viewport scaling factor.
+ // i.e when you have zoomed out by say 2x, and you drag by a pixel,
+ // you expect it to move (the model space) by 2 pixels, not one.
+ deltaX /= mTransform.getScaleX();
+ deltaY /= mTransform.getScaleY();
+
+ mTransform.translate(deltaX, deltaY);
+
+ // Do not allow panning above the axis.
+ // TODO: This actually encodes information about the canvas and what is drawn over here,
+ // and should be moved out.
+ if (mTransform.getTranslateY() > 0) {
+ mTransform.translate(0, -mTransform.getTranslateY());
+ }
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ }
+
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ }
+
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ }
+
+ @Override
+ public void mouseWheelMoved(MouseWheelEvent e) {
+ if (e.getScrollType() != MouseWheelEvent.WHEEL_UNIT_SCROLL) {
+ return;
+ }
+
+ double scale = 1 + WHEEL_UNIT_SCALE * e.getWheelRotation();
+
+ // convert mouse x, y from screen coordinates to absolute coordinates
+ mTmpPoint.setLocation(e.getX(), e.getY());
+ mInverseTransform.transform(mTmpPoint, mTmpPoint);
+
+ zoomBy(scale, 1, mTmpPoint);
+
+ notifyTransformChange();
+ }
+
+ @VisibleForTesting
+ void zoomBy(double scaleX, double scaleY, Point2D location) {
+ // When zooming, we want to zoom by the location the mouse currently points to.
+ // So we translate the current location to the origin, apply the scale, and translate back
+ mTransform.translate(location.getX(), location.getY());
+ mTransform.scale(scaleX, scaleY);
+ mTransform.translate(-location.getX(), -location.getY());
+ }
+
+ private void notifyTransformChange() {
+ try {
+ mInverseTransform = mTransform.createInverse();
+ } catch (NoninvertibleTransformException ignored) {
+ // The transform matrix is only scaled or translated, both of which are invertible.
+ }
+
+ for (ViewTransformListener l : mListeners) {
+ l.transformChanged(mTransform);
+ }
+ }
+
+ public void setToScaleX(double sx, double sy) {
+ mTransform.setToScale(sx, sy);
+ notifyTransformChange();
+ }
+
+ @VisibleForTesting
+ AffineTransform getTransform() {
+ return mTransform;
+ }
+
+ public interface ViewTransformListener {
+ public void transformChanged(@NonNull AffineTransform transform);
+ }
+
+ public void addViewTransformListener(@NonNull ViewTransformListener l) {
+ mListeners.add(l);
+ }
+}
diff --git a/perflib/src/test/java/com/android/tools/perflib/vmtrace/CallStackReconstructorTest.java b/perflib/src/test/java/com/android/tools/perflib/vmtrace/CallStackReconstructorTest.java
index f24b4c1..0f91f49 100644
--- a/perflib/src/test/java/com/android/tools/perflib/vmtrace/CallStackReconstructorTest.java
+++ b/perflib/src/test/java/com/android/tools/perflib/vmtrace/CallStackReconstructorTest.java
@@ -16,26 +16,26 @@
package com.android.tools.perflib.vmtrace;
-import com.google.common.base.Joiner;
-
import junit.framework.TestCase;
-import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
import java.util.List;
+import java.util.concurrent.TimeUnit;
public class CallStackReconstructorTest extends TestCase {
public void testBasicCallStack() {
- CallStackReconstructor reconstructor = new CallStackReconstructor();
+ CallStackReconstructor reconstructor = new CallStackReconstructor(0xff);
reconstructor.addTraceAction(0x1, TraceAction.METHOD_ENTER, 10, 10);
reconstructor.addTraceAction(0x1, TraceAction.METHOD_EXIT, 15, 15);
- List<Call> callees = reconstructor.getTopLevelCallees();
- assertEquals(1, callees.size());
- assertEquals(0x1, callees.get(0).getMethodId());
+ Call topLevel = reconstructor.getTopLevel();
+ assertEquals(1, topLevel.getCallees().size());
+ assertEquals(0x1, topLevel.getCallees().get(0).getMethodId());
}
- public void testCallStack1() {
- CallStackReconstructor reconstructor = new CallStackReconstructor();
+ private Call reconstructSampleCallStack() {
+ CallStackReconstructor reconstructor = new CallStackReconstructor(0xff);
reconstructor.addTraceAction(0x1, TraceAction.METHOD_ENTER, 10, 10);
reconstructor.addTraceAction(0x2, TraceAction.METHOD_ENTER, 11, 11);
@@ -50,17 +50,35 @@
reconstructor.addTraceAction(0x6, TraceAction.METHOD_ENTER, 21, 21);
reconstructor.addTraceAction(0x6, TraceAction.METHOD_EXIT, 22, 22);
- String callStack = getCallStackInfo(reconstructor.getTopLevelCallees());
+ return reconstructor.getTopLevel();
+ }
+
+ public void testCallStack() {
+ Call topLevel = reconstructSampleCallStack();
+ String callStack = topLevel.toString();
String expectedCallStack =
- " -> 1 -> 2 -> 3\n"
- + " -> 3\n"
- + " -> 5\n"
- + " -> 6";
+ " -> 255 -> 1 -> 2 -> 3\n"
+ + " -> 3\n"
+ + " -> 5\n"
+ + " -> 6";
assertEquals(expectedCallStack, callStack);
}
+ public void testCallHierarchyIterator() {
+ Call topLevel = reconstructSampleCallStack();
+ List<Integer> expectedSequence = Arrays.asList(255, 1, 2, 3, 3, 5, 6);
+ int i = 0;
+ Iterator<Call> it = topLevel.getCallHierarchyIterator();
+ while (it.hasNext()) {
+ Call c = it.next();
+ long expectedMethodId = expectedSequence.get(i++);
+ long actualMethodId = c.getMethodId();
+ assertEquals(expectedMethodId, actualMethodId);
+ }
+ }
+
public void testInvalidTrace() {
- CallStackReconstructor reconstructor = new CallStackReconstructor();
+ CallStackReconstructor reconstructor = new CallStackReconstructor(0xff);
try {
reconstructor.addTraceAction(0x1, TraceAction.METHOD_ENTER, 1, 1);
@@ -72,29 +90,18 @@
}
public void testMisMatchedCallStack() {
- CallStackReconstructor reconstructor = new CallStackReconstructor();
+ CallStackReconstructor reconstructor = new CallStackReconstructor(0xff);
reconstructor.addTraceAction(0x3, TraceAction.METHOD_EXIT, 1, 1);
reconstructor.addTraceAction(0x2, TraceAction.METHOD_EXIT, 2, 2);
reconstructor.addTraceAction(0x1, TraceAction.METHOD_EXIT, 3, 3);
- String callStack = getCallStackInfo(reconstructor.getTopLevelCallees());
- assertEquals(" -> 1 -> 2 -> 3", callStack);
+ String callStack = reconstructor.getTopLevel().toString();
+ assertEquals(" -> 255 -> 1 -> 2 -> 3", callStack);
}
- private String getCallStackInfo(List<Call> calls) {
- List<String> callStacks = new ArrayList<String>(calls.size());
-
- for (Call c : calls) {
- callStacks.add(c.toString());
- }
-
- return Joiner.on('\n').join(callStacks);
- }
-
-
public void testCallStackDepths() {
- CallStackReconstructor reconstructor = new CallStackReconstructor();
+ CallStackReconstructor reconstructor = new CallStackReconstructor(0xff);
reconstructor.addTraceAction(0x1, TraceAction.METHOD_ENTER, 10, 10);
reconstructor.addTraceAction(0x2, TraceAction.METHOD_ENTER, 11, 11);
@@ -107,10 +114,10 @@
reconstructor.addTraceAction(0x5, TraceAction.METHOD_EXIT, 18, 18);
reconstructor.addTraceAction(0x1, TraceAction.METHOD_EXIT, 20, 20);
- List<Call> calls = reconstructor.getTopLevelCallees();
- assertEquals(1, calls.size());
+ Call topLevel = reconstructor.getTopLevel();
+ assertEquals(1, topLevel.getCallees().size());
- Call c = calls.get(0);
+ Call c = topLevel.getCallees().get(0);
String actualDepths = c.format(new Call.Formatter() {
@Override
public String format(Call c) {
@@ -119,9 +126,91 @@
});
String expected =
- " -> 0 -> 1 -> 2\n"
- + " -> 2\n"
- + " -> 1";
+ " -> 1 -> 2 -> 3\n"
+ + " -> 3\n"
+ + " -> 2";
assertEquals(expected, actualDepths);
}
+
+ public void testCallDurations() {
+ CallStackReconstructor reconstructor = new CallStackReconstructor(0xff);
+
+ reconstructor.addTraceAction(0x1, TraceAction.METHOD_ENTER, 10, 10);
+ reconstructor.addTraceAction(0x2, TraceAction.METHOD_ENTER, 11, 11);
+ reconstructor.addTraceAction(0x2, TraceAction.METHOD_EXIT, 14, 14);
+ reconstructor.addTraceAction(0x1, TraceAction.METHOD_EXIT, 15, 15);
+
+ List<Call> callees = reconstructor.getTopLevel().getCallees();
+ assertFalse(callees.isEmpty());
+ Call call = callees.get(0);
+ assertEquals(15 - 10, call.getInclusiveTime(ClockType.THREAD, TimeUnit.MICROSECONDS));
+ assertEquals(15 - 10 - (14 - 11), call.getExclusiveTime(ClockType.THREAD,
+ TimeUnit.MICROSECONDS));
+ }
+
+ public void testMissingCallDurations() {
+ CallStackReconstructor reconstructor = new CallStackReconstructor(0xff);
+
+ // missing entry time for method 0x1, verify that it is computed as 1 less than
+ // the entry time for its callee (method 0x2)
+ reconstructor.addTraceAction(0x2, TraceAction.METHOD_ENTER, 11, 11);
+ reconstructor.addTraceAction(0x2, TraceAction.METHOD_EXIT, 14, 14);
+ reconstructor.addTraceAction(0x1, TraceAction.METHOD_EXIT, 15, 15);
+
+ List<Call> callees = reconstructor.getTopLevel().getCallees();
+ assertFalse(callees.isEmpty());
+ Call call = callees.get(0);
+ assertEquals(15 - (11 - 1), call.getInclusiveTime(ClockType.THREAD, TimeUnit.MICROSECONDS));
+ assertEquals(15 - (11 - 1) - (14 - 11), call.getExclusiveTime(ClockType.THREAD,
+ TimeUnit.MICROSECONDS));
+ }
+
+ /**
+ * Verify that the model handles cases where the timings exceed {@link Integer#MAX_VALUE},
+ * but are still within the scope of an unsigned integer.
+ */
+ public void testCallDurationOverflow() {
+ CallStackReconstructor reconstructor = new CallStackReconstructor(0xff);
+
+ reconstructor.addTraceAction(0x1, TraceAction.METHOD_ENTER, 0xfffffff0, 0xfffffff0);
+ reconstructor.addTraceAction(0x2, TraceAction.METHOD_ENTER, 0xfffffff2, 0xfffffff2);
+ reconstructor.addTraceAction(0x2, TraceAction.METHOD_EXIT, 0xfffffff4, 0xfffffff4);
+ reconstructor.addTraceAction(0x1, TraceAction.METHOD_EXIT, 0xfffffff8, 0xfffffff8);
+
+ List<Call> callees = reconstructor.getTopLevel().getCallees();
+ assertFalse(callees.isEmpty());
+ Call call = callees.get(0);
+ assertEquals(8, call.getInclusiveTime(ClockType.THREAD, TimeUnit.MICROSECONDS));
+ assertEquals(6, call.getExclusiveTime(ClockType.THREAD, TimeUnit.MICROSECONDS));
+ }
+
+ public void testRecursiveCalls() {
+ CallStackReconstructor reconstructor = new CallStackReconstructor(0xff);
+
+ reconstructor.addTraceAction(0x1, TraceAction.METHOD_ENTER, 1, 1);
+ reconstructor.addTraceAction(0x2, TraceAction.METHOD_ENTER, 3, 3);
+ reconstructor.addTraceAction(0x1, TraceAction.METHOD_ENTER, 4, 4); // recursive call, method id 1
+ reconstructor.addTraceAction(0x1, TraceAction.METHOD_EXIT, 5, 5);
+ reconstructor.addTraceAction(0x2, TraceAction.METHOD_EXIT, 6, 6);
+ reconstructor.addTraceAction(0x1, TraceAction.METHOD_EXIT, 8, 8);
+
+ List<Call> callees = reconstructor.getTopLevel().getCallees();
+
+ assertEquals(1, callees.size());
+ Call call1 = callees.get(0);
+ assertEquals(0x1, call1.getMethodId());
+ assertFalse(call1.isRecursive());
+
+ callees = call1.getCallees();
+ assertEquals(1, callees.size());
+ Call call2 = callees.get(0);
+ assertEquals(0x2, call2.getMethodId());
+ assertFalse(call2.isRecursive());
+
+ callees = call2.getCallees();
+ assertEquals(1, callees.size());
+ Call call3 = callees.get(0);
+ assertEquals(0x1, call3.getMethodId());
+ assertTrue(call3.isRecursive());
+ }
}
diff --git a/perflib/src/test/java/com/android/tools/perflib/vmtrace/VmTraceParserTest.java b/perflib/src/test/java/com/android/tools/perflib/vmtrace/VmTraceParserTest.java
index 87b7109..86045d3 100644
--- a/perflib/src/test/java/com/android/tools/perflib/vmtrace/VmTraceParserTest.java
+++ b/perflib/src/test/java/com/android/tools/perflib/vmtrace/VmTraceParserTest.java
@@ -16,9 +16,7 @@
package com.android.tools.perflib.vmtrace;
-import com.android.annotations.NonNull;
-import com.android.utils.SparseArray;
-import com.google.common.base.Joiner;
+import com.google.common.primitives.Ints;
import junit.framework.TestCase;
@@ -26,8 +24,14 @@
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
public class VmTraceParserTest extends TestCase {
public void testParseHeader() throws IOException {
@@ -38,13 +42,13 @@
assertEquals(3, traceData.getVersion());
assertTrue(traceData.isDataFileOverflow());
- assertEquals(VmTraceData.ClockType.DUAL, traceData.getClockType());
+ assertEquals(VmTraceData.VmClockType.DUAL, traceData.getVmClockType());
assertEquals("dalvik", traceData.getVm());
- SparseArray<String> threads = traceData.getThreads();
+ Collection<ThreadInfo> threads = traceData.getThreads();
assertEquals(2, threads.size());
- assertEquals("main", threads.get(1));
- assertEquals("AsyncTask #1", threads.get(11));
+ assertEquals(1, traceData.getThread("main").getId());
+ assertEquals(11, traceData.getThread("AsyncTask #1").getId());
Map<Long, MethodInfo> methods = traceData.getMethods();
assertEquals(4, methods.size());
@@ -76,64 +80,259 @@
}
}
- private void testTrace(String traceName, String threadName, String expectedCallSequence) throws IOException {
+ private void testTrace(String traceName, String threadName, String expectedCallSequence)
+ throws IOException {
VmTraceData traceData = getVmTraceData(traceName);
- int threadId = findThreadIdFromName(threadName, traceData.getThreads());
- assertTrue(String.format("Thread %s was not found in the trace", threadName), threadId > 0);
+ ThreadInfo thread = traceData.getThread(threadName);
+ assertNotNull(String.format("Thread %s was not found in the trace", threadName), thread);
- List<Call> calls = traceData.getCalls(threadId);
- String actual = formatCallStacks(calls, new CallFormatter(traceData.getMethods()));
+ Call call = thread.getTopLevelCall();
+ assertNotNull(call);
+ String actual = call.format(new CallFormatter(traceData.getMethods()));
assertEquals(expectedCallSequence, actual);
}
public void testBasicTrace() throws IOException {
String expected =
- " -> android/os/Debug.startMethodTracing: (Ljava/lang/String;)V -> android/os/Debug.startMethodTracing: (Ljava/lang/String;II)V -> dalvik/system/VMDebug.startMethodTracing: (Ljava/lang/String;II)V\n"
- + " -> com/test/android/traceview/Basic.foo: ()V -> com/test/android/traceview/Basic.bar: ()I\n"
- + " -> android/os/Debug.stopMethodTracing: ()V -> dalvik/system/VMDebug.stopMethodTracing: ()V";
+ " -> AsyncTask #1.: -> android/os/Debug.startMethodTracing: (Ljava/lang/String;)V -> android/os/Debug.startMethodTracing: (Ljava/lang/String;II)V -> dalvik/system/VMDebug.startMethodTracing: (Ljava/lang/String;II)V\n"
+ + " -> com/test/android/traceview/Basic.foo: ()V -> com/test/android/traceview/Basic.bar: ()I\n"
+ + " -> android/os/Debug.stopMethodTracing: ()V -> dalvik/system/VMDebug.stopMethodTracing: ()V";
testTrace("/basic.trace", "AsyncTask #1", expected);
+
+ // verify that the same results show up when trace is generated from an older device
+ testTrace("/basic-api10.trace", "AsyncTask #1", expected);
}
public void testMisMatchedTrace() throws IOException {
String expected =
- " -> com/test/android/traceview/MisMatched.foo: ()V -> com/test/android/traceview/MisMatched.bar: ()V -> android/os/Debug.startMethodTracing: (Ljava/lang/String;)V -> android/os/Debug.startMethodTracing: (Ljava/lang/String;II)V -> dalvik/system/VMDebug.startMethodTracing: (Ljava/lang/String;II)V\n"
- + " -> com/test/android/traceview/MisMatched.baz: ()I\n"
- + " -> android/os/Debug.stopMethodTracing: ()V -> dalvik/system/VMDebug.stopMethodTracing: ()V";
+ " -> AsyncTask #1.: -> com/test/android/traceview/MisMatched.foo: ()V -> com/test/android/traceview/MisMatched.bar: ()V -> android/os/Debug.startMethodTracing: (Ljava/lang/String;)V -> android/os/Debug.startMethodTracing: (Ljava/lang/String;II)V -> dalvik/system/VMDebug.startMethodTracing: (Ljava/lang/String;II)V\n"
+ + " -> com/test/android/traceview/MisMatched.baz: ()I\n"
+ + " -> android/os/Debug.stopMethodTracing: ()V -> dalvik/system/VMDebug.stopMethodTracing: ()V";
testTrace("/mismatched.trace", "AsyncTask #1", expected);
}
public void testExceptionTrace() throws IOException {
String expected =
- " -> android/os/Debug.startMethodTracing: (Ljava/lang/String;)V -> android/os/Debug.startMethodTracing: (Ljava/lang/String;II)V -> dalvik/system/VMDebug.startMethodTracing: (Ljava/lang/String;II)V\n"
- + " -> com/test/android/traceview/Exceptions.foo: ()V -> com/test/android/traceview/Exceptions.bar: ()V -> com/test/android/traceview/Exceptions.baz: ()V -> java/lang/RuntimeException.<init>: ()V -> java/lang/Exception.<init>: ()V -> java/lang/Throwable.<init>: ()V -> java/util/Collections.emptyList: ()Ljava/util/List;\n"
- + " -> java/lang/Throwable.fillInStackTrace: ()Ljava/lang/Throwable; -> java/lang/Throwable.nativeFillInStackTrace: ()Ljava/lang/Object;\n"
- + " -> android/os/Debug.stopMethodTracing: ()V -> dalvik/system/VMDebug.stopMethodTracing: ()V";
+ " -> AsyncTask #1.: -> android/os/Debug.startMethodTracing: (Ljava/lang/String;)V -> android/os/Debug.startMethodTracing: (Ljava/lang/String;II)V -> dalvik/system/VMDebug.startMethodTracing: (Ljava/lang/String;II)V\n"
+ + " -> com/test/android/traceview/Exceptions.foo: ()V -> com/test/android/traceview/Exceptions.bar: ()V -> com/test/android/traceview/Exceptions.baz: ()V -> java/lang/RuntimeException.<init>: ()V -> java/lang/Exception.<init>: ()V -> java/lang/Throwable.<init>: ()V -> java/util/Collections.emptyList: ()Ljava/util/List;\n"
+ + " -> java/lang/Throwable.fillInStackTrace: ()Ljava/lang/Throwable; -> java/lang/Throwable.nativeFillInStackTrace: ()Ljava/lang/Object;\n"
+ + " -> android/os/Debug.stopMethodTracing: ()V -> dalvik/system/VMDebug.stopMethodTracing: ()V";
testTrace("/exception.trace", "AsyncTask #1", expected);
}
- private int findThreadIdFromName(@NonNull String threadName,
- @NonNull SparseArray<String> threads) {
- for (int i = 0; i < threads.size(); i++) {
- int id = threads.keyAt(i);
- String name = threads.valueAt(i);
- if (threadName.equals(name)) {
- return id;
- }
- }
-
- return -1;
+ public void testCallDurations() throws IOException {
+ validateCallDurations("/basic.trace", "AsyncTask #1");
+ validateCallDurations("/mismatched.trace", "AsyncTask #1");
+ validateCallDurations("/exception.trace", "AsyncTask #1");
}
- private String formatCallStacks(List<Call> calls, Call.Formatter formatter) {
- if (calls == null) return "<none>";
- List<String> callStacks = new ArrayList<String>(calls.size());
+ private void validateCallDurations(String traceName, String threadName) throws IOException {
+ VmTraceData traceData = getVmTraceData(traceName);
- for (Call c : calls) {
- callStacks.add(c.format(formatter));
+ ThreadInfo thread = traceData.getThread(threadName);
+ assertNotNull(String.format("Thread %s was not found in the trace", threadName), thread);
+
+ Call topLevelCall = thread.getTopLevelCall();
+ assertNotNull(topLevelCall);
+ Iterator<Call> it = topLevelCall.getCallHierarchyIterator();
+ while (it.hasNext()) {
+ Call c = it.next();
+
+ assertTrue(c.getEntryTime(ClockType.GLOBAL, TimeUnit.NANOSECONDS) <=
+ c.getExitTime(ClockType.GLOBAL, TimeUnit.NANOSECONDS));
+ assertTrue(c.getEntryTime(ClockType.THREAD, TimeUnit.NANOSECONDS) <=
+ c.getExitTime(ClockType.THREAD, TimeUnit.NANOSECONDS));
+ }
+ }
+
+ public void testMethodStats() throws IOException {
+ VmTraceData traceData = getVmTraceData("/basic.trace");
+ final ThreadInfo thread = traceData.getThread("AsyncTask #1");
+ List<Map.Entry<Long, MethodInfo>> methods = new ArrayList<Map.Entry<Long, MethodInfo>>(
+ traceData.getMethods().entrySet());
+ Collections.sort(methods, new Comparator<Map.Entry<Long, MethodInfo>>() {
+ @Override
+ public int compare(Map.Entry<Long, MethodInfo> o1, Map.Entry<Long, MethodInfo> o2) {
+ long diff =
+ o2.getValue().getProfileData().getInclusiveTime(
+ thread, ClockType.THREAD, TimeUnit.NANOSECONDS) -
+ o1.getValue().getProfileData().getInclusiveTime(
+ thread, ClockType.THREAD, TimeUnit.NANOSECONDS);
+ return Ints.saturatedCast(diff);
+ }
+ });
+
+ // verify that the top level actually comes out with the max time
+ // note that while this works for the simple traces currently being tested, this
+ // condition itself isn't valid in case some methods are being called from multiple
+ // threads, in which their inclusive time could be higher than any of the thread's
+ // toplevel time.
+ assertEquals("AsyncTask #1.: ", methods.get(0).getValue().getFullName());
+ }
+
+ // Validate that the inclusive time of the top level call = sum of all inclusive times of
+ // all methods called from that top level
+ public void testMethodStats2() throws IOException {
+ VmTraceData traceData = getVmTraceData("/basic.trace");
+ ThreadInfo thread = traceData.getThread("AsyncTask #1");
+ Call top = thread.getTopLevelCall();
+
+ assertNotNull(top);
+
+ long topThreadTime = top.getInclusiveTime(ClockType.THREAD, TimeUnit.NANOSECONDS);
+
+ Collection<MethodInfo> methods = traceData.getMethods().values();
+ Iterator<MethodInfo> it = methods.iterator();
+ long sum = 0;
+
+ while (it.hasNext()) {
+ MethodInfo method = it.next();
+ sum += method.getProfileData().getExclusiveTime(thread, ClockType.THREAD,
+ TimeUnit.NANOSECONDS);
}
- return Joiner.on('\n').join(callStacks);
+ assertEquals(topThreadTime, sum);
+ }
+
+ public void testMethodProfileData() throws IOException {
+ VmTraceData traceData = getVmTraceData("/basic.trace");
+ ThreadInfo thread = traceData.getThread("AsyncTask #1");
+ Call top = thread.getTopLevelCall();
+
+ assertNotNull(top);
+
+ MethodProfileData topProfileData = traceData.getMethod(top.getMethodId()).getProfileData();
+
+ // There should only be 1 instance of the top level method, so that call's time
+ // should match its corresponding method's time.
+ assertEquals(top.getExclusiveTime(ClockType.GLOBAL, TimeUnit.NANOSECONDS),
+ topProfileData.getExclusiveTime(thread, ClockType.GLOBAL, TimeUnit.NANOSECONDS));
+ assertEquals(top.getInclusiveTime(ClockType.GLOBAL, TimeUnit.NANOSECONDS),
+ topProfileData.getInclusiveTime(thread, ClockType.GLOBAL, TimeUnit.NANOSECONDS));
+
+ // The top level call's time should match the sum of all its callee's inclusive times
+ // plus the top level's exclusive time.
+ long sum = 0;
+ for (Long callee : topProfileData.getCallees(thread)) {
+ sum += topProfileData.getInclusiveTimeByCallee(thread, callee, ClockType.GLOBAL,
+ TimeUnit.NANOSECONDS);
+ }
+
+ long exclusiveTime = top.getExclusiveTime(ClockType.GLOBAL, TimeUnit.NANOSECONDS);
+ assertEquals(top.getInclusiveTime(ClockType.GLOBAL, TimeUnit.NANOSECONDS),
+ exclusiveTime + sum);
+
+ for (MethodInfo method : traceData.getMethods().values()) {
+ MethodProfileData profile = method.getProfileData();
+ if (profile.getInvocationCount(thread) == 0) {
+ continue;
+ }
+
+ boolean isTop = method.id == top.getMethodId();
+
+ // Top level call should not have any callers, everyone else should have atleast 1
+ assertEquals(isTop, profile.getCallers(thread).isEmpty());
+
+ if (profile.isRecursive()) {
+ continue;
+ }
+
+ // Validate that the inclusive time is properly split across all callees
+ long methodInclusiveTime =
+ profile.getInclusiveTime(thread, ClockType.GLOBAL, TimeUnit.NANOSECONDS);
+ long methodExclusiveTime =
+ profile.getExclusiveTime(thread, ClockType.GLOBAL, TimeUnit.NANOSECONDS);
+ long sumCalleeInclusiveTime = sumInclusiveTimesByCallee(
+ profile, thread, ClockType.GLOBAL, TimeUnit.NANOSECONDS);
+ assertEquals(methodInclusiveTime, methodExclusiveTime + sumCalleeInclusiveTime);
+
+ if (!isTop) {
+ // Validate that the inclusive time is properly attributed to all its callers
+ long sumInclusiveTimeByCaller = sumInclusiveTimesByCaller(
+ profile, thread, ClockType.GLOBAL, TimeUnit.NANOSECONDS);
+ assertEquals(methodInclusiveTime, sumInclusiveTimeByCaller);
+
+ // Validate that exclusive time is properly attributed to all callers
+ long sumCallerExclusiveTimeByCaller = sumExclusiveTimesByCaller(
+ profile, thread, ClockType.GLOBAL, TimeUnit.NANOSECONDS);
+ assertEquals(methodExclusiveTime, sumCallerExclusiveTimeByCaller);
+
+ // Validate that the method count is correctly distributed among the callers
+ assertEquals(profile.getInvocationCount(thread), sumInvocationCountsByCaller(
+ profile,
+ thread));
+ }
+ }
+ }
+
+ private long sumInvocationCountsByCaller(MethodProfileData profile, ThreadInfo thread) {
+ long sum = 0;
+ for (Long callerId : profile.getCallers(thread)) {
+ sum += profile.getInvocationCountFromCaller(thread, callerId);
+ }
+ return sum;
+ }
+
+ private long sumInclusiveTimesByCaller(MethodProfileData profile, ThreadInfo thread,
+ ClockType type, TimeUnit unit) {
+ long sum = 0;
+ for (Long calleeId : profile.getCallers(thread)) {
+ sum += profile.getInclusiveTimeByCaller(thread, calleeId, type, unit);
+ }
+ return sum;
+ }
+
+ private long sumExclusiveTimesByCaller(MethodProfileData profile, ThreadInfo thread,
+ ClockType type, TimeUnit unit) {
+ long sum = 0;
+ for (Long calleeId : profile.getCallers(thread)) {
+ sum += profile.getExclusiveTimeByCaller(thread, calleeId, type, unit);
+ }
+ return sum;
+ }
+
+ private long sumInclusiveTimesByCallee(MethodProfileData profile, ThreadInfo thread,
+ ClockType type, TimeUnit unit) {
+ long sum = 0;
+ for (Long calleeId : profile.getCallees(thread)) {
+ sum += profile.getInclusiveTimeByCallee(thread, calleeId, type, unit);
+ }
+ return sum;
+ }
+
+
+ public void testSearch() throws IOException {
+ VmTraceData traceData = getVmTraceData("/basic.trace");
+ ThreadInfo thread = traceData.getThread("AsyncTask #1");
+
+ SearchResult results = traceData.searchFor("startMethodTracing", thread);
+
+ // 3 different methods (varying in parameter list) of name startMethodTracing are called
+ assertEquals(3, results.getMethods().size());
+ assertEquals(3, results.getInstances().size());
+ }
+
+ // Validates that search is not impacted by current locale
+ public void testSearchLocale() throws IOException {
+ VmTraceData traceData = getVmTraceData("/basic.trace");
+ ThreadInfo thread = traceData.getThread("AsyncTask #1");
+
+ String pattern = "ii)v";
+ SearchResult results = traceData.searchFor(pattern, thread);
+
+ Locale originalDefaultLocale = Locale.getDefault();
+
+ try {
+ // Turkish has two different variants for lowercase i
+ Locale.setDefault(new Locale("tr", "TR"));
+ SearchResult turkish = traceData.searchFor(pattern, thread);
+
+ assertEquals(results.getInstances().size(), turkish.getInstances().size());
+ assertEquals(results.getMethods().size(), turkish.getMethods().size());
+ } finally {
+ Locale.setDefault(originalDefaultLocale);
+ }
}
private VmTraceData getVmTraceData(String traceFilePath) throws IOException {
diff --git a/perflib/src/test/java/com/android/tools/perflib/vmtrace/viz/TraceView.java b/perflib/src/test/java/com/android/tools/perflib/vmtrace/viz/TraceView.java
new file mode 100644
index 0000000..ea4a460
--- /dev/null
+++ b/perflib/src/test/java/com/android/tools/perflib/vmtrace/viz/TraceView.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.vmtrace.viz;
+
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.fail;
+
+import com.android.tools.perflib.vmtrace.ClockType;
+import com.android.tools.perflib.vmtrace.SearchResult;
+import com.android.tools.perflib.vmtrace.ThreadInfo;
+import com.android.tools.perflib.vmtrace.VmTraceData;
+import com.android.tools.perflib.vmtrace.VmTraceParser;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.util.List;
+
+import javax.swing.*;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+
+/**
+ * This is just a simple test application that loads a particular trace file,
+ * and displays the stackchart view it within a JFrame.
+ */
+public class TraceView {
+ private static final String TRACE_FILE_NAME = "/play.dalvik.trace";
+ private static final String DEFAULT_THREAD_NAME = "main";
+
+ public static void main(String[] args) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ createAndShowUI();
+ }
+ });
+ }
+
+ private static void createAndShowUI() {
+ final TraceViewPanel traceViewPanel = new TraceViewPanel();
+ final VmTraceData traceData = getVmTraceData(TRACE_FILE_NAME);
+
+ JFrame frame = new JFrame("TraceViewTestApplication");
+ frame.setLayout(new BorderLayout());
+ frame.add(traceViewPanel, BorderLayout.CENTER);
+ frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
+ frame.pack();
+ frame.setSize(1200, 800);
+ frame.setVisible(true);
+
+ traceViewPanel.setTrace(traceData);
+ }
+
+ private static VmTraceData getVmTraceData(String tracePath) {
+ VmTraceParser parser = new VmTraceParser(getFile(tracePath));
+ try {
+ parser.parse();
+ } catch (IOException e) {
+ fail("Unexpected error while reading tracing file: " + tracePath);
+ }
+
+ return parser.getTraceData();
+ }
+
+ private static File getFile(String path) {
+ URL resource = TraceView.class.getResource(path);
+ // Note: When running from an IntelliJ, make sure the IntelliJ compiler settings treats
+ // *.trace files as resources, otherwise they are excluded from compiler output
+ // resulting in a NPE.
+ assertNotNull(path + " not found", resource);
+ return new File(resource.getFile());
+ }
+
+ public static class TraceViewPanel extends JPanel {
+ private VmTraceData mTraceData;
+
+ private final TraceViewCanvas mTraceViewCanvas;
+ private JComboBox mThreadCombo;
+ private JCheckBox mClockSelector;
+ private JCheckBox mUseInclusiveTimeForColor;
+ private JTextField mSearchField;
+ private JLabel mSearchResults;
+
+ public TraceViewPanel() {
+ setLayout(new BorderLayout());
+
+ add(createControlPanel(), BorderLayout.NORTH);
+
+ mTraceViewCanvas = new TraceViewCanvas();
+ add(mTraceViewCanvas, BorderLayout.CENTER);
+ }
+
+ private JPanel createControlPanel() {
+ JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT));
+
+ JLabel l = new JLabel("Thread: ");
+ p.add(l);
+
+ mThreadCombo = new JComboBox();
+ p.add(mThreadCombo);
+ mThreadCombo.setRenderer(new DefaultListCellRenderer() {
+ @Override
+ public Component getListCellRendererComponent(JList list, Object value, int index,
+ boolean isSelected, boolean cellHasFocus) {
+ Object v = value instanceof ThreadInfo ? ((ThreadInfo) value).getName() : value;
+ return super.getListCellRendererComponent(list, v, index, isSelected,
+ cellHasFocus);
+ }
+ });
+
+ mClockSelector = new JCheckBox("Use Wallclock Time");
+ mClockSelector.setSelected(true);
+ p.add(mClockSelector);
+
+ mUseInclusiveTimeForColor = new JCheckBox("Color by inclusive time");
+ p.add(mUseInclusiveTimeForColor);
+
+ ActionListener listener = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ assert mTraceViewCanvas != null;
+
+ if (e.getSource() == mThreadCombo) {
+ mTraceViewCanvas.displayThread((ThreadInfo) mThreadCombo.getSelectedItem());
+ } else if (e.getSource() == mClockSelector) {
+ mTraceViewCanvas.setRenderClock(mClockSelector.isSelected() ?
+ ClockType.GLOBAL : ClockType.THREAD);
+ } else if (e.getSource() == mUseInclusiveTimeForColor) {
+ mTraceViewCanvas.setUseInclusiveTimeForColorAssignment(
+ mUseInclusiveTimeForColor.isSelected());
+ }
+ }
+ };
+ mThreadCombo.addActionListener(listener);
+ mClockSelector.addActionListener(listener);
+ mUseInclusiveTimeForColor.addActionListener(listener);
+
+ l = new JLabel("Find: ");
+ p.add(l);
+
+ mSearchField = new JTextField(20);
+ p.add(mSearchField);
+ mSearchField.setEnabled(false);
+ mSearchField.getDocument().addDocumentListener(new DocumentListener() {
+ @Override
+ public void insertUpdate(DocumentEvent e) {
+ searchTextUpdated();
+ }
+
+ @Override
+ public void removeUpdate(DocumentEvent e) {
+ searchTextUpdated();
+ }
+
+ @Override
+ public void changedUpdate(DocumentEvent e) {
+ searchTextUpdated();
+ }
+
+ private void searchTextUpdated() {
+ if (mTraceData == null) {
+ return;
+ }
+
+ String pattern = getText(mSearchField.getDocument());
+ if (pattern.length() < 3) {
+ mTraceViewCanvas.setHighlightMethods(null);
+ mSearchResults.setText("");
+ return;
+ }
+
+ ThreadInfo thread = (ThreadInfo) mThreadCombo.getSelectedItem();
+ SearchResult results = mTraceData.searchFor(pattern, thread);
+ mTraceViewCanvas.setHighlightMethods(results.getMethods());
+
+ String result = String.format("%1$d methods, %2$d instances",
+ results.getMethods().size(), results.getInstances().size());
+ mSearchResults.setText(result);
+ }
+
+ private String getText(Document document) {
+ try {
+ return document.getText(0, document.getLength());
+ } catch (BadLocationException e) {
+ return "";
+ }
+ }
+ });
+
+ mSearchResults = new JLabel();
+ p.add(mSearchResults);
+
+ return p;
+ }
+
+ public void setTrace(VmTraceData traceData) {
+ mTraceData = traceData;
+
+ List<ThreadInfo> threads = traceData.getThreads(true);
+ ThreadInfo defaultThread = Iterables.find(threads, new Predicate<ThreadInfo>() {
+ @Override
+ public boolean apply(ThreadInfo input) {
+ return DEFAULT_THREAD_NAME.equals(input.getName());
+ }
+ }, threads.get(0));
+
+ mThreadCombo.setModel(new DefaultComboBoxModel(threads.toArray()));
+ mThreadCombo.setEnabled(true);
+ mSearchField.setEnabled(true);
+
+ mTraceViewCanvas.setTrace(traceData, defaultThread, ClockType.GLOBAL);
+ mThreadCombo.setSelectedItem(defaultThread);
+ }
+ }
+}
diff --git a/perflib/src/test/java/com/android/tools/perflib/vmtrace/viz/ZoomPanInteractorTest.java b/perflib/src/test/java/com/android/tools/perflib/vmtrace/viz/ZoomPanInteractorTest.java
new file mode 100644
index 0000000..9d02a5c
--- /dev/null
+++ b/perflib/src/test/java/com/android/tools/perflib/vmtrace/viz/ZoomPanInteractorTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.perflib.vmtrace.viz;
+
+import junit.framework.TestCase;
+
+import java.awt.*;
+
+public class ZoomPanInteractorTest extends TestCase {
+ private ZoomPanInteractor mZoomPanInteractor;
+ Point src = new Point();
+ Point dst = new Point();
+
+ @Override
+ protected void setUp() throws Exception {
+ mZoomPanInteractor = new ZoomPanInteractor();
+ }
+
+ public void testTranslation() {
+ mZoomPanInteractor.translateBy(10, -20);
+
+ src.setLocation(1, 2);
+ mZoomPanInteractor.getTransform().transform(src, dst);
+
+ assertEquals(11, dst.x);
+ assertEquals(-18, dst.y);
+ }
+
+ public void testScaleByOrigin() {
+ mZoomPanInteractor.zoomBy(4, 5, new Point(0, 0));
+
+ src.setLocation(2, 3);
+ mZoomPanInteractor.getTransform().transform(src, dst);
+
+ assertEquals(2 * 4, dst.x);
+ assertEquals(3 * 5, dst.y);
+ }
+
+ public void testScaleByLocation() {
+ mZoomPanInteractor.zoomBy(4, 5, new Point(20, 0));
+
+ src.setLocation(1, 5);
+ mZoomPanInteractor.getTransform().transform(src, dst);
+
+ // Zooming 4 times from 20 => origin is now at (-4 * 20 + 20) = -60
+ // So x = 1 => -60 + 1*4 = -56
+ assertEquals(-56, dst.x);
+
+ // No translation for y, just zoom
+ assertEquals(5 * 5, dst.y);
+ }
+}
diff --git a/perflib/src/test/resources/.gitignore b/perflib/src/test/resources/.gitignore
new file mode 100644
index 0000000..6f9717b
--- /dev/null
+++ b/perflib/src/test/resources/.gitignore
@@ -0,0 +1 @@
+play.dalvik.trace
diff --git a/perflib/src/test/resources/basic-api10.trace b/perflib/src/test/resources/basic-api10.trace
new file mode 100644
index 0000000..a825681
--- /dev/null
+++ b/perflib/src/test/resources/basic-api10.trace
Binary files differ
diff --git a/publish.gradle b/publish.gradle
new file mode 100644
index 0000000..7dbe36d
--- /dev/null
+++ b/publish.gradle
@@ -0,0 +1,78 @@
+apply plugin: 'maven'
+apply plugin: 'signing'
+
+task publishLocal(type: Upload) {
+ configuration = configurations.archives
+ repositories {
+ mavenDeployer {
+ repository(url: uri("$rootProject.ext.androidHostOut/repo"))
+ }
+ }
+}
+
+project.ext.sonatypeUsername = hasProperty('sonatypeUsername') ? sonatypeUsername : ""
+project.ext.sonatypePassword = hasProperty('sonatypePassword') ? sonatypePassword : ""
+
+uploadArchives {
+ repositories {
+ mavenDeployer {
+ beforeDeployment { MavenDeployment deployment ->
+ if (!project.has("release")) {
+ throw new StopExecutionException("uploadArchives must be called with the release.gradle init script")
+ }
+
+ if (project.ext.sonatypeUsername.length() == 0 || project.ext.sonatypePassword.length() == 0) {
+ throw new StopExecutionException("uploadArchives cannot be called without sonatype username and password")
+ }
+
+ signing.signPom(deployment)
+ }
+
+ repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
+ authentication(userName: project.ext.sonatypeUsername, password: project.ext.sonatypePassword)
+ }
+
+ pom.project {
+ name project.ext.pomName
+ description project.ext.pomDesc
+ url 'http://tools.android.com'
+ inceptionYear '2007'
+
+ licenses {
+ license {
+ name 'The Apache Software License, Version 2.0'
+ url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+ distribution 'repo'
+ }
+ }
+
+ scm {
+ url 'https://android.googlesource.com/platform/tools/base'
+ connection 'git://android.googlesource.com/platform/tools/base.git'
+ }
+ developers {
+ developer {
+ name 'The Android Open Source Project'
+ }
+ }
+ }
+ }
+ }
+}
+
+// custom tasks for creating source/javadoc jars
+task sourcesJar(type: Jar, dependsOn:classes) {
+ classifier = 'sources'
+ from sourceSets.main.allSource
+}
+
+// add source jar tasks as artifacts
+artifacts {
+ archives jar
+ archives sourcesJar
+}
+
+signing {
+ required { project.has("release") && gradle.taskGraph.hasTask("uploadArchives") }
+ sign configurations.archives
+}
diff --git a/rule-api/build.gradle b/rule-api/build.gradle
index ed42519..824e698 100644
--- a/rule-api/build.gradle
+++ b/rule-api/build.gradle
@@ -1,3 +1,6 @@
+apply plugin: 'java'
+apply plugin: 'distrib'
+
group = 'com.android.tools'
archivesBaseName = 'rule-api'
@@ -6,3 +9,4 @@
compile project(':layoutlib-api')
}
+apply from: '../baseVersion.gradle'
\ No newline at end of file
diff --git a/sdk-common/build.gradle b/sdk-common/build.gradle
index 39d886d..b9e0c94 100644
--- a/sdk-common/build.gradle
+++ b/sdk-common/build.gradle
@@ -1,3 +1,6 @@
+apply plugin: 'java'
+apply plugin: 'distrib'
+
group = 'com.android.tools'
archivesBaseName = 'sdk-common'
@@ -13,45 +16,10 @@
from 'NOTICE'
}
-uploadArchives {
- repositories {
- mavenDeployer {
- beforeDeployment { MavenDeployment deployment ->
- if (!project.has("release")) {
- throw new StopExecutionException("uploadArchives must be called with the release.gradle init script")
- }
+project.ext.pomName = 'Android Tools sdk-common library'
+project.ext.pomDesc = 'sdk-common library used by other Android tools libraries.'
- signing.signPom(deployment)
- }
+apply from: '../baseVersion.gradle'
+apply from: '../publish.gradle'
+apply from: '../javadoc.gradle'
- repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
- authentication(userName: project.ext.sonatypeUsername, password: project.ext.sonatypePassword)
- }
-
- pom.project {
- name 'Android Tools sdk-common library'
- description 'sdk-common library used by other Android tools libraries.'
- url 'http://tools.android.com'
- inceptionYear '2007'
-
- licenses {
- license {
- name 'The Apache Software License, Version 2.0'
- url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
- distribution 'repo'
- }
- }
-
- scm {
- url "https://android.googlesource.com/platform/tools/base"
- connection "git://android.googlesource.com/platform/tools/base.git"
- }
- developers {
- developer {
- name 'The Android Open Source Project'
- }
- }
- }
- }
- }
-}
diff --git a/sdk-common/sdk-common.iml b/sdk-common/sdk-common.iml
index 03a2b95..4f564af 100644
--- a/sdk-common/sdk-common.iml
+++ b/sdk-common/sdk-common.iml
@@ -5,7 +5,7 @@
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
- <sourceFolder url="file://$MODULE_DIR$/src/test/resources" isTestSource="true" />
+ <sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
<excludeFolder url="file://$MODULE_DIR$/.settings" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
diff --git a/sdk-common/src/main/java/com/android/ide/common/packaging/PackagingUtils.java b/sdk-common/src/main/java/com/android/ide/common/packaging/PackagingUtils.java
index 66c585f..91fc182 100644
--- a/sdk-common/src/main/java/com/android/ide/common/packaging/PackagingUtils.java
+++ b/sdk-common/src/main/java/com/android/ide/common/packaging/PackagingUtils.java
@@ -32,7 +32,6 @@
return !folderName.equalsIgnoreCase("CVS") &&
!folderName.equalsIgnoreCase(".svn") &&
!folderName.equalsIgnoreCase("SCCS") &&
- !folderName.equalsIgnoreCase("META-INF") &&
!folderName.startsWith("_");
}
@@ -62,6 +61,7 @@
return !(fileName.charAt(0) == '.' || fileName.charAt(fileName.length() - 1) == '~') &&
!"aidl".equalsIgnoreCase(extension) && // Aidl files
!"rs".equalsIgnoreCase(extension) && // RenderScript files
+ !"fs".equalsIgnoreCase(extension) && // FilterScript files
!"rsh".equalsIgnoreCase(extension) && // RenderScript header files
!"d".equalsIgnoreCase(extension) && // Dependency files
!"java".equalsIgnoreCase(extension) && // Java files
@@ -71,8 +71,8 @@
!"swp".equalsIgnoreCase(extension) && // vi swap file
!"thumbs.db".equalsIgnoreCase(fileName) && // image index file
!"picasa.ini".equalsIgnoreCase(fileName) && // image index file
+ !"about.html".equalsIgnoreCase(fileName) && // Javadoc
!"package.html".equalsIgnoreCase(fileName) && // Javadoc
!"overview.html".equalsIgnoreCase(fileName); // Javadoc
-
}
}
diff --git a/sdk-common/src/main/java/com/android/ide/common/rendering/HardwareConfigHelper.java b/sdk-common/src/main/java/com/android/ide/common/rendering/HardwareConfigHelper.java
index 35bf846..aff9780 100644
--- a/sdk-common/src/main/java/com/android/ide/common/rendering/HardwareConfigHelper.java
+++ b/sdk-common/src/main/java/com/android/ide/common/rendering/HardwareConfigHelper.java
@@ -26,6 +26,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -171,7 +172,7 @@
public static final String MANUFACTURER_GENERIC = "Generic"; //$NON-NLS-1$
private static final String NEXUS = "Nexus"; //$NON-NLS-1$
private static final Pattern GENERIC_PATTERN =
- Pattern.compile("(\\d+\\.?\\d*)in (.+?)( \\(.*Nexus.*\\))?"); //$NON-NLS-1$
+ Pattern.compile("(\\d+\\.?\\d*)\" (.+?)( \\(.*Nexus.*\\))?"); //$NON-NLS-1$
/**
* Returns a user-displayable description of the given Nexus device
@@ -181,12 +182,12 @@
*/
@NonNull
public static String getNexusLabel(@NonNull Device device) {
- String name = device.getName();
+ String name = device.getDisplayName();
Screen screen = device.getDefaultHardware().getScreen();
float length = (float) screen.getDiagonalLength();
// Round dimensions to the nearest tenth
length = Math.round(10 * length) / 10.0f;
- return String.format(java.util.Locale.US, "%1$s (%3$s\", %2$s)",
+ return String.format(Locale.US, "%1$s (%3$s\", %2$s)",
name, getResolutionString(device), Float.toString(length));
}
@@ -198,24 +199,18 @@
*/
@NonNull
public static String getGenericLabel(@NonNull Device device) {
- // * Replace "'in'" with '"' (e.g. 2.7" QVGA instead of 2.7in QVGA)
// * Use the same precision for all devices (all but one specify decimals)
// * Add some leading space such that the dot ends up roughly in the
// same space
// * Add in screen resolution and density
- String name = device.getName();
- if (name.equals("3.7 FWVGA slider")) { //$NON-NLS-1$
- // Fix metadata: this one entry doesn't have "in" like the rest of them
- name = "3.7in FWVGA slider"; //$NON-NLS-1$
- }
-
+ String name = device.getDisplayName();
Matcher matcher = GENERIC_PATTERN.matcher(name);
if (matcher.matches()) {
String size = matcher.group(1);
String n = matcher.group(2);
int dot = size.indexOf('.');
if (dot == -1) {
- size = size + ".0";
+ size += ".0";
dot = size.length() - 2;
}
for (int i = 0; i < 2 - dot; i++) {
@@ -224,7 +219,7 @@
name = size + "\" " + n;
}
- return String.format(java.util.Locale.US, "%1$s (%2$s)", name,
+ return String.format(Locale.US, "%1$s (%2$s)", name,
getResolutionString(device));
}
@@ -236,7 +231,7 @@
@NonNull
public static String getResolutionString(@NonNull Device device) {
Screen screen = device.getDefaultHardware().getScreen();
- return String.format(java.util.Locale.US,
+ return String.format(Locale.US,
"%1$d \u00D7 %2$d: %3$s", // U+00D7: Unicode multiplication sign
screen.getXDimension(),
screen.getYDimension(),
@@ -258,7 +253,7 @@
* @return true if the device is a Nexus
*/
public static boolean isNexus(@NonNull Device device) {
- return device.getName().contains(NEXUS);
+ return device.getId().contains(NEXUS);
}
/**
@@ -269,27 +264,33 @@
* @return the rank of the device
*/
public static int nexusRank(Device device) {
- String name = device.getName();
- if (name.endsWith(" One")) { //$NON-NLS-1$
+ String id = device.getId();
+ if (id.equals("Nexus One")) { //$NON-NLS-1$
return 1;
}
- if (name.endsWith(" S")) { //$NON-NLS-1$
+ if (id.equals("Nexus S")) { //$NON-NLS-1$
return 2;
}
- if (name.startsWith("Galaxy")) { //$NON-NLS-1$
+ if (id.equals("Galaxy Nexus")) { //$NON-NLS-1$
return 3;
}
- if (name.endsWith(" 7")) { //$NON-NLS-1$
- return 4;
+ if (id.equals("Nexus 7")) { //$NON-NLS-1$
+ return 4; // 2012 version
}
- if (name.endsWith(" 10")) { //$NON-NLS-1$
+ if (id.equals("Nexus 10")) { //$NON-NLS-1$
return 5;
}
- if (name.endsWith(" 4")) { //$NON-NLS-1$
+ if (id.equals("Nexus 4")) { //$NON-NLS-1$
return 6;
}
+ if (id.equals("Nexus 7 2013")) { //$NON-NLS-1$
+ return 7;
+ }
+ if (id.equals("Nexus 5")) { //$NON-NLS-1$
+ return 8;
+ }
- return 7;
+ return 100; // devices released in the future?
}
/**
diff --git a/sdk-common/src/main/java/com/android/ide/common/rendering/LayoutLibrary.java b/sdk-common/src/main/java/com/android/ide/common/rendering/LayoutLibrary.java
index a72470c..8f39cb6 100644
--- a/sdk-common/src/main/java/com/android/ide/common/rendering/LayoutLibrary.java
+++ b/sdk-common/src/main/java/com/android/ide/common/rendering/LayoutLibrary.java
@@ -43,13 +43,13 @@
import com.android.layoutlib.api.IXmlPullParser;
import com.android.resources.ResourceType;
import com.android.utils.ILogger;
+import com.android.utils.SdkUtils;
import java.awt.image.BufferedImage;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
-import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
@@ -61,10 +61,10 @@
/**
* Class to use the Layout library.
* <p/>
- * Use {@link #load(String, ILogger)} to load the jar file.
+ * Use {@link #load(String, ILogger, String)} to load the jar file.
* <p/>
* Use the layout library with:
- * {@link #init(String, Map)}, {@link #supports(Capability)}, {@link #createSession(SessionParams)},
+ * {@link #init}, {@link #supports(Capability)}, {@link #createSession(SessionParams)},
* {@link #dispose()}, {@link #clearCaches(Object)}.
*
* <p/>
@@ -79,6 +79,7 @@
public class LayoutLibrary {
public static final String CLASS_BRIDGE = "com.android.layoutlib.bridge.Bridge"; //$NON-NLS-1$
+ public static final String FN_ICU_JAR = "icu4j.jar"; //$NON-NLS-1$
/** Link to the layout bridge */
private final Bridge mBridge;
@@ -128,8 +129,8 @@
* Loads the layoutlib.jar file located at the given path and returns a {@link LayoutLibrary}
* object representing the result.
* <p/>
- * If loading failed {@link #getStatus()} will reflect this, and {@link #getBridge()} will
- * return null.
+ * If loading failed {@link #getStatus()} will reflect this, and {@link #mBridge} will
+ * be null.
*
* @param layoutLibJarOsPath the path of the jar file
* @param log an optional log file.
@@ -151,14 +152,22 @@
log.error(null, "layoutlib.jar is missing!"); //$NON-NLS-1$
}
} else {
- URI uri = f.toURI();
- URL url = uri.toURL();
+ URL[] urls;
+ // TODO: The icu jar has to be in the same location as layoutlib.jar. Get rid of
+ // this dependency.
+ File icu4j = new File(f.getParent(), FN_ICU_JAR);
+ if (icu4j.isFile()) {
+ urls = new URL[2];
+ urls[1] = SdkUtils.fileToUrl(icu4j);
+ } else {
+ urls = new URL[1];
+ }
+ urls[0] = SdkUtils.fileToUrl(f);
// create a class loader. Because this jar reference interfaces
// that are in the editors plugin, it's important to provide
// a parent class loader.
- classLoader = new URLClassLoader(
- new URL[] { url },
+ classLoader = new URLClassLoader(urls,
LayoutLibrary.class.getClassLoader());
// load the class
@@ -279,8 +288,6 @@
* read from attrs.xml in the SDK target.
* @param log a {@link LayoutLog} object. Can be null.
* @return true if success.
- *
- * @see Bridge#init(String, Map)
*/
public boolean init(Map<String, String> platformProperties,
File fontLocation,
@@ -315,7 +322,7 @@
* Before taking further actions on the scene, it is recommended to use
* {@link #supports(Capability)} to check what the scene can do.
*
- * @return a new {@link ILayoutScene} object that contains the result of the scene creation and
+ * @return a new {@link RenderSession} object that contains the result of the scene creation and
* first rendering or null if {@link #getStatus()} doesn't return {@link LoadStatus#LOADED}.
*
* @see Bridge#createSession(SessionParams)
@@ -413,6 +420,15 @@
return getViewIndexReflection(viewObject);
}
+ /**
+ * Returns true if the character orientation of the locale is right to left.
+ * @param locale The locale formatted as language-region
+ * @return true if the locale is right to left.
+ */
+ public boolean isRtl(String locale) {
+ return supports(Capability.RTL) ? mBridge.isRtl(locale) : false;
+ }
+
// ------ Implementation
private LayoutLibrary(Bridge bridge, ILayoutBridge legacyBridge, ClassLoader classLoader,
diff --git a/sdk-common/src/main/java/com/android/ide/common/rendering/RenderSecurityException.java b/sdk-common/src/main/java/com/android/ide/common/rendering/RenderSecurityException.java
new file mode 100644
index 0000000..38dcce9
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/rendering/RenderSecurityException.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.common.rendering;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+/** Exception thrown when custom view code makes an illegal code while rendering under layoutlib */
+public class RenderSecurityException extends SecurityException {
+
+ private final String myMessage;
+
+ /** Use one of the create factory methods */
+ private RenderSecurityException(@NonNull String message) {
+ super(message);
+ myMessage = message;
+ }
+
+ @Override
+ public String getMessage() {
+ return myMessage;
+ }
+
+ @Override
+ public String toString() {
+ // super prepends the fully qualified name of the exception
+ return getMessage();
+ }
+ /**
+ * Creates a new {@linkplain RenderSecurityException}
+ *
+ * @param resource the type of resource being accessed - "Thread", "Write", "Socket", etc
+ * @param context more information about the object, such as the path of the file being read
+ * @return a new exception
+ */
+ @NonNull
+ public static RenderSecurityException create(@NonNull String resource,
+ @Nullable String context) {
+ return new RenderSecurityException(computeLabel(resource, context));
+ }
+
+ /**
+ * Creates a new {@linkplain RenderSecurityException}
+ *
+ * @param message the message for the exception
+ * @return a new exception
+ */
+ @NonNull
+ public static RenderSecurityException create(@NonNull String message) {
+ return new RenderSecurityException(message);
+ }
+
+ private static String computeLabel(@NonNull String resource, @Nullable String context) {
+ StringBuilder sb = new StringBuilder(40);
+ sb.append(resource);
+ sb.append(" access not allowed during rendering");
+ if (context != null) {
+ sb.append(" (").append(context).append(")");
+ }
+ return sb.toString();
+ }
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/rendering/RenderSecurityManager.java b/sdk-common/src/main/java/com/android/ide/common/rendering/RenderSecurityManager.java
new file mode 100644
index 0000000..6f74c12
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/rendering/RenderSecurityManager.java
@@ -0,0 +1,575 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.common.rendering;
+
+import static com.android.SdkConstants.DOT_CLASS;
+import static com.android.SdkConstants.DOT_JAR;
+import static com.android.SdkConstants.VALUE_FALSE;
+
+import com.android.annotations.Nullable;
+import com.android.utils.ILogger;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FilePermission;
+import java.lang.reflect.Member;
+import java.net.InetAddress;
+import java.security.Permission;
+import java.util.PropertyPermission;
+
+/**
+ * A {@link java.lang.SecurityManager} which is used for layout lib rendering, to
+ * prevent custom views from accidentally exiting the whole IDE if they call
+ * {@code System.exit}, as well as unintentionally writing files etc.
+ * <p>
+ * The security manager only checks calls on the current thread for which it
+ * was made active with a call to {@link #setActive(boolean, Object)}, as well as any
+ * threads constructed from the render thread.
+ */
+public class RenderSecurityManager extends SecurityManager {
+ /** Property used to disable sandbox */
+ public static final String ENABLED_PROPERTY = "android.render.sandbox";
+
+ /** Whether we should restrict reading to certain paths */
+ public static final boolean RESTRICT_READS = false;
+
+ /**
+ * Whether the security manager is enabled for this session (it might still
+ * be inactive, either because it's active for a different thread, or because
+ * it has been disabled via {@link #setActive(boolean, Object)} (which sets the
+ * per-instance mEnabled flag)
+ */
+ public static boolean sEnabled =
+ !VALUE_FALSE.equals(System.getProperty(ENABLED_PROPERTY));
+
+ /**
+ * Thread local data which indicates whether the current thread is relevant for
+ * this security manager. This is an inheritable thread local such that any threads
+ * spawned from this thread will also apply the security manager; otherwise code
+ * could just create new threads and execute code separate from the security manager
+ * there.
+ */
+ private static ThreadLocal<Boolean> sIsRenderThread = new InheritableThreadLocal<Boolean>() {
+ @Override protected synchronized Boolean initialValue() {
+ return Boolean.FALSE;
+ }
+ @Override protected synchronized Boolean childValue(Boolean parentValue) {
+ return parentValue;
+ }
+ };
+
+ /** Secret which must be provided by callers wishing to deactivate the security manager */
+ private static Object sCredential;
+
+ private boolean mAllowSetSecurityManager;
+ private boolean mDisabled;
+ @SuppressWarnings("FieldCanBeLocal")
+ private String mSdkPath;
+ @SuppressWarnings("FieldCanBeLocal")
+ private String mProjectPath;
+ private String mTempDir;
+ private String mNormalizedTempDir;
+ private SecurityManager myPreviousSecurityManager;
+ private ILogger mLogger;
+
+ /**
+ * Returns the current render security manager, if any. This will only return
+ * non-null if there is an active {@linkplain RenderSecurityManager} as the
+ * current global security manager.
+ */
+ @Nullable
+ public static RenderSecurityManager getCurrent() {
+ if (sIsRenderThread.get()) {
+ SecurityManager securityManager = System.getSecurityManager();
+ if (securityManager instanceof RenderSecurityManager) {
+ RenderSecurityManager manager = (RenderSecurityManager) securityManager;
+ return manager.isRelevant() ? manager : null;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Creates a security manager suitable for controlling access to custom views
+ * being rendered by layoutlib, ensuring that they don't accidentally try to
+ * write files etc (which could corrupt data if they for example assume device
+ * paths that are not the same for the running IDE; for example, they could try
+ * to clear out their own local app storage, which in the IDE could be the
+ * user's home directory.)
+ * <p>
+ * Note: By default a security manager is not active. You need to call
+ * {@link #setActive(boolean, Object)} with true to activate it, <b>instead</b> of just calling
+ * {@link System#setSecurityManager(SecurityManager)}.
+ *
+ * @param sdkPath an optional path to the SDK install being used by layoutlib;
+ * this is used to white-list path prefixes for layoutlib resource
+ * lookup
+ * @param projectPath a path to the project directory, used for similar purposes
+ */
+ public RenderSecurityManager(
+ @Nullable String sdkPath,
+ @Nullable String projectPath) {
+ mSdkPath = sdkPath;
+ mProjectPath = projectPath;
+ mTempDir = System.getProperty("java.io.tmpdir");
+ mNormalizedTempDir = new File(mTempDir).getPath(); // will call fs.normalize() on the path
+ }
+
+ /** Sets an optional logger. Returns this for constructor chaining. */
+ public RenderSecurityManager setLogger(@Nullable ILogger logger) {
+ mLogger = logger;
+ return this;
+ }
+
+ /**
+ * Sets whether the {@linkplain RenderSecurityManager} is active or not.
+ * If it is being set as active, the passed in credential is remembered
+ * and anyone wishing to turn off the security manager must provide the
+ * same credential.
+ *
+ * @param active whether to turn on or off the security manager
+ * @param credential when turning off the security manager, the exact same
+ * credential passed in to the earlier activation call
+ */
+ public void setActive(boolean active, @Nullable Object credential) {
+ SecurityManager current = System.getSecurityManager();
+ boolean isActive = current == this;
+ if (active == isActive) {
+ return;
+ }
+
+ if (active) {
+ // Enable
+ assert !(current instanceof RenderSecurityManager);
+ myPreviousSecurityManager = current;
+ sIsRenderThread.set(true);
+ mDisabled = false;
+ System.setSecurityManager(this);
+ //noinspection AssignmentToStaticFieldFromInstanceMethod
+ sCredential = credential;
+ } else {
+ if (credential != sCredential) {
+ throw RenderSecurityException.create("Invalid credential");
+ }
+
+ // Disable
+ mAllowSetSecurityManager = true;
+ // Don't set mDisabled and clear sInRenderThread yet: the call
+ // to revert to the previous security manager below will trigger
+ // a check permission, and in that code we need to distinguish between
+ // this call (isRelevant() should return true) and other threads calling
+ // it outside the scope of the security manager
+ try {
+ // Only reset the security manager if it hasn't already been set to
+ // something else. If other threads try to do the same thing we could have
+ // a problem; if they sampled the render security manager while it was globally
+ // active, replaced it with their own, and sometime in the future try to
+ // set it back, it will be active when we didn't intend for it to be. That's
+ // why there is also the {@code mDisabled} flag, used to ignore any requests
+ // later on.
+ if (current instanceof RenderSecurityManager) {
+ System.setSecurityManager(myPreviousSecurityManager);
+ } else if (mLogger != null) {
+ sIsRenderThread.set(false);
+ mLogger.warning("Security manager was changed behind the scenes: ", current);
+ }
+ } finally {
+ mDisabled = true;
+ mAllowSetSecurityManager = false;
+ sIsRenderThread.set(false);
+ }
+ }
+ }
+
+ private boolean isRelevant() {
+ return sEnabled && !mDisabled && sIsRenderThread.get();
+ }
+
+ /**
+ * Disposes the security manager. An alias for calling {@link #setActive} with
+ * false.
+ *
+ * @param credential the sandbox credential initially passed to
+ * {@link #setActive(boolean, Object)}
+ */
+ public void dispose(@Nullable Object credential) {
+ setActive(false, credential);
+ }
+
+ /**
+ * Enters a code region where the sandbox is not needed
+ *
+ * @param credential a credential which proves that the caller has the right to do this
+ * @return a token which should be passed back to {@link #exitSafeRegion(boolean)}
+ */
+ public static boolean enterSafeRegion(@Nullable Object credential) {
+ boolean token = sEnabled;
+ if (credential == sCredential) {
+ sEnabled = false;
+ }
+ return token;
+ }
+
+ /**
+ * Exits a code region where the sandbox was not needed
+ *
+ * @param token the token which was returned back from the paired
+ * {@link #enterSafeRegion(Object)} call
+ */
+ public static void exitSafeRegion(boolean token) {
+ sEnabled = token;
+ }
+
+ // Permitted by custom views: access any package or member, read properties
+
+ @Override
+ public void checkPackageAccess(String pkg) {
+ }
+
+ @Override
+ public void checkMemberAccess(Class<?> clazz, int which) {
+ if (which == Member.DECLARED && isRelevant() &&
+ "com.android.ide.common.rendering.RenderSecurityManager".equals(clazz.getName())) {
+ throw RenderSecurityException.create("Reflection", clazz.getName());
+ }
+ }
+
+ @Override
+ public void checkPropertyAccess(String property) {
+ }
+
+ @Override
+ public void checkLink(String lib) {
+ // Allow linking with relative paths
+ // Needed to for example load the "fontmanager" library from layout lib (from the
+ // BiDiRenderer's layoutGlyphVector call
+ if (isRelevant() && (lib.indexOf('/') != -1 || lib.indexOf('\\') != -1)) {
+ if (lib.startsWith(System.getProperty("java.home"))) {
+ return; // Allow loading JRE libraries
+ }
+ throw RenderSecurityException.create("Link", lib);
+ }
+ }
+
+ @Override
+ public void checkCreateClassLoader() {
+ // TODO: Layoutlib makes heavy use of this, so we can't block it yet.
+ // To fix this we should make a local class loader, passed to layoutlib, which
+ // knows how to reset the security manager
+ }
+
+ //------------------------------------------------------------------------------------------
+ // Reading is permitted for certain files only
+ //------------------------------------------------------------------------------------------
+
+ @SuppressWarnings({"PointlessBooleanExpression", "ConstantConditions"})
+ @Override
+ public void checkRead(String file) {
+ if (RESTRICT_READS && isRelevant() && !isReadingAllowed(file)) {
+ throw RenderSecurityException.create("Read", file);
+ }
+ }
+
+ @SuppressWarnings({"PointlessBooleanExpression", "ConstantConditions"})
+ @Override
+ public void checkRead(String file, Object context) {
+ if (RESTRICT_READS && isRelevant() && !isReadingAllowed(file)) {
+ throw RenderSecurityException.create("Read", file);
+ }
+ }
+
+ private boolean isReadingAllowed(String path) {
+ if (RESTRICT_READS) {
+ // Allow reading files in the SDK install (fonts etc)
+ if (mSdkPath != null && path.startsWith(mSdkPath)) {
+ return true;
+ }
+
+ // Allowing reading resources in the project, such as icons
+ if (mProjectPath != null && path.startsWith(mProjectPath)) {
+ return true;
+ }
+
+ if (path.startsWith("#") && path.indexOf(File.separatorChar) == -1) {
+ // It's really layoutlib's ResourceHelper.getColorStateList which calls isFile()
+ // on values to see if it's a file or a color.
+ return true;
+ }
+
+ // Needed by layoutlib's class loader. Note that we've locked down the ability to
+ // create new class loaders.
+ if (path.endsWith(DOT_CLASS) || path.endsWith(DOT_JAR)) {
+ return true;
+ }
+
+ // Allow reading files in temp
+ if (path.startsWith(mTempDir) || path.startsWith(mNormalizedTempDir)) {
+ return true;
+ }
+
+ String javaHome = System.getProperty("java.home");
+ if (path.startsWith(javaHome)) { // Allow JDK to load its own classes
+ return true;
+ } else if (javaHome.endsWith("/Contents/Home")) {
+ // On Mac, Home lives two directory levels down from the real home, and we
+ // sometimes need to read from sibling directories (e.g. ../Libraries/ etc)
+ if (path.regionMatches(0, javaHome, 0, javaHome.length() -
+ "Contents/Home".length())) {
+ return true;
+ }
+ }
+
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ @SuppressWarnings("RedundantIfStatement")
+ private boolean isWritingAllowed(String path) {
+ if (path.startsWith(mTempDir) || path.startsWith(mNormalizedTempDir)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ @SuppressWarnings({"SpellCheckingInspection", "RedundantIfStatement"})
+ private static boolean isPropertyWriteAllowed(String name) {
+ // Linux sets this on fontmanager load; allow it since even if code points
+ // to their own classes they don't get additional privileges, it's just like
+ // using reflection
+ if (name.equals("sun.font.fontmanager")) {
+ return true;
+ }
+
+ // Toolkit initializations
+ if (name.startsWith("sun.awt.") || name.startsWith("apple.awt.")) {
+ return true;
+ }
+
+ return false;
+ }
+
+ //------------------------------------------------------------------------------------------
+ // Not permitted:
+ //------------------------------------------------------------------------------------------
+
+ @Override
+ public void checkExit(int status) {
+ // Probably not intentional in a custom view; would take down the whole IDE!
+ if (isRelevant()) {
+ throw RenderSecurityException.create("Exit", String.valueOf(status));
+ }
+
+ super.checkExit(status);
+ }
+
+ @Override
+ public void checkPropertiesAccess() {
+ if (isRelevant()) {
+ throw RenderSecurityException.create("Property", null);
+ }
+ }
+
+ // Prevent code execution/linking/loading
+
+ @Override
+ public void checkPackageDefinition(String pkg) {
+ if (isRelevant()) {
+ throw RenderSecurityException.create("Package", pkg);
+ }
+ }
+
+ @Override
+ public void checkExec(String cmd) {
+ if (isRelevant()) {
+ throw RenderSecurityException.create("Exec", cmd);
+ }
+ }
+
+ // Prevent network access
+
+ @Override
+ public void checkConnect(String host, int port) {
+ if (isRelevant()) {
+ throw RenderSecurityException.create("Socket", host + ":" + port);
+ }
+ }
+
+ @Override
+ public void checkConnect(String host, int port, Object context) {
+ if (isRelevant()) {
+ throw RenderSecurityException.create("Socket", host + ":" + port);
+ }
+ }
+
+ @Override
+ public void checkListen(int port) {
+ if (isRelevant()) {
+ throw RenderSecurityException.create("Socket", "port " + port);
+ }
+ }
+
+ @Override
+ public void checkAccept(String host, int port) {
+ if (isRelevant()) {
+ throw RenderSecurityException.create("Socket", host + ":" + port);
+ }
+ }
+
+ @Override
+ public void checkSetFactory() {
+ if (isRelevant()) {
+ throw RenderSecurityException.create("Socket", null);
+ }
+ }
+
+ @Override
+ public void checkMulticast(InetAddress inetAddress) {
+ if (isRelevant()) {
+ throw RenderSecurityException.create("Socket", inetAddress.getCanonicalHostName());
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public void checkMulticast(InetAddress inetAddress, byte ttl) {
+ if (isRelevant()) {
+ throw RenderSecurityException.create("Socket", inetAddress.getCanonicalHostName());
+ }
+ }
+
+ // Prevent file access
+
+ @Override
+ public void checkDelete(String file) {
+ if (isRelevant()) {
+ // Allow writing to temp
+ if (isWritingAllowed(file)) {
+ return;
+ }
+
+ throw RenderSecurityException.create("Delete", file);
+ }
+ }
+
+ @Override
+ public void checkAwtEventQueueAccess() {
+ if (isRelevant()) {
+ throw RenderSecurityException.create("Event", null);
+ }
+ }
+
+ // Prevent writes
+
+ @Override
+ public void checkWrite(FileDescriptor fileDescriptor) {
+ if (isRelevant()) {
+ throw RenderSecurityException.create("Write", fileDescriptor.toString());
+ }
+ }
+
+ @Override
+ public void checkWrite(String file) {
+ if (isRelevant()) {
+ if (isWritingAllowed(file)) {
+ return;
+ }
+
+ throw RenderSecurityException.create("Write", file);
+ }
+ }
+
+ // Misc
+
+ @Override
+ public void checkPrintJobAccess() {
+ if (isRelevant()) {
+ throw RenderSecurityException.create("Print", null);
+ }
+ }
+
+ @Override
+ public void checkSystemClipboardAccess() {
+ if (isRelevant()) {
+ throw RenderSecurityException.create("Clipboard", null);
+ }
+ }
+
+ @Override
+ public boolean checkTopLevelWindow(Object context) {
+ if (isRelevant()) {
+ throw RenderSecurityException.create("Window", null);
+ }
+ return false;
+ }
+
+ @Override
+ public void checkAccess(Thread thread) {
+ // Turns out layoutlib sometimes creates asynchronous calls, for example
+ // java.lang.Thread.<init>(Thread.java:521)
+ // at android.os.AsyncTask$1.newThread(AsyncTask.java:189)
+ // at java.util.concurrent.ThreadPoolExecutor.addThread(ThreadPoolExecutor.java:670)
+ // at java.util.concurrent.ThreadPoolExecutor.addIfUnderCorePoolSize(ThreadPoolExecutor.java:706)
+ // at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:650)
+ // at android.os.AsyncTask$SerialExecutor.scheduleNext(AsyncTask.java:244)
+ // at android.os.AsyncTask$SerialExecutor.execute(AsyncTask.java:238)
+ // at android.os.AsyncTask.execute(AsyncTask.java:604)
+ // at android.widget.TextView.updateTextServicesLocaleAsync(TextView.java:8078)
+
+ // This may not work correctly for render sessions, which are treated as synchronous
+ // by callers. We should re-enable these checks to chase down these calls and
+ // eliminate them from layoutlib, but until we do, it's necessary to allow thread
+ // creation.
+ }
+
+ @Override
+ public void checkAccess(ThreadGroup threadGroup) {
+ // See checkAccess(Thread)
+ }
+
+ @Override
+ public void checkPermission(Permission permission) {
+ String name = permission.getName();
+ if ("setSecurityManager".equals(name)) {
+ if (isRelevant()) {
+ if (!mAllowSetSecurityManager) {
+ throw RenderSecurityException.create("Security", null);
+ }
+ } else if (mLogger != null) {
+ mLogger.warning("RenderSecurityManager being replaced by another thread");
+ }
+ } else if (isRelevant()) {
+ String actions = permission.getActions();
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ if (RESTRICT_READS && "read".equals(actions)) {
+ if (!isReadingAllowed(name)) {
+ throw RenderSecurityException.create("Read", name);
+ }
+ } else if (!actions.isEmpty() && !actions.equals("read")) {
+ // write, execute, delete, readlink
+ if (!(permission instanceof FilePermission) || !isWritingAllowed(name)) {
+ if (permission instanceof PropertyPermission && isPropertyWriteAllowed(name)) {
+ return;
+ }
+ throw RenderSecurityException.create("Write", name);
+ }
+ }
+ }
+ }
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/repository/GradleCoordinate.java b/sdk-common/src/main/java/com/android/ide/common/repository/GradleCoordinate.java
new file mode 100644
index 0000000..ae74be4
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/repository/GradleCoordinate.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.common.repository;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This class represents a maven coordinate and allows for comparison at any level.
+ */
+public class GradleCoordinate implements Comparable<GradleCoordinate> {
+
+ /**
+ * Maven coordinates take the following form: groupId:artifactId:packaging:classifier:version
+ * where
+ * groupId is dot-notated alphanumeric
+ * artifactId is the name of the project
+ * packaging is optional and is jar/war/pom/aar/etc
+ * classifier is optional and provides filtering context
+ * version uniquely identifies a version.
+ *
+ * We only care about coordinates of the following form: groupId:artifactId:revision
+ * where revision is a series of '.' separated numbers optionally terminated by a '+' character.
+ */
+
+ /**
+ * List taken from <a href="http://maven.apache.org/pom.html#Maven_Coordinates">http://maven.apache.org/pom.html#Maven_Coordinates</a>
+ */
+ public enum ArtifactType {
+ POM("pom"),
+ JAR("jar"),
+ MAVEN_PLUGIN("maven-plugin"),
+ EJB("ejb"),
+ WAR("war"),
+ EAR("ear"),
+ RAR("rar"),
+ PAR("par"),
+ AAR("aar");
+
+ private final String myId;
+
+ ArtifactType(String id) {
+ myId = id;
+ }
+
+ @Nullable
+ public static ArtifactType getArtifactType(@Nullable String name) {
+ if (name != null) {
+ for (ArtifactType type : ArtifactType.values()) {
+ if (type.myId.equalsIgnoreCase(name)) {
+ return type;
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return myId;
+ }
+ }
+
+ public static final int PLUS_REV = -1;
+
+ private final String myGroupId;
+ private final String myArtifactId;
+ private final ArtifactType myArtifactType;
+
+ private final List<Integer> myRevisions = new ArrayList<Integer>(3);
+ private final boolean myIsAnyRevision;
+
+ private static final Pattern MAVEN_PATTERN = Pattern.compile("([\\w\\d\\.-]+):([\\w\\d\\.-]+):([\\d+\\.\\+]+)(@[\\w-]+)?");
+ private static final Pattern REVISION_PATTERN = Pattern.compile("(\\d+|\\+)");
+
+ /**
+ * Constructor
+ * @param groupId
+ * @param artifactId
+ * @param revisions
+ */
+ public GradleCoordinate(@NonNull String groupId, @NonNull String artifactId, @NonNull Integer... revisions) {
+ this(groupId, artifactId, Arrays.asList(revisions), null);
+ }
+
+ /**
+ * Constructor
+ * @param groupId
+ * @param artifactId
+ * @param revisions
+ */
+ public GradleCoordinate(@NonNull String groupId, @NonNull String artifactId, @NonNull List<Integer> revisions, @Nullable ArtifactType type) {
+ myGroupId = groupId;
+ myArtifactId = artifactId;
+ myRevisions.addAll(revisions);
+
+ // If the major revision is "+" then we'll accept any revision
+ myIsAnyRevision = (!myRevisions.isEmpty() && myRevisions.get(0) == PLUS_REV);
+
+ myArtifactType = type;
+ }
+
+ /**
+ * Create a GradleCoordinate from a string of the form groupId:artifactId:MajorRevision.MinorRevision.(MicroRevision|+)
+ * @param coordinateString the string to parse
+ * @return a coordinate object or null if the given string was malformed.
+ */
+ @Nullable
+ public static GradleCoordinate parseCoordinateString(@NonNull String coordinateString) {
+ if (coordinateString == null) {
+ return null;
+ }
+
+ Matcher matcher = MAVEN_PATTERN.matcher(coordinateString);
+ if (!matcher.matches()) {
+ return null;
+ }
+
+ String groupId = matcher.group(1);
+ String artifactId = matcher.group(2);
+ String revision = matcher.group(3);
+ String typeString = matcher.group(4);
+ ArtifactType type = null;
+
+ if (typeString != null) {
+ // Strip off the '@' symbol and try to convert
+ type = ArtifactType.getArtifactType(typeString.substring(1));
+ }
+
+ matcher = REVISION_PATTERN.matcher(revision);
+
+ List<Integer> revisions = new ArrayList<Integer>(matcher.groupCount());
+
+ while (matcher.find()) {
+ String group = matcher.group();
+ revisions.add(group.equals("+") ? PLUS_REV : Integer.parseInt(group));
+ // A plus revision terminates the revision string
+ if (group.equals("+")) {
+ break;
+ }
+ }
+
+ return new GradleCoordinate(groupId, artifactId, revisions, type);
+ }
+
+ @Override
+ public String toString() {
+ String s = String.format(Locale.US, "%s:%s:%s", myGroupId, myArtifactId, getFullRevision());
+ if (myArtifactType != null) {
+ s += "@" + myArtifactType.toString();
+ }
+ return s;
+ }
+
+ @Nullable
+ public String getGroupId() {
+ return myGroupId;
+ }
+
+ @Nullable
+ public String getArtifactId() {
+ return myArtifactId;
+ }
+
+ @Nullable
+ public String getId() {
+ if (myGroupId == null || myArtifactId == null) {
+ return null;
+ }
+
+ return String.format("%s:%s", myGroupId, myArtifactId);
+ }
+
+ @Nullable
+ public ArtifactType getType() {
+ return myArtifactType;
+ }
+
+ public boolean acceptsGreaterRevisions() {
+ return myRevisions.get(myRevisions.size() - 1) == PLUS_REV;
+ }
+
+ public String getFullRevision() {
+ StringBuilder revision = new StringBuilder();
+ for (int i : myRevisions) {
+ if (revision.length() > 0) {
+ revision.append('.');
+ }
+ revision.append((i == PLUS_REV) ? "+" : i);
+ }
+
+ return revision.toString();
+ }
+
+ /**
+ * Returns true if and only if the given coordinate refers to the same group and artifact.
+ * @param o the coordinate to compare with
+ * @return true iff the other group and artifact match the group and artifact of this coordinate.
+ */
+ public boolean isSameArtifact(@NonNull GradleCoordinate o) {
+ return o.myGroupId.equals(myGroupId) && o.myArtifactId.equals(myArtifactId);
+ }
+
+ @Override
+ public boolean equals(@NonNull Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ GradleCoordinate that = (GradleCoordinate)o;
+
+ if (!myRevisions.equals(that.myRevisions)) return false;
+ if (!myArtifactId.equals(that.myArtifactId)) return false;
+ if (!myGroupId.equals(that.myGroupId)) return false;
+ if ((myArtifactType == null) != (that.myArtifactType == null)) return false;
+ if (myArtifactType != null && !myArtifactType.equals(that.myArtifactType)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = myGroupId.hashCode();
+ result = 31 * result + myArtifactId.hashCode();
+ for (Integer i : myRevisions) {
+ result = 31 * result + i;
+ }
+ if (myArtifactType != null) {
+ result = 31 * result + myArtifactType.hashCode();
+ }
+ return result;
+ }
+
+ @Override
+ public int compareTo(@NonNull GradleCoordinate that) {
+ // Make sure we're comparing apples to apples. If not, compare artifactIds
+ if (!this.isSameArtifact(that)) {
+ return this.myArtifactId.compareTo(that.myArtifactId);
+ }
+
+ // Specific version should beat "any version"
+ if (myIsAnyRevision) {
+ return -1;
+ } else if (that.myIsAnyRevision) {
+ return 1;
+ }
+
+ for (int i = 0; i < myRevisions.size(); ++i) {
+ int delta = myRevisions.get(i) - that.myRevisions.get(i);
+ if (delta != 0) {
+ return delta;
+ }
+ }
+ return 0;
+ }
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/res2/AbstractResourceRepository.java b/sdk-common/src/main/java/com/android/ide/common/res2/AbstractResourceRepository.java
index f222d0e..d27dfff 100644
--- a/sdk-common/src/main/java/com/android/ide/common/res2/AbstractResourceRepository.java
+++ b/sdk-common/src/main/java/com/android/ide/common/res2/AbstractResourceRepository.java
@@ -23,7 +23,6 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
-import com.android.annotations.VisibleForTesting;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.resources.ResourceType;
@@ -102,7 +101,6 @@
}
@NonNull
- @VisibleForTesting
public Map<ResourceType, ListMultimap<String, ResourceItem>> getItems() {
return getMap();
}
@@ -370,7 +368,6 @@
private void addItem(@NonNull ResourceItem item) {
synchronized (ITEM_MAP_LOCK) {
- Map<ResourceType, ListMultimap<String, ResourceItem>> itemMap = getMap();
ListMultimap<String, ResourceItem> map = getMap(item.getType());
if (!map.containsValue(item)) {
map.put(item.getName(), item);
diff --git a/sdk-common/src/main/java/com/android/ide/common/res2/AssetSet.java b/sdk-common/src/main/java/com/android/ide/common/res2/AssetSet.java
index a442b79..e5f6780 100644
--- a/sdk-common/src/main/java/com/android/ide/common/res2/AssetSet.java
+++ b/sdk-common/src/main/java/com/android/ide/common/res2/AssetSet.java
@@ -24,7 +24,6 @@
import org.w3c.dom.Node;
import java.io.File;
-import java.io.IOException;
/**
* Represents a set of Assets.
@@ -86,11 +85,12 @@
@Override
protected void readSourceFolder(File sourceFolder, ILogger logger)
- throws DuplicateDataException, IOException {
+ throws MergingException {
readFiles(sourceFolder, sourceFolder, logger);
}
- private void readFiles(File sourceFolder, File folder, ILogger logger) throws IOException {
+ private void readFiles(File sourceFolder, File folder, ILogger logger)
+ throws MergingException {
File[] files = folder.listFiles();
if (files != null && files.length > 0) {
for (File file : files) {
diff --git a/sdk-common/src/main/java/com/android/ide/common/res2/DataFile.java b/sdk-common/src/main/java/com/android/ide/common/res2/DataFile.java
index 29f8715..ad3a8b8 100644
--- a/sdk-common/src/main/java/com/android/ide/common/res2/DataFile.java
+++ b/sdk-common/src/main/java/com/android/ide/common/res2/DataFile.java
@@ -29,7 +29,7 @@
/**
* Represents a data file.
*
- * It contains a link to its {@link java.io.File}, and the {@DataItem}s it generates.
+ * It contains a link to its {@link java.io.File}, and the {@link DataItem}s it generates.
*
*/
public abstract class DataFile<I extends DataItem> {
diff --git a/sdk-common/src/main/java/com/android/ide/common/res2/DataMerger.java b/sdk-common/src/main/java/com/android/ide/common/res2/DataMerger.java
index 05a52fa..e9ac33f 100755
--- a/sdk-common/src/main/java/com/android/ide/common/res2/DataMerger.java
+++ b/sdk-common/src/main/java/com/android/ide/common/res2/DataMerger.java
@@ -32,11 +32,11 @@
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
@@ -143,12 +143,12 @@
* @param consumer the consumer of the merge.
* @param doCleanUp clean up the state to be able to do further incremental merges. If this
* is a one-shot merge, this can be false to improve performance.
- * @throws java.io.IOException
- * @throws DuplicateDataException
- * @throws MergeConsumer.ConsumerException
+
+ * @throws MergingException such as a DuplicateDataException or a
+ * MergeConsumer.ConsumerException if something goes wrong
*/
public void mergeData(@NonNull MergeConsumer<I> consumer, boolean doCleanUp)
- throws IOException, DuplicateDataException, MergeConsumer.ConsumerException {
+ throws MergingException {
consumer.start();
@@ -263,12 +263,12 @@
* @param blobRootFolder the root folder where blobs are store.
* @param consumer the merge consumer that was used by the merge.
*
- * @throws IOException
+ * @throws MergingException if something goes wrong
*
* @see #loadFromBlob(File, boolean)
*/
public void writeBlobTo(@NonNull File blobRootFolder, @NonNull MergeConsumer<I> consumer)
- throws IOException {
+ throws MergingException {
// write "compact" blob
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
@@ -290,12 +290,21 @@
dataSet.appendToXml(dataSetNode, document, consumer);
}
- String content = XmlPrettyPrinter.prettyPrint(document);
+ String content = XmlPrettyPrinter.prettyPrint(document, true);
- createDir(blobRootFolder);
- Files.write(content, new File(blobRootFolder, FN_MERGER_XML), Charsets.UTF_8);
+ try {
+ createDir(blobRootFolder);
+ } catch (IOException ioe) {
+ throw new MergingException(ioe).setFile(blobRootFolder);
+ }
+ File file = new File(blobRootFolder, FN_MERGER_XML);
+ try {
+ Files.write(content, file, Charsets.UTF_8);
+ } catch (IOException ioe) {
+ throw new MergingException(ioe).setFile(file);
+ }
} catch (ParserConfigurationException e) {
- throw new IOException(e);
+ throw new MergingException(e);
}
}
@@ -315,12 +324,12 @@
* @param blobRootFolder the folder containing the blob.
* @param incrementalState whether to load into an incremental state or a new state.
* @return true if the blob was loaded.
- * @throws IOException
+ * @throws MergingException if something goes wrong
*
* @see #writeBlobTo(File, MergeConsumer)
*/
public boolean loadFromBlob(@NonNull File blobRootFolder, boolean incrementalState)
- throws IOException {
+ throws MergingException {
File file = new File(blobRootFolder, FN_MERGER_XML);
if (!file.isFile()) {
return false;
@@ -366,12 +375,21 @@
}
return true;
- } catch (FileNotFoundException e) {
- throw new IOException(e);
+ } catch (SAXParseException e) {
+ MergingException exception = new MergingException(e);
+ exception.setFile(file);
+ int lineNumber = e.getLineNumber();
+ if (lineNumber != -1) {
+ exception.setLine(lineNumber - 1); // make line numbers 0-based
+ exception.setColumn(e.getColumnNumber() - 1);
+ }
+ throw exception;
+ } catch (IOException e) {
+ throw new MergingException(e).setFile(file);
} catch (ParserConfigurationException e) {
- throw new IOException(e);
+ throw new MergingException(e).setFile(file);
} catch (SAXException e) {
- throw new IOException(e);
+ throw new MergingException(e).setFile(file);
} finally {
try {
if (stream != null) {
diff --git a/sdk-common/src/main/java/com/android/ide/common/res2/DataSet.java b/sdk-common/src/main/java/com/android/ide/common/res2/DataSet.java
index 63f704f..a49835c 100755
--- a/sdk-common/src/main/java/com/android/ide/common/res2/DataSet.java
+++ b/sdk-common/src/main/java/com/android/ide/common/res2/DataSet.java
@@ -30,7 +30,6 @@
import org.w3c.dom.NodeList;
import java.io.File;
-import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
@@ -100,7 +99,7 @@
/**
* Creates a DataFile and associated DataItems from an XML node from a file created with
- * {@link DataSet##appendToXml(org.w3c.dom.Node, org.w3c.dom.Document)}
+ * {@link DataSet#appendToXml(org.w3c.dom.Node, org.w3c.dom.Document, MergeConsumer)}
*
* @param file the file represented by the DataFile
* @param fileNode the XML node.
@@ -116,15 +115,14 @@
*
* @param sourceFolder the source folder to load the resources from.
*
- * @throws DuplicateDataException
- * @throws IOException
+ * @throws MergingException if something goes wrong
*/
protected abstract void readSourceFolder(File sourceFolder, ILogger logger)
- throws DuplicateDataException, IOException;
+ throws MergingException;
@Nullable
protected abstract F createFileAndItems(File sourceFolder, File file, ILogger logger)
- throws IOException;
+ throws MergingException;
/**
* Adds a collection of source files.
@@ -226,10 +224,9 @@
*
* This also checks for duplicates items.
*
- * @throws DuplicateDataException
- * @throws IOException
+ * @throws MergingException if something goes wrong
*/
- public void loadFromFiles(ILogger logger) throws DuplicateDataException, IOException {
+ public void loadFromFiles(ILogger logger) throws MergingException {
for (File file : mSourceFiles) {
if (file.isDirectory()) {
readSourceFolder(file, logger);
@@ -393,10 +390,11 @@
* @param changedFile The changed file
* @param fileStatus the change state
* @return true if the set was properly updated, false otherwise
+ * @throws MergingException if something goes wrong
*/
public boolean updateWith(File sourceFolder, File changedFile, FileStatus fileStatus,
ILogger logger)
- throws IOException {
+ throws MergingException {
switch (fileStatus) {
case NEW:
return handleNewFile(sourceFolder, changedFile, logger);
@@ -420,7 +418,7 @@
}
protected boolean handleNewFile(File sourceFolder, File file, ILogger logger)
- throws IOException {
+ throws MergingException {
F dataFile = createFileAndItems(sourceFolder, file, logger);
if (dataFile != null) {
processNewDataFile(sourceFolder, dataFile, true /*setTouched*/);
@@ -444,7 +442,7 @@
}
protected boolean handleChangedFile(@NonNull File sourceFolder,
- @NonNull File changedFile) throws IOException {
+ @NonNull File changedFile) throws MergingException {
F dataFile = mDataFileMap.get(changedFile);
dataFile.getItem().setTouched();
return true;
diff --git a/sdk-common/src/main/java/com/android/ide/common/res2/DuplicateDataException.java b/sdk-common/src/main/java/com/android/ide/common/res2/DuplicateDataException.java
index 3e28d8f..9b50b76 100644
--- a/sdk-common/src/main/java/com/android/ide/common/res2/DuplicateDataException.java
+++ b/sdk-common/src/main/java/com/android/ide/common/res2/DuplicateDataException.java
@@ -16,20 +16,23 @@
package com.android.ide.common.res2;
+import com.android.annotations.NonNull;
+
/**
* Exception when a {@link DataItem} is declared more than once in a {@link DataSet}
*/
-public class DuplicateDataException extends Exception {
+public class DuplicateDataException extends MergingException {
private DataItem mOne;
private DataItem mTwo;
- DuplicateDataException(DataItem one, DataItem two) {
+ DuplicateDataException(@NonNull DataItem one, @NonNull DataItem two) {
super(String.format("Duplicate resources: %1s:%2s, %3s:%4s",
one.getSource().getFile().getAbsolutePath(), one.getKey(),
two.getSource().getFile().getAbsolutePath(), two.getKey()));
mOne = one;
mTwo = two;
+ setFile(one.getSource().getFile());
}
public DataItem getOne() {
diff --git a/sdk-common/src/main/java/com/android/ide/common/res2/MergeConsumer.java b/sdk-common/src/main/java/com/android/ide/common/res2/MergeConsumer.java
index 065f971..deba96d 100644
--- a/sdk-common/src/main/java/com/android/ide/common/res2/MergeConsumer.java
+++ b/sdk-common/src/main/java/com/android/ide/common/res2/MergeConsumer.java
@@ -19,6 +19,8 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import java.io.File;
+
/**
* A consumer of merges. Used with {@link DataMerger#mergeData(MergeConsumer, boolean)}.
*/
@@ -28,7 +30,7 @@
* An exception thrown during by the consumer. It always contains the original exception
* as its cause.
*/
- public static class ConsumerException extends Exception {
+ public static class ConsumerException extends MergingException {
public ConsumerException(Throwable cause) {
super(cause);
}
diff --git a/sdk-common/src/main/java/com/android/ide/common/res2/MergeWriter.java b/sdk-common/src/main/java/com/android/ide/common/res2/MergeWriter.java
index 0b7cca8..f3a82f0 100644
--- a/sdk-common/src/main/java/com/android/ide/common/res2/MergeWriter.java
+++ b/sdk-common/src/main/java/com/android/ide/common/res2/MergeWriter.java
@@ -47,6 +47,8 @@
postWriteAction();
getExecutor().waitForTasksWithQuickFail(true);
+ } catch (ConsumerException e) {
+ throw e;
} catch (Exception e) {
throw new ConsumerException(e);
}
diff --git a/sdk-common/src/main/java/com/android/ide/common/res2/MergedResourceWriter.java b/sdk-common/src/main/java/com/android/ide/common/res2/MergedResourceWriter.java
index 21a0504..092d169 100644
--- a/sdk-common/src/main/java/com/android/ide/common/res2/MergedResourceWriter.java
+++ b/sdk-common/src/main/java/com/android/ide/common/res2/MergedResourceWriter.java
@@ -19,30 +19,29 @@
import static com.android.SdkConstants.DOT_PNG;
import static com.android.SdkConstants.DOT_XML;
import static com.android.SdkConstants.RES_QUALIFIER_SEP;
+import static com.android.SdkConstants.TAG_EAT_COMMENT;
import static com.android.SdkConstants.TAG_RESOURCES;
+import static com.android.utils.SdkUtils.createPathComment;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.internal.AaptRunner;
import com.android.ide.common.xml.XmlPrettyPrinter;
import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.utils.SdkUtils;
import com.android.utils.XmlUtils;
import com.google.common.base.Charsets;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Sets;
-import com.google.common.io.ByteStreams;
-import com.google.common.io.Closeables;
import com.google.common.io.Files;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.InputStream;
import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -63,6 +62,8 @@
@Nullable
private final AaptRunner mAaptRunner;
+ private boolean mInsertSourceMarkers = true;
+
/**
* map of XML values files to write after parsing all the files. the key is the qualifier.
*/
@@ -80,6 +81,24 @@
mAaptRunner = aaptRunner;
}
+ /**
+ * Sets whether this manifest merger will insert source markers into the merged source
+ *
+ * @param insertSourceMarkers if true, insert source markers
+ */
+ public void setInsertSourceMarkers(boolean insertSourceMarkers) {
+ mInsertSourceMarkers = insertSourceMarkers;
+ }
+
+ /**
+ * Returns whether this manifest merger will insert source markers into the merged source
+ *
+ * @return whether this manifest merger will insert source markers into the merged source
+ */
+ public boolean isInsertSourceMarkers() {
+ return mInsertSourceMarkers;
+ }
+
@Override
public void start() throws ConsumerException {
super.start();
@@ -121,26 +140,57 @@
File file = resourceFile.getFile();
String filename = file.getName();
- String folderName = item.getType().getName();
+
+ // Validate the filename here. Waiting for aapt isn't good
+ // because the error messages don't point back to the original
+ // file (if it's not an XML file) and besides, aapt prints
+ // the wrong path (it hard-codes "res" into the path for example,
+ // even if the file is not in a folder named res.
+ for (int i = 0, n = filename.length(); i < n; i++) {
+ // This is a direct port of the aapt file check in aapt's
+ // Resource.cpp#makeFileResources validation
+ char c = filename.charAt(i);
+ if (!((c >= 'a' && c <= 'z')
+ || (c >= '0' && c <= '9')
+ || c == '_' || c == '.')) {
+ String message =
+ "Invalid file name: must contain only lowercase "
+ + "letters and digits ([a-z0-9_.])";
+ throw new MergingException(message).setFile(file);
+ }
+ }
+
+ ResourceType itemType = item.getType();
+ String folderName = itemType.getName();
String qualifiers = resourceFile.getQualifiers();
if (!qualifiers.isEmpty()) {
folderName = folderName + RES_QUALIFIER_SEP + qualifiers;
}
File typeFolder = new File(getRootFolder(), folderName);
- createDir(typeFolder);
+ try {
+ createDir(typeFolder);
+ } catch (IOException ioe) {
+ throw new MergingException(ioe).setFile(typeFolder);
+ }
File outFile = new File(typeFolder, filename);
- if (mAaptRunner != null && filename.endsWith(DOT_PNG)) {
- // run aapt in single crunch mode on the original file to write the
- // destination file.
- mAaptRunner.crunchPng(file, outFile);
- } else if (filename.endsWith(DOT_XML)) {
- String p = file.toURI().toURL().toString();
- copyXmlWithComment(file, outFile, FILENAME_PREFIX + p);
- } else {
- Files.copy(file, outFile);
+ try {
+ if (itemType == ResourceType.RAW) {
+ // Don't crunch, don't insert source comments, etc - leave alone.
+ Files.copy(file, outFile);
+ } else if (mAaptRunner != null && filename.endsWith(DOT_PNG)) {
+ // run aapt in single crunch mode on the original file to write the
+ // destination file.
+ mAaptRunner.crunchPng(file, outFile);
+ } else if (mInsertSourceMarkers && filename.endsWith(DOT_XML)) {
+ SdkUtils.copyXmlWithSourceReference(file, outFile);
+ } else {
+ Files.copy(file, outFile);
+ }
+ } catch (IOException ioe) {
+ throw new MergingException(ioe).setFile(file);
}
return null;
}
@@ -149,30 +199,6 @@
}
}
- /** Copies a given XML file, and appends a given comment to the end */
- private static void copyXmlWithComment(@NonNull File from, @NonNull File to,
- @Nullable String comment) throws IOException {
- int successfulOps = 0;
- InputStream in = new FileInputStream(from);
- try {
- FileOutputStream out = new FileOutputStream(to, false);
- try {
- ByteStreams.copy(in, out);
- successfulOps++;
- if (comment != null) {
- String commentText = "<!-- " + XmlUtils.toXmlTextValue(comment) + " -->";
- byte[] suffix = commentText.getBytes(Charsets.UTF_8);
- out.write(suffix);
- }
- } finally {
- Closeables.close(out, successfulOps < 1);
- successfulOps++;
- }
- } finally {
- Closeables.close(in, successfulOps < 2);
- }
- }
-
@Override
public void removeItem(@NonNull ResourceItem removedItem, @Nullable ResourceItem replacedBy)
throws ConsumerException {
@@ -232,10 +258,11 @@
ResourceFolderType.VALUES.getName() :
ResourceFolderType.VALUES.getName() + RES_QUALIFIER_SEP + key;
+ File valuesFolder = new File(getRootFolder(), folderName);
+ File outFile = new File(valuesFolder, FN_VALUES_XML);
+ ResourceFile currentFile = null;
try {
- File valuesFolder = new File(getRootFolder(), folderName);
createDir(valuesFolder);
- File outFile = new File(valuesFolder, FN_VALUES_XML);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
@@ -251,26 +278,39 @@
Collections.sort(items);
- ResourceFile currentFile = null;
for (ResourceItem item : items) {
ResourceFile source = item.getSource();
- if (source != currentFile) {
- currentFile = source;
- rootNode.appendChild(document.createTextNode("\n"));
- File file = source.getFile();
- String path = file.toURI().toURL().toString();
- rootNode.appendChild(document.createComment(FILENAME_PREFIX + path));
- rootNode.appendChild(document.createTextNode("\n"));
+ if (source != currentFile && source != null && mInsertSourceMarkers) {
+ currentFile = source;
+ rootNode.appendChild(document.createTextNode("\n"));
+ File file = source.getFile();
+ rootNode.appendChild(document.createComment(
+ createPathComment(file, true)));
+ rootNode.appendChild(document.createTextNode("\n"));
+ // Add an <eat-comment> element to ensure that this comment won't
+ // get merged into a potential comment from the next child (or
+ // even added as the sole comment in the R class)
+ rootNode.appendChild(document.createElement(TAG_EAT_COMMENT));
+ rootNode.appendChild(document.createTextNode("\n"));
}
Node adoptedNode = NodeUtils.adoptNode(document, item.getValue());
rootNode.appendChild(adoptedNode);
}
- String content = XmlPrettyPrinter.prettyPrint(document, true);
+ currentFile = null;
+
+ String content;
+ try {
+ content = XmlPrettyPrinter.prettyPrint(document, true);
+ } catch (Throwable t) {
+ content = XmlUtils.toXml(document, false);
+ }
Files.write(content, outFile, Charsets.UTF_8);
} catch (Throwable t) {
- throw new ConsumerException(t);
+ ConsumerException exception = new ConsumerException(t);
+ exception.setFile(currentFile != null ? currentFile.getFile() : outFile);
+ throw exception;
}
}
}
@@ -285,7 +325,6 @@
}
}
-
/**
* Removes a file that already exists in the out res folder. This has to be a non value file.
*
diff --git a/sdk-common/src/main/java/com/android/ide/common/res2/MergingException.java b/sdk-common/src/main/java/com/android/ide/common/res2/MergingException.java
new file mode 100644
index 0000000..f56e189
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/res2/MergingException.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.res2;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+import java.io.File;
+
+/** Exception for errors during merging */
+public class MergingException extends Exception {
+ private String mMessage; // Keeping our own copy since parent prepends exception class name
+ private File mFile;
+ private int mLine = -1;
+ private int mColumn = -1;
+
+ public MergingException(@NonNull String message, @Nullable Throwable cause) {
+ super(message, cause);
+ mMessage = message;
+ }
+
+ public MergingException(@NonNull String message) {
+ this(message, null);
+ }
+
+ public MergingException(@NonNull Throwable cause) {
+ this(cause.getLocalizedMessage(), cause);
+ }
+
+ public MergingException setFile(@NonNull File file) {
+ mFile = file;
+ return this;
+ }
+
+ public MergingException setCause(@NonNull Throwable cause) {
+ initCause(cause);
+ return this;
+ }
+
+ public MergingException setLine(int line) {
+ mLine = line;
+ return this;
+ }
+
+ public MergingException setColumn(int column) {
+ mColumn = column;
+ return this;
+ }
+
+ /** Computes the error message to display for this error */
+ @Override
+ public String getMessage() {
+ StringBuilder sb = new StringBuilder();
+ String path = null;
+ if (mFile != null) {
+ path = mFile.getAbsolutePath();
+ sb.append(path);
+ if (mLine >= 0) {
+ sb.append(':');
+ sb.append(Integer.toString(mLine));
+ if (mColumn >= 0) {
+ sb.append(':');
+ sb.append(Integer.toString(mColumn));
+ }
+ }
+ }
+
+ if (sb.length() > 0) {
+ sb.append(':').append(' ');
+
+ // ALWAYS insert the string "Error:" between the path and the message.
+ // This is done to make the error messages more simple to detect
+ // (since a generic path: message pattern can match a lot of output, basically
+ // any labeled output, and we don't want to do file existence checks on any random
+ // string to the left of a colon.)
+ if (!mMessage.startsWith("Error: ")) {
+ sb.append("Error: ");
+ }
+ } else if (!mMessage.contains("Error: ")) {
+ sb.append("Error: ");
+ }
+
+ String message = mMessage;
+
+ // If the error message already starts with the path, strip it out.
+ // This avoids redundant looking error messages you can end up with
+ // like for example for permission denied errors where the error message
+ // string itself contains the path as a prefix:
+ // /my/full/path: /my/full/path (Permission denied)
+ if (path != null && message.startsWith(path)) {
+ int stripStart = path.length();
+ if (message.length() > stripStart && message.charAt(stripStart) == ':') {
+ stripStart++;
+ }
+ if (message.length() > stripStart && message.charAt(stripStart) == ' ') {
+ stripStart++;
+ }
+ message = message.substring(stripStart);
+ }
+
+ sb.append(message);
+ return sb.toString();
+ }
+
+ @Override
+ public String toString() {
+ return getMessage();
+ }
+
+ /** @return the source file where the error occurred, if known */
+ @Nullable
+ public File getFile() {
+ return mFile;
+ }
+
+ /** @return the 0-based line number, if known, otherwise -1 */
+ public int getLine() {
+ return mLine;
+ }
+
+ /** @return the 0-based column number, if known, otherwise -1 */
+ public int getColumn() {
+ return mColumn;
+ }
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/res2/NodeUtils.java b/sdk-common/src/main/java/com/android/ide/common/res2/NodeUtils.java
index e977bdf..f945cdb 100644
--- a/sdk-common/src/main/java/com/android/ide/common/res2/NodeUtils.java
+++ b/sdk-common/src/main/java/com/android/ide/common/res2/NodeUtils.java
@@ -80,7 +80,15 @@
NamedNodeMap attributes = node.getAttributes();
if (attributes != null) {
for (int i = 0, n = attributes.getLength(); i < n; i++) {
- processSingleNodeNamespace(attributes.item(i), document);
+ Node attribute = attributes.item(i);
+ if (!processSingleNodeNamespace(attribute, document)) {
+ String nsUri = attribute.getNamespaceURI();
+ if (nsUri != null) {
+ attributes.removeNamedItemNS(nsUri, attribute.getLocalName());
+ } else {
+ attributes.removeNamedItem(attribute.getLocalName());
+ }
+ }
}
}
@@ -98,10 +106,17 @@
/**
* Update the namespace of a given node to work with a given document.
+ *
* @param node the node to update
* @param document the new document
+ *
+ * @return false if the attribute is to be dropped
*/
- private static void processSingleNodeNamespace(Node node, Document document) {
+ private static boolean processSingleNodeNamespace(Node node, Document document) {
+ if ("xmlns".equals(node.getLocalName())) {
+ return false;
+ }
+
String ns = node.getNamespaceURI();
if (ns != null) {
NamedNodeMap docAttributes = document.getAttributes();
@@ -118,6 +133,8 @@
prefix = prefix.substring(6);
node.setPrefix(prefix);
}
+
+ return true;
}
/**
diff --git a/sdk-common/src/main/java/com/android/ide/common/res2/ResourceItem.java b/sdk-common/src/main/java/com/android/ide/common/res2/ResourceItem.java
index 6311692..f89abcd 100644
--- a/sdk-common/src/main/java/com/android/ide/common/res2/ResourceItem.java
+++ b/sdk-common/src/main/java/com/android/ide/common/res2/ResourceItem.java
@@ -20,6 +20,7 @@
import static com.android.SdkConstants.ANDROID_NS_NAME_PREFIX;
import static com.android.SdkConstants.ANDROID_NS_NAME_PREFIX_LEN;
import static com.android.SdkConstants.ANDROID_PREFIX;
+import static com.android.SdkConstants.ATTR_ID;
import static com.android.SdkConstants.ATTR_NAME;
import static com.android.SdkConstants.ATTR_PARENT;
import static com.android.SdkConstants.ATTR_QUANTITY;
@@ -27,6 +28,9 @@
import static com.android.SdkConstants.ATTR_VALUE;
import static com.android.SdkConstants.NEW_ID_PREFIX;
import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
+import static com.android.ide.common.resources.ResourceResolver.ATTR_EXAMPLE;
+import static com.android.ide.common.resources.ResourceResolver.XLIFF_G_TAG;
+import static com.android.ide.common.resources.ResourceResolver.XLIFF_NAMESPACE_PREFIX;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
@@ -47,6 +51,7 @@
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
+import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
@@ -494,10 +499,29 @@
short nodeType = child.getNodeType();
switch (nodeType) {
- case Node.ELEMENT_NODE:
- String str = getTextNode(child);
- sb.append(str);
+ case Node.ELEMENT_NODE: {
+ Element element = (Element) child;
+ if (XLIFF_G_TAG.equals(element.getLocalName()) &&
+ element.getNamespaceURI() != null &&
+ element.getNamespaceURI().startsWith( XLIFF_NAMESPACE_PREFIX)) {
+ if (element.hasAttribute(ATTR_EXAMPLE)) {
+ // <xliff:g id="number" example="7">%d</xliff:g> minutes
+ // => "(7) minutes"
+ String example = element.getAttribute(ATTR_EXAMPLE);
+ sb.append('(').append(example).append(')');
+ continue;
+ } else if (element.hasAttribute(ATTR_ID)) {
+ // Step <xliff:g id="step_number">%1$d</xliff:g>
+ // => Step ${step_number}
+ String id = element.getAttribute(ATTR_ID);
+ sb.append('$').append('{').append(id).append('}');
+ continue;
+ }
+ }
+
+ sb.append(getTextNode(child));
break;
+ }
case Node.TEXT_NODE:
sb.append(child.getNodeValue());
break;
diff --git a/sdk-common/src/main/java/com/android/ide/common/res2/ResourceSet.java b/sdk-common/src/main/java/com/android/ide/common/res2/ResourceSet.java
index 48b60ba..1ee3a5e 100644
--- a/sdk-common/src/main/java/com/android/ide/common/res2/ResourceSet.java
+++ b/sdk-common/src/main/java/com/android/ide/common/res2/ResourceSet.java
@@ -32,9 +32,9 @@
import org.w3c.dom.Attr;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
+import org.xml.sax.SAXParseException;
import java.io.File;
-import java.io.IOException;
import java.util.List;
import java.util.Map;
@@ -57,7 +57,7 @@
@Override
protected ResourceFile createFileAndItems(File sourceFolder, File file, ILogger logger)
- throws IOException {
+ throws MergingException {
// get the type.
FolderData folderData = getFolderData(file.getParentFile());
@@ -94,8 +94,9 @@
// Need to also create ATTR items for its children
try {
ValueResourceParser2.addStyleableItems(resNode, resourceList, null);
- } catch (IOException ignored) {
- // since we are not passing a dup map, this will never bet thrown
+ } catch (MergingException ignored) {
+ // since we are not passing a dup map, this will never be thrown
+ assert false : file + ": " + ignored.getMessage();
}
}
}
@@ -122,7 +123,7 @@
@Override
protected void readSourceFolder(File sourceFolder, ILogger logger)
- throws DuplicateDataException, IOException {
+ throws MergingException {
File[] folders = sourceFolder.listFiles();
if (folders != null) {
for (File folder : folders) {
@@ -153,7 +154,7 @@
@Override
protected boolean handleChangedFile(@NonNull File sourceFolder, @NonNull File changedFile)
- throws IOException {
+ throws MergingException {
FolderData folderData = getFolderData(changedFile.getParentFile());
if (folderData.folderType == null) {
@@ -163,9 +164,12 @@
ResourceFile resourceFile = getDataFile(changedFile);
if (resourceFile == null) {
- throw new RuntimeException(String.format(
- "In DataSet '%s', no data file for changedFile '%s'",
- getConfigName(), changedFile.getAbsolutePath()));
+ String message = String.format(
+ "In DataSet '%s', no data file for changedFile '%s'. "
+ + "This is an internal error in the incremental builds code; "
+ + "to work around it, try doing a full clean build.",
+ getConfigName(), changedFile.getAbsolutePath());
+ throw new MergingException(message).setFile(changedFile);
}
//noinspection VariableNotUsedInsideIf
@@ -234,10 +238,10 @@
* @param folderData the folder Data
* @param logger a logger object
*
- * @throws IOException
+ * @throws MergingException if something goes wrong
*/
private void parseFolder(File sourceFolder, File folder, FolderData folderData, ILogger logger)
- throws IOException {
+ throws MergingException {
File[] files = folder.listFiles();
if (files != null && files.length > 0) {
for (File file : files) {
@@ -253,8 +257,8 @@
}
}
- private ResourceFile createResourceFile(File file, FolderData folderData, ILogger logger)
- throws IOException {
+ private static ResourceFile createResourceFile(File file, FolderData folderData, ILogger logger)
+ throws MergingException {
if (folderData.type != null) {
int pos;// get the resource name based on the filename
String name = file.getName();
@@ -273,7 +277,8 @@
List<ResourceItem> items = parser.parseFile();
return new ResourceFile(file, items, folderData.qualifiers);
- } catch (IOException e) {
+ } catch (MergingException e) {
+ e.setFile(file);
logger.error(e, "Failed to parse %s", file.getAbsolutePath());
throw e;
}
diff --git a/sdk-common/src/main/java/com/android/ide/common/res2/ValueResourceParser2.java b/sdk-common/src/main/java/com/android/ide/common/res2/ValueResourceParser2.java
index a7052c1..a55e1d4 100644
--- a/sdk-common/src/main/java/com/android/ide/common/res2/ValueResourceParser2.java
+++ b/sdk-common/src/main/java/com/android/ide/common/res2/ValueResourceParser2.java
@@ -25,6 +25,7 @@
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.resources.ResourceType;
+import com.android.utils.XmlUtils;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
@@ -36,6 +37,7 @@
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
import java.io.BufferedInputStream;
import java.io.File;
@@ -71,10 +73,10 @@
* Parses the file and returns a list of {@link ResourceItem} objects.
* @return a list of resources.
*
- * @throws IOException
+ * @throws MergingException if a merging exception happens
*/
@NonNull
- List<ResourceItem> parseFile() throws IOException {
+ List<ResourceItem> parseFile() throws MergingException {
Document document = parseDocument(mFile);
// get the root node
@@ -106,7 +108,12 @@
if (resource.getType() == ResourceType.DECLARE_STYLEABLE) {
// Need to also create ATTR items for its children
- ValueResourceParser2.addStyleableItems(node, resources, map);
+ try {
+ ValueResourceParser2.addStyleableItems(node, resources, map);
+ } catch (MergingException e) {
+ e.setFile(mFile);
+ throw e;
+ }
}
}
}
@@ -175,22 +182,35 @@
* Loads the DOM for a given file and returns a {@link Document} object.
* @param file the file to parse
* @return a Document object.
- * @throws IOException
+ * @throws MergingException if a merging exception happens
*/
@NonNull
- static Document parseDocument(File file) throws IOException {
+ static Document parseDocument(File file) throws MergingException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- BufferedInputStream stream = new BufferedInputStream(new FileInputStream(file));
- InputSource is = new InputSource(stream);
- factory.setNamespaceAware(true);
- factory.setValidating(false);
+ BufferedInputStream stream = null;
try {
+ stream = new BufferedInputStream(new FileInputStream(file));
+ InputSource is = new InputSource(stream);
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
DocumentBuilder builder = factory.newDocumentBuilder();
return builder.parse(is);
+ } catch (SAXParseException e) {
+ String message = e.getLocalizedMessage();
+ MergingException exception = new MergingException(message, e);
+ exception.setFile(file);
+ int lineNumber = e.getLineNumber();
+ if (lineNumber != -1) {
+ exception.setLine(lineNumber - 1); // make line numbers 0-based
+ exception.setColumn(e.getColumnNumber() - 1);
+ }
+ throw exception;
} catch (ParserConfigurationException e) {
- throw new IOException(e);
+ throw new MergingException(e).setFile(file);
} catch (SAXException e) {
- throw new IOException(e);
+ throw new MergingException(e).setFile(file);
+ } catch (IOException e) {
+ throw new MergingException(e).setFile(file);
} finally {
Closeables.closeQuietly(stream);
}
@@ -206,7 +226,8 @@
*/
static void addStyleableItems(@NonNull Node styleableNode,
@NonNull List<ResourceItem> list,
- @Nullable Map<ResourceType, Set<String>> map) throws IOException {
+ @Nullable Map<ResourceType, Set<String>> map)
+ throws MergingException {
assert styleableNode.getNodeName().equals(ResourceType.DECLARE_STYLEABLE.getName());
NodeList nodes = styleableNode.getChildNodes();
@@ -223,7 +244,7 @@
// is the attribute in the android namespace?
if (!resource.getName().startsWith(ANDROID_NS_NAME_PREFIX)) {
- if (hasFormatAttribute(node)) {
+ if (hasFormatAttribute(node) || XmlUtils.hasElementChildren(node)) {
checkDuplicate(resource, map);
resource.setIgnoredFromDiskMerge(true);
list.add(resource);
@@ -234,7 +255,8 @@
}
private static void checkDuplicate(@NonNull ResourceItem resource,
- @Nullable Map<ResourceType, Set<String>> map) throws IOException {
+ @Nullable Map<ResourceType, Set<String>> map)
+ throws MergingException {
if (map == null) {
return;
}
@@ -246,7 +268,7 @@
map.put(resource.getType(), set);
} else {
if (set.contains(name)) {
- throw new IOException(String.format(
+ throw new MergingException(String.format(
"Found item %s/%s more than one time",
resource.getType().getDisplayName(), name));
}
diff --git a/sdk-common/src/main/java/com/android/ide/common/res2/ValueXmlHelper.java b/sdk-common/src/main/java/com/android/ide/common/res2/ValueXmlHelper.java
index b01622f..f779986 100644
--- a/sdk-common/src/main/java/com/android/ide/common/res2/ValueXmlHelper.java
+++ b/sdk-common/src/main/java/com/android/ide/common/res2/ValueXmlHelper.java
@@ -40,6 +40,7 @@
* @param trim whether surrounding space and quotes should be trimmed
* @return the string with the escape characters removed and expanded
*/
+ @SuppressWarnings("UnnecessaryContinue")
@Nullable
public static String unescapeResourceString(
@Nullable String s,
@@ -51,6 +52,7 @@
// Trim space surrounding optional quotes
int i = 0;
int n = s.length();
+ boolean quoted = false;
if (trim) {
while (i < n) {
char c = s.charAt(i);
@@ -74,47 +76,21 @@
// Trim surrounding quotes. Note that there can be *any* number of these, and
// the left side and right side do not have to match; e.g. you can have
// """"f"" => f
- int quoteStart = i;
int quoteEnd = n;
while (i < n) {
char c = s.charAt(i);
if (c != '"') {
break;
}
+ quoted = true;
i++;
}
// Searching backwards is slightly more complicated; make sure we don't trim
// quotes that have been escaped.
- while (n > i) {
- char c = s.charAt(n - 1);
- if (c != '"') {
- if (n < s.length() && isEscaped(s, n)) {
- n++;
- }
- break;
- }
- n--;
- }
- if (n == i) {
- return ""; //$NON-NLS-1$
- }
-
- // Only trim leading spaces if we didn't already process a leading quote:
- if (i == quoteStart) {
- while (i < n) {
- char c = s.charAt(i);
- if (!Character.isWhitespace(c)) {
- break;
- }
- i++;
- }
- }
- // Only trim trailing spaces if we didn't already process a trailing quote:
- if (n == quoteEnd) {
+ if (quoted) {
while (n > i) {
char c = s.charAt(n - 1);
- if (!Character.isWhitespace(c)) {
- //See if this was a \, and if so, see whether it was escaped
+ if (c != '"') {
if (n < s.length() && isEscaped(s, n)) {
n++;
}
@@ -126,19 +102,80 @@
if (n == i) {
return ""; //$NON-NLS-1$
}
+
+ // Only trim leading spaces if we didn't already process a leading quote:
+ if (!quoted) {
+ while (i < n) {
+ char c = s.charAt(i);
+ if (!Character.isWhitespace(c)) {
+ break;
+ }
+ i++;
+ }
+
+ // Only trim trailing spaces if we didn't already process a trailing quote:
+ if (n == quoteEnd) {
+ while (n > i) {
+ char c = s.charAt(n - 1);
+ if (!Character.isWhitespace(c)) {
+ //See if this was a \, and if so, see whether it was escaped
+ if (n < s.length() && isEscaped(s, n)) {
+ n++;
+ }
+ break;
+ }
+ n--;
+ }
+ }
+ if (n == i) {
+ return ""; //$NON-NLS-1$
+ }
+ }
}
- // If no surrounding whitespace and no escape characters, no need to do any
- // more work
- if (i == 0 && n == s.length() && s.indexOf('\\') == -1
- && (!escapeEntities || s.indexOf('&') == -1)) {
- return s;
+ // Perform a single pass over the string and see if it contains
+ // (1) spaces that should be converted (e.g. repeated spaces or a newline which
+ // should be converted to a space)
+ // (2) escape characters (\ and &) which will require expansions
+ // If we find neither of these, we can simply return the string
+ boolean rewriteWhitespace = false;
+ if (!quoted) {
+ // See if we need to fold adjacent spaces
+ boolean prevSpace = false;
+ boolean hasEscape = false;
+ for (int curr = i; curr < n; curr++) {
+ char c = s.charAt(curr);
+ if (c == '\\' || c == '&') {
+ hasEscape = true;
+ }
+ boolean isSpace = Character.isWhitespace(c);
+ if (isSpace && prevSpace) {
+ // fold adjacent spaces
+ rewriteWhitespace = true;
+ } else if (c == '\n') {
+ // rewrite newlines as spaces
+ rewriteWhitespace = true;
+ }
+ prevSpace = isSpace;
+ }
+
+ if (!trim) {
+ rewriteWhitespace = false;
+ }
+
+ // If no surrounding whitespace and no escape characters, no need to do any
+ // more work
+ if (!rewriteWhitespace && !hasEscape && i == 0 && n == s.length()) {
+ return s;
+ }
}
StringBuilder sb = new StringBuilder(n - i);
+ boolean prevSpace = false;
for (; i < n; i++) {
char c = s.charAt(i);
if (c == '\\' && i < n - 1) {
+ prevSpace = false;
char next = s.charAt(i + 1);
// Unicode escapes
if (next == 'u' && i < n - 5) { // case sensitive
@@ -155,9 +192,11 @@
} else if (next == 'n') {
sb.append('\n');
i++;
+ continue;
} else if (next == 't') {
sb.append('\t');
i++;
+ continue;
} else {
sb.append(next);
i++;
@@ -165,6 +204,7 @@
}
} else {
if (c == '&' && escapeEntities) {
+ prevSpace = false;
if (s.regionMatches(true, i, LT_ENTITY, 0, LT_ENTITY.length())) {
sb.append('<');
i += LT_ENTITY.length() - 1;
@@ -185,9 +225,41 @@
sb.append('>');
i += GT_ENTITY.length() - 1;
continue;
+ } else if (i < n - 2 && s.charAt(i + 1) == '#') {
+ int end = s.indexOf(';', i + 1);
+ if (end != -1) {
+ char first = s.charAt(i + 2);
+ boolean hex = first == 'x' || first == 'X';
+ String number = s.substring(i + (hex ? 3 : 2), end);
+ try {
+ int unicodeValue = Integer.parseInt(number, hex ? 16 : 10);
+ sb.append((char) unicodeValue);
+ i = end;
+ continue;
+ } catch (NumberFormatException e) {
+ // Invalid escape: Just proceed to literally transcribe it
+ sb.append(c);
+ }
+ } else {
+ // Invalid escape: Just proceed to literally transcribe it
+ sb.append(c);
+ }
}
}
- sb.append(c);
+
+ if (rewriteWhitespace) {
+ boolean isSpace = Character.isWhitespace(c);
+ if (isSpace) {
+ if (!prevSpace) {
+ sb.append(' '); // replace newlines etc with a plain space
+ }
+ } else {
+ sb.append(c);
+ }
+ prevSpace = isSpace;
+ } else {
+ sb.append(c);
+ }
}
}
s = sb.toString();
diff --git a/sdk-common/src/main/java/com/android/ide/common/resources/FrameworkResources.java b/sdk-common/src/main/java/com/android/ide/common/resources/FrameworkResources.java
index fe8e197..f41e115 100755
--- a/sdk-common/src/main/java/com/android/ide/common/resources/FrameworkResources.java
+++ b/sdk-common/src/main/java/com/android/ide/common/resources/FrameworkResources.java
@@ -45,10 +45,10 @@
*
* This behaves the same as {@link ResourceRepository} except that it differentiates between
* resources that are public and non public.
- * {@link #getResources(ResourceType)} and {@link #hasResourcesOfType(ResourceType)} only return
+ * {@link #getResourceItemsOfType(ResourceType)} and {@link #hasResourcesOfType(ResourceType)} only return
* public resources. This is typically used to display resource lists in the UI.
*
- * {@link #getConfiguredResources(com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration)}
+ * {@link #getConfiguredResources(com.android.ide.common.resources.configuration.FolderConfiguration)}
* returns all resources, even the non public ones so that this can be used for rendering.
*/
public class FrameworkResources extends ResourceRepository {
@@ -100,7 +100,6 @@
* This map is a subset of the full resource map that only contains framework resources
* that are public.
*
- * @param resFolder The root folder of the resources
* @param logger a logger to report issues to
*/
public void loadPublicResources(@Nullable ILogger logger) {
diff --git a/sdk-common/src/main/java/com/android/ide/common/resources/LocaleManager.java b/sdk-common/src/main/java/com/android/ide/common/resources/LocaleManager.java
index 39922db..5eda488 100644
--- a/sdk-common/src/main/java/com/android/ide/common/resources/LocaleManager.java
+++ b/sdk-common/src/main/java/com/android/ide/common/resources/LocaleManager.java
@@ -27,16 +27,7 @@
/**
* The {@linkplain LocaleManager} provides access to locale information such as
- * language names and flag icons for the various locales.
- * <p>
- * All the flag images came from the WindowBuilder subversion repository
- * http://dev.eclipse.org/svnroot/tools/org.eclipse.windowbuilder/trunk (and in
- * particular, a snapshot of revision 424). However, it appears that the icons
- * are from http://www.famfamfam.com/lab/icons/flags/ which states that "these
- * flag icons are available for free use for any purpose with no requirement for
- * attribution." Adding the URL here such that we can check back occasionally
- * and see if there are corrections or updates. Also note that the flag names
- * are in ISO 3166-1 alpha-2 country codes.
+ * language names and language to region name mappings for the various locales.
*/
public class LocaleManager {
@SuppressWarnings("InstantiationOfUtilityClass")
diff --git a/sdk-common/src/main/java/com/android/ide/common/resources/ResourceItem.java b/sdk-common/src/main/java/com/android/ide/common/resources/ResourceItem.java
index 2a4761f..bed1a5a 100644
--- a/sdk-common/src/main/java/com/android/ide/common/resources/ResourceItem.java
+++ b/sdk-common/src/main/java/com/android/ide/common/resources/ResourceItem.java
@@ -135,7 +135,7 @@
/**
* Returns {@code true} if the item has no source file.
- * @return
+ * @return true if the item has no source file.
*/
protected boolean hasNoSourceFile() {
return mFiles.isEmpty();
diff --git a/sdk-common/src/main/java/com/android/ide/common/resources/ResourceItemResolver.java b/sdk-common/src/main/java/com/android/ide/common/resources/ResourceItemResolver.java
new file mode 100644
index 0000000..546955c
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/resources/ResourceItemResolver.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.common.resources;
+
+import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
+import static com.android.SdkConstants.PREFIX_THEME_REF;
+import static com.android.ide.common.resources.ResourceResolver.MAX_RESOURCE_INDIRECTION;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.StyleResourceValue;
+import com.android.ide.common.res2.AbstractResourceRepository;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.resources.ResourceType;
+
+import java.util.List;
+
+/**
+ * Like {@link ResourceResolver} but for a single item, so it does not need the full resource maps
+ * to be resolved up front. Typically used for cases where we may not have fully configured
+ * resource maps and we need to look up a specific value, such as in Android Studio where
+ * a color reference is found in an XML style file, and we want to resolve it in order to
+ * display the final resolved color in the editor margin.
+ */
+public class ResourceItemResolver extends RenderResources {
+ private final FolderConfiguration mConfiguration;
+ private final LayoutLog mLogger;
+ private final ResourceProvider mResourceProvider;
+ private ResourceRepository mFrameworkResources;
+ private ResourceResolver mResolver;
+ private AbstractResourceRepository myAppResources;
+ @Nullable private List<ResourceValue> mLookupChain;
+
+ public ResourceItemResolver(
+ @NonNull FolderConfiguration configuration,
+ @NonNull ResourceProvider resourceProvider,
+ @Nullable LayoutLog logger) {
+ mConfiguration = configuration;
+ mResourceProvider = resourceProvider;
+ mLogger = logger;
+ mResolver = resourceProvider.getResolver(false);
+ }
+
+ public ResourceItemResolver(
+ @NonNull FolderConfiguration configuration,
+ @NonNull ResourceRepository frameworkResources,
+ @NonNull AbstractResourceRepository appResources,
+ @Nullable LayoutLog logger) {
+ mConfiguration = configuration;
+ mResourceProvider = null;
+ mLogger = logger;
+ mFrameworkResources = frameworkResources;
+ myAppResources = appResources;
+ }
+
+ @Override
+ @Nullable
+ public ResourceValue resolveResValue(@Nullable ResourceValue resValue) {
+ if (mResolver != null) {
+ return mResolver.resolveResValue(resValue);
+ }
+ if (mLookupChain != null) {
+ mLookupChain.add(resValue);
+ }
+ return resolveResValue(resValue, 0);
+ }
+
+ @Nullable
+ private ResourceValue resolveResValue(@Nullable ResourceValue resValue, int depth) {
+ if (resValue == null) {
+ return null;
+ }
+
+ // if the resource value is null, we simply return it.
+ String value = resValue.getValue();
+ if (value == null) {
+ return resValue;
+ }
+
+ // else attempt to find another ResourceValue referenced by this one.
+ ResourceValue resolvedResValue = findResValue(value, resValue.isFramework());
+
+ // if the value did not reference anything, then we simply return the input value
+ if (resolvedResValue == null) {
+ return resValue;
+ }
+
+ // detect potential loop due to mishandled namespace in attributes
+ if (resValue == resolvedResValue || depth >= MAX_RESOURCE_INDIRECTION) {
+ if (mLogger != null) {
+ mLogger.error(LayoutLog.TAG_BROKEN,
+ String.format(
+ "Potential stack overflow trying to resolve '%s': cyclic resource definitions? Render may not be accurate.",
+ value),
+ null);
+ }
+ return resValue;
+ }
+
+ // otherwise, we attempt to resolve this new value as well
+ return resolveResValue(resolvedResValue, depth + 1);
+ }
+
+ @Override
+ @Nullable
+ public ResourceValue findResValue(@Nullable String reference, boolean inFramework) {
+ if (mResolver != null) {
+ return mResolver.findResValue(reference, inFramework);
+ }
+
+ if (reference == null) {
+ return null;
+ }
+
+ if (mLookupChain != null && !mLookupChain.isEmpty() &&
+ reference.startsWith(PREFIX_RESOURCE_REF)) {
+ ResourceValue prev = mLookupChain.get(mLookupChain.size() - 1);
+ if (!reference.equals(prev.getValue())) {
+ ResourceValue next = new ResourceValue(prev.getResourceType(), prev.getName(),
+ prev.isFramework());
+ next.setValue(reference);
+ mLookupChain.add(next);
+ }
+ }
+
+ ResourceUrl resource = ResourceUrl.parse(reference);
+ if (resource != null && resource.hasValidName()) {
+ if (resource.theme) {
+ // Do theme lookup? We can't do that here; requires full global analysis, so just use
+ // a real resource resolver
+ ResourceResolver resolver = getFullResolver();
+ if (resolver != null) {
+ return resolver.findResValue(reference, inFramework);
+ } else {
+ return null;
+ }
+ } else if (reference.startsWith(PREFIX_RESOURCE_REF)) {
+ return findResValue(resource.type, resource.name, inFramework || resource.framework);
+ }
+ }
+
+ // Looks like the value didn't reference anything. Return null.
+ return null;
+ }
+
+ private ResourceValue findResValue(ResourceType resType, String resName, boolean framework) {
+ // map of ResourceValue for the given type
+ // if allowed, search in the project resources first.
+ if (!framework) {
+ if (myAppResources == null) {
+ assert mResourceProvider != null;
+ myAppResources = mResourceProvider.getAppResources();
+ if (myAppResources == null) {
+ return null;
+ }
+ }
+ ResourceValue item = myAppResources.getConfiguredValue(resType, resName,
+ mConfiguration);
+ if (item != null) {
+ if (mLookupChain != null) {
+ mLookupChain.add(item);
+ }
+ return item;
+ }
+ } else {
+ if (mFrameworkResources == null) {
+ assert mResourceProvider != null;
+ mFrameworkResources = mResourceProvider.getFrameworkResources();
+ if (mFrameworkResources == null) {
+ return null;
+ }
+ }
+ // now search in the framework resources.
+ if (mFrameworkResources.hasResourceItem(resType, resName)) {
+ ResourceItem item = mFrameworkResources.getResourceItem(resType, resName);
+ ResourceValue value = item.getResourceValue(resType, mConfiguration, true);
+ if (value != null && mLookupChain != null) {
+ mLookupChain.add(value);
+ }
+ return value;
+ }
+ }
+
+ // didn't find the resource anywhere.
+ if (mLogger != null) {
+ mLogger.warning(LayoutLog.TAG_RESOURCES_RESOLVE,
+ "Couldn't resolve resource @" +
+ (framework ? "android:" : "") + resType + "/" + resName,
+ new ResourceValue(resType, resName, framework));
+ }
+ return null;
+ }
+
+ @Override
+ public StyleResourceValue getCurrentTheme() {
+ ResourceResolver resolver = getFullResolver();
+ if (resolver != null) {
+ return resolver.getCurrentTheme();
+ }
+
+ return null;
+ }
+
+ @Override
+ public ResourceValue resolveValue(ResourceType type, String name, String value,
+ boolean isFrameworkValue) {
+ if (value == null) {
+ return null;
+ }
+
+ // get the ResourceValue referenced by this value
+ ResourceValue resValue = findResValue(value, isFrameworkValue);
+
+ // if resValue is null, but value is not null, this means it was not a reference.
+ // we return the name/value wrapper in a ResourceValue. the isFramework flag doesn't
+ // matter.
+ if (resValue == null) {
+ return new ResourceValue(type, name, value, isFrameworkValue);
+ }
+
+ // we resolved a first reference, but we need to make sure this isn't a reference also.
+ return resolveResValue(resValue);
+ }
+
+ // For theme lookup, we need to delegate to a full resource resolver
+
+ @Override
+ public StyleResourceValue getTheme(String name, boolean frameworkTheme) {
+ assert false; // This method shouldn't be called on this resolver
+ return super.getTheme(name, frameworkTheme);
+ }
+
+ @Override
+ public boolean themeIsParentOf(StyleResourceValue parentTheme, StyleResourceValue childTheme) {
+ assert false; // This method shouldn't be called on this resolver
+ return super.themeIsParentOf(parentTheme, childTheme);
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public ResourceValue findItemInTheme(String itemName) {
+ ResourceResolver resolver = getFullResolver();
+ return resolver != null ? resolver.findItemInTheme(itemName) : null;
+ }
+
+ @Override
+ public ResourceValue findItemInTheme(String attrName, boolean isFrameworkAttr) {
+ ResourceResolver resolver = getFullResolver();
+ return resolver != null ? resolver.findItemInTheme(attrName, isFrameworkAttr) : null;
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public ResourceValue findItemInStyle(StyleResourceValue style, String attrName) {
+ ResourceResolver resolver = getFullResolver();
+ return resolver != null ? resolver.findItemInStyle(style, attrName) : null;
+ }
+
+ @Override
+ public ResourceValue findItemInStyle(StyleResourceValue style, String attrName,
+ boolean isFrameworkAttr) {
+ ResourceResolver resolver = getFullResolver();
+ return resolver != null ? resolver.findItemInStyle(style, attrName, isFrameworkAttr) : null;
+ }
+
+ private ResourceResolver getFullResolver() {
+ if (mResolver == null) {
+ if (mResourceProvider == null) {
+ return null;
+ }
+ mResolver = mResourceProvider.getResolver(true);
+ if (mResolver != null) {
+ if (mLookupChain != null) {
+ mResolver = mResolver.createRecorder(mLookupChain);
+ }
+ }
+
+ }
+ return mResolver;
+ }
+
+ /**
+ * Optional method to set a list the resolver should record all value resolutions
+ * into. Useful if you want to find out the resolution chain for a resource,
+ * e.g. {@code @color/buttonForeground => @color/foreground => @android:color/black }.
+ * <p>
+ * There is no getter. Clients setting this list should look it up themselves.
+ * Note also that if this resolver has to delegate to a full resource resolver,
+ * e.g. to follow theme attributes, those resolutions will not be recorded.
+ *
+ * @param lookupChain the list to set, or null
+ */
+ public void setLookupChainList(@Nullable List<ResourceValue> lookupChain) {
+ mLookupChain = lookupChain;
+ }
+
+ /** Returns the lookup chain being used by this resolver */
+ @Nullable
+ public List<ResourceValue> getLookupChain() {
+ return mLookupChain;
+ }
+
+ /**
+ * Returns a display string for a resource lookup
+ *
+ * @param type the resource type
+ * @param name the resource name
+ * @param isFramework whether the item is in the framework
+ * @param lookupChain the list of resolved items to display
+ * @return the display string
+ */
+ public static String getDisplayString(
+ @NonNull ResourceType type,
+ @NonNull String name,
+ boolean isFramework,
+ @NonNull List<ResourceValue> lookupChain) {
+ String url = ResourceUrl.create(type, name, isFramework, false).toString();
+ return getDisplayString(url, lookupChain);
+ }
+
+ /**
+ * Returns a display string for a resource lookup
+ * @param url the resource url, such as {@code @string/foo}
+ * @param lookupChain the list of resolved items to display
+ * @return the display string
+ */
+ @NonNull
+ public static String getDisplayString(
+ @NonNull String url,
+ @NonNull List<ResourceValue> lookupChain) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(url);
+ String prev = url;
+ for (ResourceValue element : lookupChain) {
+ if (element == null) {
+ continue;
+ }
+ String value = element.getValue();
+ if (value == null) {
+ continue;
+ }
+ String text = value;
+ if (text.equals(prev)) {
+ continue;
+ }
+
+ sb.append(" => ");
+
+ // Strip paths
+ if (!(text.startsWith(PREFIX_THEME_REF) || text.startsWith(PREFIX_RESOURCE_REF))) {
+ int end = Math.max(text.lastIndexOf('/'), text.lastIndexOf('\\'));
+ if (end != -1) {
+ text = text.substring(end + 1);
+ }
+ }
+ sb.append(text);
+
+ prev = value;
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Interface implemented by clients of the {@link ResourceItemResolver} which allows
+ * it to lazily look up the project resources, the framework resources and optionally
+ * to provide a fully configured resource resolver, if any
+ */
+ public interface ResourceProvider {
+ @Nullable ResourceResolver getResolver(boolean createIfNecessary);
+ @Nullable ResourceRepository getFrameworkResources();
+ @Nullable AbstractResourceRepository getAppResources();
+ }
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/resources/ResourceRepository.java b/sdk-common/src/main/java/com/android/ide/common/resources/ResourceRepository.java
index ee6ef4a..61fe663 100755
--- a/sdk-common/src/main/java/com/android/ide/common/resources/ResourceRepository.java
+++ b/sdk-common/src/main/java/com/android/ide/common/resources/ResourceRepository.java
@@ -35,7 +35,6 @@
import com.android.resources.FolderTypeRelationship;
import com.android.resources.ResourceFolderType;
import com.android.resources.ResourceType;
-import com.android.utils.Pair;
import java.io.File;
import java.util.ArrayList;
@@ -611,10 +610,9 @@
if (value != null) {
String v = value.getValue();
if (v != null) {
- Pair<ResourceType, String> pair = parseResource(v);
- if (pair != null) {
- return getMatchingFile(pair.getSecond(), pair.getFirst(),
- config);
+ ResourceUrl url = ResourceUrl.parse(v);
+ if (url != null) {
+ return getMatchingFile(url.name, url.type, config);
} else {
// Looks like the resource value is pointing to a file
// It's most likely one of the source files for this
@@ -923,63 +921,5 @@
return null;
}
-
- /**
- * Return the resource type of the given url, and the resource name
- *
- * @param url the resource url to be parsed
- * @return a pair of the resource type and the resource name
- */
- public static Pair<ResourceType,String> parseResource(String url) {
- // Handle theme references
- if (url.startsWith(PREFIX_THEME_REF)) {
- String remainder = url.substring(PREFIX_THEME_REF.length());
- if (url.startsWith(ATTR_REF_PREFIX)) {
- url = PREFIX_RESOURCE_REF + url.substring(PREFIX_THEME_REF.length());
- return parseResource(url);
- }
- int colon = url.indexOf(':');
- if (colon != -1) {
- // Convert from ?android:progressBarStyleBig to ?android:attr/progressBarStyleBig
- if (remainder.indexOf('/', colon) == -1) {
- remainder = remainder.substring(0, colon) + RESOURCE_CLZ_ATTR + '/'
- + remainder.substring(colon);
- }
- url = PREFIX_RESOURCE_REF + remainder;
- return parseResource(url);
- } else {
- int slash = url.indexOf('/');
- if (slash == -1) {
- url = PREFIX_RESOURCE_REF + RESOURCE_CLZ_ATTR + '/' + remainder;
- return parseResource(url);
- }
- }
- }
-
- if (!url.startsWith(PREFIX_RESOURCE_REF)) {
- return null;
- }
- int typeEnd = url.indexOf('/', 1);
- if (typeEnd == -1) {
- return null;
- }
- int nameBegin = typeEnd + 1;
-
- // Skip @ and @+
- int typeBegin = url.startsWith("@+") ? 2 : 1; //$NON-NLS-1$
-
- int colon = url.lastIndexOf(':', typeEnd);
- if (colon != -1) {
- typeBegin = colon + 1;
- }
- String typeName = url.substring(typeBegin, typeEnd);
- ResourceType type = ResourceType.getEnum(typeName);
- if (type == null) {
- return null;
- }
- String name = url.substring(nameBegin);
-
- return Pair.of(type, name);
- }
}
diff --git a/sdk-common/src/main/java/com/android/ide/common/resources/ResourceResolver.java b/sdk-common/src/main/java/com/android/ide/common/resources/ResourceResolver.java
index 7e14eda..c470aa4 100644
--- a/sdk-common/src/main/java/com/android/ide/common/resources/ResourceResolver.java
+++ b/sdk-common/src/main/java/com/android/ide/common/resources/ResourceResolver.java
@@ -16,11 +16,9 @@
package com.android.ide.common.resources;
-import static com.android.SdkConstants.ANDROID_PREFIX;
-import static com.android.SdkConstants.ANDROID_THEME_PREFIX;
+import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX;
import static com.android.SdkConstants.PREFIX_ANDROID;
import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
-import static com.android.SdkConstants.PREFIX_THEME_REF;
import static com.android.SdkConstants.REFERENCE_STYLE;
import com.android.annotations.NonNull;
@@ -33,11 +31,17 @@
import java.util.Collection;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
import java.util.Map;
+import java.util.Set;
public class ResourceResolver extends RenderResources {
public static final String THEME_NAME = "Theme";
public static final String THEME_NAME_DOT = "Theme.";
+ public static final String XLIFF_NAMESPACE_PREFIX = "urn:oasis:names:tc:xliff:document:";
+ public static final String XLIFF_G_TAG = "g";
+ public static final String ATTR_EXAMPLE = "example";
/**
* Number of indirections we'll follow for resource resolution before assuming there
@@ -47,12 +51,9 @@
private final Map<ResourceType, Map<String, ResourceValue>> mProjectResources;
private final Map<ResourceType, Map<String, ResourceValue>> mFrameworkResources;
-
private final Map<StyleResourceValue, StyleResourceValue> mStyleInheritanceMap =
new HashMap<StyleResourceValue, StyleResourceValue>();
-
private StyleResourceValue mTheme;
-
private FrameworkResourceIdProvider mFrameworkProvider;
private LayoutLog mLogger;
private String mThemeName;
@@ -60,9 +61,12 @@
private ResourceResolver(
Map<ResourceType, Map<String, ResourceValue>> projectResources,
- Map<ResourceType, Map<String, ResourceValue>> frameworkResources) {
+ Map<ResourceType, Map<String, ResourceValue>> frameworkResources,
+ String themeName, boolean isProjectTheme) {
mProjectResources = projectResources;
mFrameworkResources = frameworkResources;
+ mThemeName = themeName;
+ mIsProjectTheme = isProjectTheme;
}
/**
@@ -79,10 +83,9 @@
Map<ResourceType, Map<String, ResourceValue>> frameworkResources,
String themeName, boolean isProjectTheme) {
- ResourceResolver resolver = new ResourceResolver(
- projectResources, frameworkResources);
-
- resolver.computeStyleMaps(themeName, isProjectTheme);
+ ResourceResolver resolver = new ResourceResolver(projectResources, frameworkResources,
+ themeName, isProjectTheme);
+ resolver.computeStyleMaps();
return resolver;
}
@@ -124,7 +127,7 @@
@Override
public StyleResourceValue getTheme(String name, boolean frameworkTheme) {
- ResourceValue theme = null;
+ ResourceValue theme;
if (frameworkTheme) {
Map<String, ResourceValue> frameworkStyleMap = mFrameworkResources.get(
@@ -164,6 +167,7 @@
return getResource(resourceType, resourceName, mProjectResources);
}
+ @SuppressWarnings("deprecation") // Required to support older layoutlib clients
@Override
@Deprecated
public ResourceValue findItemInStyle(StyleResourceValue style, String attrName) {
@@ -181,145 +185,109 @@
@Override
public ResourceValue findItemInStyle(StyleResourceValue style, String itemName,
boolean isFrameworkAttr) {
+ return findItemInStyle(style, itemName, isFrameworkAttr, 0);
+ }
+
+ private ResourceValue findItemInStyle(StyleResourceValue style, String itemName,
+ boolean isFrameworkAttr, int depth) {
ResourceValue item = style.findValue(itemName, isFrameworkAttr);
// if we didn't find it, we look in the parent style (if applicable)
- if (item == null && mStyleInheritanceMap != null) {
+ //noinspection VariableNotUsedInsideIf
+ if (item == null) {
StyleResourceValue parentStyle = mStyleInheritanceMap.get(style);
if (parentStyle != null) {
- return findItemInStyle(parentStyle, itemName, isFrameworkAttr);
+ if (depth >= MAX_RESOURCE_INDIRECTION) {
+ if (mLogger != null) {
+ mLogger.error(LayoutLog.TAG_BROKEN,
+ String.format("Cyclic style parent definitions: %1$s",
+ computeCyclicStyleChain(style)),
+ null);
+ }
+
+ return null;
+ }
+
+ return findItemInStyle(parentStyle, itemName, isFrameworkAttr, depth + 1);
}
}
return item;
}
+ private String computeCyclicStyleChain(StyleResourceValue style) {
+ StringBuilder sb = new StringBuilder(100);
+ appendStyleParents(style, new HashSet<StyleResourceValue>(), 0, sb);
+ return sb.toString();
+ }
+
+ private void appendStyleParents(StyleResourceValue style, Set<StyleResourceValue> seen,
+ int depth, StringBuilder sb) {
+ if (depth >= MAX_RESOURCE_INDIRECTION) {
+ sb.append("...");
+ return;
+ }
+
+ boolean haveSeen = seen.contains(style);
+ seen.add(style);
+
+ sb.append('"');
+ if (style.isFramework()) {
+ sb.append(PREFIX_ANDROID);
+ }
+ sb.append(style.getName());
+ sb.append('"');
+
+ if (haveSeen) {
+ return;
+ }
+
+ StyleResourceValue parentStyle = mStyleInheritanceMap.get(style);
+ if (parentStyle != null) {
+ if (style.getParentStyle() != null) {
+ sb.append(" specifies parent ");
+ } else {
+ sb.append(" implies parent ");
+ }
+
+ appendStyleParents(parentStyle, seen, depth + 1, sb);
+ }
+ }
+
@Override
public ResourceValue findResValue(String reference, boolean forceFrameworkOnly) {
if (reference == null) {
return null;
}
- if (reference.startsWith(PREFIX_THEME_REF)
- && reference.length() > PREFIX_THEME_REF.length()) {
- // no theme? no need to go further!
- if (mTheme == null) {
- return null;
- }
- boolean frameworkOnly = false;
+ ResourceUrl resource = ResourceUrl.parse(reference);
+ if (resource != null && resource.hasValidName()) {
+ if (resource.theme) {
+ // no theme? no need to go further!
+ if (mTheme == null) {
+ return null;
+ }
- // eliminate the prefix from the string
- String originalReference = reference;
- if (reference.startsWith(ANDROID_THEME_PREFIX)) {
- frameworkOnly = true;
- reference = reference.substring(ANDROID_THEME_PREFIX.length());
- } else {
- reference = reference.substring(PREFIX_THEME_REF.length());
- }
-
- // at this point, value can contain type/name (drawable/foo for instance).
- // split it to make sure.
- String[] segments = reference.split("/");
-
- // we look for the referenced item name.
- String referenceName = null;
-
- if (segments.length == 2) {
- // there was a resType in the reference. If it's attr, we ignore it
- // else, we assert for now.
- if (ResourceType.ATTR.getName().equals(segments[0])) {
- referenceName = segments[1];
- } else {
+ if (resource.type != ResourceType.ATTR) {
// At this time, no support for ?type/name where type is not "attr"
return null;
}
+
+ // Now look for the item in the theme, starting with the current one.
+ ResourceValue item = findItemInStyle(mTheme, resource.name,
+ forceFrameworkOnly || resource.framework);
+ if (item == null && mLogger != null) {
+ mLogger.warning(LayoutLog.TAG_RESOURCES_RESOLVE_THEME_ATTR,
+ String.format("Couldn't find theme resource %1$s for the current theme",
+ reference),
+ new ResourceValue(ResourceType.ATTR, reference, resource.framework));
+ }
+
+ return item;
} else {
- // it's just an item name.
- referenceName = segments[0];
-
- // Make sure it looks like a resource name; if not, it could just be a string
- // which starts with a ?
- if (!Character.isJavaIdentifierStart(referenceName.charAt(0))) {
- return null;
- }
- for (int i = 1, n = referenceName.length(); i < n; i++) {
- char c = referenceName.charAt(i);
- if (!Character.isJavaIdentifierPart(c) && c != '.') {
- return null;
- }
- }
+ return findResValue(resource.type, resource.name,
+ forceFrameworkOnly || resource.framework);
}
-
- // now we look for android: in the referenceName in order to support format
- // such as: ?attr/android:name
- if (referenceName.startsWith(PREFIX_ANDROID)) {
- frameworkOnly = true;
- referenceName = referenceName.substring(PREFIX_ANDROID.length());
- }
-
- // Now look for the item in the theme, starting with the current one.
- ResourceValue item = findItemInStyle(mTheme, referenceName,
- forceFrameworkOnly || frameworkOnly);
-
- if (item == null && mLogger != null) {
- mLogger.warning(LayoutLog.TAG_RESOURCES_RESOLVE_THEME_ATTR,
- String.format("Couldn't find theme resource %1$s for the current theme",
- reference),
- new ResourceValue(ResourceType.ATTR, originalReference, frameworkOnly));
- }
-
- return item;
- } else if (reference.startsWith(PREFIX_RESOURCE_REF)) {
- boolean frameworkOnly = false;
-
- // check for the specific null reference value.
- if (REFERENCE_NULL.equals(reference)) {
- return null;
- }
-
- // Eliminate the prefix from the string.
- if (reference.startsWith(ANDROID_PREFIX)) {
- frameworkOnly = true;
- reference = reference.substring(ANDROID_PREFIX.length());
- } else {
- reference = reference.substring(PREFIX_RESOURCE_REF.length());
- }
-
- // at this point, value contains type/[android:]name (drawable/foo for instance)
- String[] segments = reference.split("/");
- if (segments.length != 2) {
- return null;
- }
-
- // now we look for android: in the resource name in order to support format
- // such as: @drawable/android:name
- String referenceName = segments[1];
- if (referenceName.startsWith(PREFIX_ANDROID)) {
- frameworkOnly = true;
- referenceName = referenceName.substring(PREFIX_ANDROID.length());
- }
-
- ResourceType type = ResourceType.getEnum(segments[0]);
-
- // unknown type?
- if (type == null) {
- return null;
- }
-
- // Make sure it looks like a resource name; if not, it could just be a string
- // which starts with a ?
- if (!Character.isJavaIdentifierStart(referenceName.charAt(0))) {
- return null;
- }
- for (int i = 1, n = referenceName.length(); i < n; i++) {
- char c = referenceName.charAt(i);
- if (!Character.isJavaIdentifierPart(c) && c != '.') {
- return null;
- }
- }
-
- return findResValue(type, referenceName,
- forceFrameworkOnly ? true :frameworkOnly);
}
// Looks like the value didn't reference anything. Return null.
@@ -447,17 +415,12 @@
// didn't find the resource anywhere.
return null;
-
}
/**
* Compute style information from the given list of style for the project and framework.
- * @param themeName the name of the current theme.
- * @param isProjectTheme Is this a project theme?
*/
- private void computeStyleMaps(String themeName, boolean isProjectTheme) {
- mThemeName = themeName;
- mIsProjectTheme = isProjectTheme;
+ private void computeStyleMaps() {
Map<String, ResourceValue> projectStyleMap = mProjectResources.get(ResourceType.STYLE);
Map<String, ResourceValue> frameworkStyleMap = mFrameworkResources.get(ResourceType.STYLE);
@@ -465,13 +428,13 @@
ResourceValue theme = null;
// project theme names have been prepended with a *
- if (isProjectTheme) {
+ if (mIsProjectTheme) {
if (projectStyleMap != null) {
- theme = projectStyleMap.get(themeName);
+ theme = projectStyleMap.get(mThemeName);
}
} else {
if (frameworkStyleMap != null) {
- theme = frameworkStyleMap.get(themeName);
+ theme = frameworkStyleMap.get(mThemeName);
}
}
@@ -506,7 +469,6 @@
for (ResourceValue value : styles) {
if (value instanceof StyleResourceValue) {
StyleResourceValue style = (StyleResourceValue)value;
- StyleResourceValue parentStyle = null;
// first look for a specified parent.
String parentName = style.getParentStyle();
@@ -517,7 +479,8 @@
}
if (parentName != null) {
- parentStyle = getStyle(parentName, inProjectStyleMap, inFrameworkStyleMap);
+ StyleResourceValue parentStyle = getStyle(parentName, inProjectStyleMap,
+ inFrameworkStyleMap);
if (parentStyle != null) {
mStyleInheritanceMap.put(style, parentStyle);
@@ -530,7 +493,7 @@
/**
* Computes the name of the parent style, or <code>null</code> if the style is a root style.
*/
- private String getParentName(String styleName) {
+ private static String getParentName(String styleName) {
int index = styleName.lastIndexOf('.');
if (index != -1) {
return styleName.substring(0, index);
@@ -639,4 +602,113 @@
return false;
}
+
+ /**
+ * Returns true if the given {@code themeStyle} extends the theme given by
+ * {@code parentStyle}
+ */
+ public boolean themeExtends(@NonNull String parentStyle, @NonNull String themeStyle) {
+ ResourceValue parentValue = findResValue(parentStyle, parentStyle.startsWith(ANDROID_STYLE_RESOURCE_PREFIX));
+ if (parentValue instanceof StyleResourceValue) {
+ ResourceValue themeValue = findResValue(themeStyle,
+ themeStyle.startsWith(ANDROID_STYLE_RESOURCE_PREFIX));
+ if (themeValue == parentValue) {
+ return true;
+ }
+ if (themeValue instanceof StyleResourceValue) {
+ return themeIsParentOf((StyleResourceValue) parentValue,
+ (StyleResourceValue) themeValue);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Creates a new {@link ResourceResolver} which records all resource resolution
+ * lookups into the given list. Note that it is the responsibility of the caller
+ * to clear/reset the list between subsequent lookup operations.
+ *
+ * @param lookupChain the list to write resource lookups into
+ * @return a new {@link ResourceResolver}
+ */
+ public ResourceResolver createRecorder(List<ResourceValue> lookupChain) {
+ ResourceResolver resolver = new RecordingResourceResolver(
+ lookupChain, mProjectResources, mFrameworkResources, mThemeName, mIsProjectTheme);
+ resolver.mFrameworkProvider = mFrameworkProvider;
+ resolver.mLogger = mLogger;
+ resolver.mTheme = mTheme;
+ resolver.mStyleInheritanceMap.putAll(mStyleInheritanceMap);
+ return resolver;
+ }
+
+ private static class RecordingResourceResolver extends ResourceResolver {
+ @NonNull private List<ResourceValue> mLookupChain;
+
+ private RecordingResourceResolver(
+ @NonNull List<ResourceValue> lookupChain,
+ @NonNull Map<ResourceType, Map<String, ResourceValue>> projectResources,
+ @NonNull Map<ResourceType, Map<String, ResourceValue>> frameworkResources,
+ @NonNull String themeName, boolean isProjectTheme) {
+ super(projectResources, frameworkResources, themeName, isProjectTheme);
+ mLookupChain = lookupChain;
+ }
+
+ @Override
+ public ResourceValue resolveResValue(ResourceValue resValue) {
+ mLookupChain.add(resValue);
+
+ return super.resolveResValue(resValue);
+ }
+
+ @Override
+ public ResourceValue findResValue(String reference, boolean forceFrameworkOnly) {
+ if (!mLookupChain.isEmpty() && reference.startsWith(PREFIX_RESOURCE_REF)) {
+ ResourceValue prev = mLookupChain.get(mLookupChain.size() - 1);
+ if (!reference.equals(prev.getValue())) {
+ ResourceValue next = new ResourceValue(prev.getResourceType(), prev.getName(),
+ prev.isFramework());
+ next.setValue(reference);
+ mLookupChain.add(next);
+ }
+ }
+
+ ResourceValue resValue = super.findResValue(reference, forceFrameworkOnly);
+
+ if (resValue != null) {
+ mLookupChain.add(resValue);
+ }
+
+ return resValue;
+ }
+
+ @Override
+ public ResourceValue findItemInStyle(StyleResourceValue style, String itemName,
+ boolean isFrameworkAttr) {
+ ResourceValue value = super.findItemInStyle(style, itemName, isFrameworkAttr);
+ if (value != null) {
+ mLookupChain.add(value);
+ }
+ return value;
+ }
+
+ @Override
+ public ResourceValue findItemInTheme(String attrName, boolean isFrameworkAttr) {
+ ResourceValue value = super.findItemInTheme(attrName, isFrameworkAttr);
+ if (value != null) {
+ mLookupChain.add(value);
+ }
+ return value;
+ }
+
+ @Override
+ public ResourceValue resolveValue(ResourceType type, String name, String value,
+ boolean isFrameworkValue) {
+ ResourceValue resourceValue = super.resolveValue(type, name, value, isFrameworkValue);
+ if (resourceValue != null) {
+ mLookupChain.add(resourceValue);
+ }
+ return resourceValue;
+ }
+ }
}
diff --git a/sdk-common/src/main/java/com/android/ide/common/resources/ResourceUrl.java b/sdk-common/src/main/java/com/android/ide/common/resources/ResourceUrl.java
new file mode 100644
index 0000000..097760b
--- /dev/null
+++ b/sdk-common/src/main/java/com/android/ide/common/resources/ResourceUrl.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.common.resources;
+
+import static com.android.SdkConstants.ANDROID_NS_NAME;
+import static com.android.SdkConstants.ATTR_REF_PREFIX;
+import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
+import static com.android.SdkConstants.PREFIX_THEME_REF;
+import static com.android.SdkConstants.RESOURCE_CLZ_ATTR;
+import static com.android.ide.common.rendering.api.RenderResources.REFERENCE_NULL;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.resources.ResourceType;
+
+/**
+ * A {@linkplain ResourceUrl} represents a parsed resource url such as {@code @string/foo} or
+ * {@code ?android:attr/bar}
+ */
+public class ResourceUrl {
+ /** Type of resource */
+ @NonNull public final ResourceType type;
+
+ /** Name of resource */
+ @NonNull public final String name;
+
+ /** If true, the resource is in the android: framework */
+ public final boolean framework;
+
+ /** Whether an id resource is of the form {@code @+id} rather than just {@code @id} */
+ public final boolean create;
+
+ /** Whether this is a theme resource reference */
+ public boolean theme;
+
+ private ResourceUrl(@NonNull ResourceType type, @NonNull String name,
+ boolean framework, boolean create) {
+ this.type = type;
+ this.name = name;
+ this.framework = framework;
+ this.create = create;
+ }
+
+ /**
+ * Creates a new resource URL. Normally constructed via {@link #parse(String)}.
+ *
+ * @param type the resource type
+ * @param name the name
+ * @param framework whether it's a framework resource
+ * @param create if it's an id resource, whether it's of the form {@code @+id}
+ */
+ public static ResourceUrl create(@NonNull ResourceType type, @NonNull String name,
+ boolean framework, boolean create) {
+ return new ResourceUrl(type, name, framework, create);
+ }
+
+ /**
+ * Return the resource type of the given url, and the resource name
+ *
+ * @param url the resource url to be parsed
+ * @return a pair of the resource type and the resource name
+ */
+ @Nullable
+ public static ResourceUrl parse(@NonNull String url) {
+ // Handle theme references
+ if (url.startsWith(PREFIX_THEME_REF)) {
+ String remainder = url.substring(PREFIX_THEME_REF.length());
+ if (url.startsWith(ATTR_REF_PREFIX)) {
+ url = PREFIX_RESOURCE_REF + url.substring(PREFIX_THEME_REF.length());
+ return setTheme(parse(url));
+ }
+ int colon = url.indexOf(':');
+ if (colon != -1) {
+ // Convert from ?android:progressBarStyleBig to ?android:attr/progressBarStyleBig
+ if (remainder.indexOf('/', colon) == -1) {
+ remainder = remainder.substring(0, colon) + RESOURCE_CLZ_ATTR + '/'
+ + remainder.substring(colon);
+ }
+ url = PREFIX_RESOURCE_REF + remainder;
+ return setTheme(parse(url));
+ } else {
+ int slash = url.indexOf('/');
+ if (slash == -1) {
+ url = PREFIX_RESOURCE_REF + RESOURCE_CLZ_ATTR + '/' + remainder;
+ return setTheme(parse(url));
+ }
+ }
+ }
+
+ if (!url.startsWith(PREFIX_RESOURCE_REF) || url.equals(REFERENCE_NULL)) {
+ return null;
+ }
+
+ int typeEnd = url.indexOf('/', 1);
+ if (typeEnd == -1) {
+ return null;
+ }
+ int nameBegin = typeEnd + 1;
+
+ // Skip @ and @+
+ boolean create = url.startsWith("@+");
+ int typeBegin = create ? 2 : 1;
+
+ int colon = url.lastIndexOf(':', typeEnd);
+ boolean framework = false;
+ if (colon != -1) {
+ if (url.startsWith(ANDROID_NS_NAME, typeBegin)) {
+ framework = true;
+ }
+ typeBegin = colon + 1;
+ }
+ String typeName = url.substring(typeBegin, typeEnd);
+ ResourceType type = ResourceType.getEnum(typeName);
+ if (type == null) {
+ return null;
+ }
+ String name = url.substring(nameBegin);
+ return new ResourceUrl(type, name, framework, create);
+ }
+
+ /** Marks the given url, if any, as corresponding to a theme attribute */
+ @Nullable
+ private static ResourceUrl setTheme(@Nullable ResourceUrl url) {
+ if (url != null) {
+ url.theme = true;
+ }
+ return url;
+ }
+
+ /**
+ * Checks whether this resource has a valid name. Used when parsing data that isn't
+ * necessarily known to be a valid resource; for example, "?attr/hello world"
+ */
+ public boolean hasValidName() {
+ // Make sure it looks like a resource name; if not, it could just be a string
+ // which starts with a ?, etc.
+ if (name.isEmpty()) {
+ return false;
+ }
+
+ if (!Character.isJavaIdentifierStart(name.charAt(0))) {
+ return false;
+ }
+ for (int i = 1, n = name.length(); i < n; i++) {
+ char c = name.charAt(i);
+ if (!Character.isJavaIdentifierPart(c) && c != '.') {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(theme ? PREFIX_THEME_REF : PREFIX_RESOURCE_REF);
+ if (create) {
+ sb.append('+');
+ }
+ if (framework) {
+ sb.append(ANDROID_NS_NAME);
+ sb.append(':');
+ }
+ sb.append(type.getName());
+ sb.append('/');
+ sb.append(name);
+ return sb.toString();
+ }
+
+ @SuppressWarnings("RedundantIfStatement")
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ ResourceUrl that = (ResourceUrl) o;
+
+ if (create != that.create) {
+ return false;
+ }
+ if (framework != that.framework) {
+ return false;
+ }
+ if (!name.equals(that.name)) {
+ return false;
+ }
+ if (type != that.type) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = type.hashCode();
+ result = 31 * result + name.hashCode();
+ result = 31 * result + (framework ? 1 : 0);
+ result = 31 * result + (create ? 1 : 0);
+ return result;
+ }
+}
diff --git a/sdk-common/src/main/java/com/android/ide/common/resources/configuration/DeviceConfigHelper.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/DeviceConfigHelper.java
index 27eaa01..98cb979 100644
--- a/sdk-common/src/main/java/com/android/ide/common/resources/configuration/DeviceConfigHelper.java
+++ b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/DeviceConfigHelper.java
@@ -71,6 +71,7 @@
config.setNightModeQualifier(new NightModeQualifier(NightMode.NOTNIGHT));
config.setCountryCodeQualifier(new CountryCodeQualifier());
config.setLanguageQualifier(new LanguageQualifier());
+ config.setLayoutDirectionQualifier(new LayoutDirectionQualifier());
config.setNetworkCodeQualifier(new NetworkCodeQualifier());
config.setRegionQualifier(new RegionQualifier());
config.setVersionQualifier(new VersionQualifier());
diff --git a/sdk-common/src/main/java/com/android/ide/common/resources/configuration/FolderConfiguration.java b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/FolderConfiguration.java
index 96031fc..fced184 100644
--- a/sdk-common/src/main/java/com/android/ide/common/resources/configuration/FolderConfiguration.java
+++ b/sdk-common/src/main/java/com/android/ide/common/resources/configuration/FolderConfiguration.java
@@ -656,7 +656,26 @@
return result.toString();
}
- /**
+ /**
+ * Returns the folder configuration as a unique key
+ */
+ public String getUniqueKey() {
+ StringBuilder result = new StringBuilder(100);
+
+ for (ResourceQualifier qualifier : mQualifiers) {
+ if (qualifier != null) {
+ String segment = qualifier.getFolderSegment();
+ if (segment != null && !segment.isEmpty()) {
+ result.append(SdkConstants.RES_QUALIFIER_SEP);
+ result.append(segment);
+ }
+ }
+ }
+
+ return result.toString();
+ }
+
+ /**
* Returns {@link #toDisplayString()}.
*/
@Override
@@ -730,7 +749,7 @@
return "default";
}
- StringBuilder result = new StringBuilder();
+ StringBuilder result = new StringBuilder(100);
int index = 0;
// pre- language/region qualifiers
diff --git a/sdk-common/src/main/java/com/android/ide/common/sdk/SdkVersionInfo.java b/sdk-common/src/main/java/com/android/ide/common/sdk/SdkVersionInfo.java
index dbe57e1..70b5749 100644
--- a/sdk-common/src/main/java/com/android/ide/common/sdk/SdkVersionInfo.java
+++ b/sdk-common/src/main/java/com/android/ide/common/sdk/SdkVersionInfo.java
@@ -17,6 +17,8 @@
import com.android.annotations.Nullable;
+import java.util.Locale;
+
/** Information about available SDK Versions */
public class SdkVersionInfo {
/**
@@ -26,7 +28,7 @@
* release. This number is used as a baseline and any more recent platforms
* found can be used to increase the highest known number.
*/
- public static final int HIGHEST_KNOWN_API = 17;
+ public static final int HIGHEST_KNOWN_API = 19;
/**
* Returns the Android version and code name of the given API level, or null
@@ -57,6 +59,8 @@
case 15: return "API 15: Android 4.0.3 (IceCreamSandwich)";
case 16: return "API 16: Android 4.1 (Jelly Bean)";
case 17: return "API 17: Android 4.2 (Jelly Bean)";
+ case 18: return "API 18: Android 4.3 (Jelly Bean)";
+ case 19: return "API 19: Android 4.4 (KitKat)";
// If you add more versions here, also update #getBuildCodes and
// #HIGHEST_KNOWN_API
@@ -94,10 +98,108 @@
case 15: return "ICE_CREAM_SANDWICH_MR1"; //$NON-NLS-1$
case 16: return "JELLY_BEAN"; //$NON-NLS-1$
case 17: return "JELLY_BEAN_MR1"; //$NON-NLS-1$
+ case 18: return "JELLY_BEAN_MR2"; //$NON-NLS-1$
+ case 19: return "KITKAT"; //$NON-NLS-1$
// If you add more versions here, also update #getAndroidName and
// #HIGHEST_KNOWN_API
}
return null;
}
+
+ /**
+ * Returns the API level of the given build code (e.g. JELLY_BEAN_MR1 => 17), or -1 if not
+ * recognized
+ *
+ * @param buildCode the build code name (not case sensitive)
+ * @param recognizeUnknowns if true, treat an unrecognized code name as a newly released
+ * platform the tools are not yet aware of, and set its API level to
+ * some higher number than all the currently known API versions
+ * @return the API level, or -1 if not recognized (unless recognizeUnknowns is true, in which
+ * {@link #HIGHEST_KNOWN_API} plus one is returned
+ */
+ public static int getApiByBuildCode(String buildCode, boolean recognizeUnknowns) {
+ for (int api = 1; api <= HIGHEST_KNOWN_API; api++) {
+ String code = getBuildCode(api);
+ if (code != null && code.equalsIgnoreCase(buildCode)) {
+ return api;
+ }
+ }
+
+ return recognizeUnknowns ? HIGHEST_KNOWN_API + 1 : -1;
+ }
+
+ /**
+ * Returns the API level of the given preview code name (e.g. JellyBeanMR2 => 17), or -1 if not
+ * recognized
+ *
+ * @param previewName the preview name (not case sensitive)
+ * @param recognizeUnknowns if true, treat an unrecognized code name as a newly released
+ * platform the tools are not yet aware of, and set its API level to
+ * some higher number than all the currently known API versions
+ * @return the API level, or -1 if not recognized (unless recognizeUnknowns is true, in which
+ * {@link #HIGHEST_KNOWN_API} plus one is returned
+ */
+ public static int getApiByPreviewName(String previewName, boolean recognizeUnknowns) {
+ // JellyBean => JELLY_BEAN
+ String codeName = camelCaseToUnderlines(previewName).toUpperCase(Locale.US);
+ return getApiByBuildCode(codeName, recognizeUnknowns);
+ }
+
+ /**
+ * Converts a CamelCase word into an underlined_word
+ *
+ * @param string the CamelCase version of the word
+ * @return the underlined version of the word
+ */
+ public static String camelCaseToUnderlines(String string) {
+ if (string.isEmpty()) {
+ return string;
+ }
+
+ StringBuilder sb = new StringBuilder(2 * string.length());
+ int n = string.length();
+ boolean lastWasUpperCase = Character.isUpperCase(string.charAt(0));
+ for (int i = 0; i < n; i++) {
+ char c = string.charAt(i);
+ boolean isUpperCase = Character.isUpperCase(c);
+ if (isUpperCase && !lastWasUpperCase) {
+ sb.append('_');
+ }
+ lastWasUpperCase = isUpperCase;
+ c = Character.toLowerCase(c);
+ sb.append(c);
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Converts an underlined_word into a CamelCase word
+ *
+ * @param string the underlined word to convert
+ * @return the CamelCase version of the word
+ */
+ public static String underlinesToCamelCase(String string) {
+ StringBuilder sb = new StringBuilder(string.length());
+ int n = string.length();
+
+ int i = 0;
+ @SuppressWarnings("SpellCheckingInspection")
+ boolean upcaseNext = true;
+ for (; i < n; i++) {
+ char c = string.charAt(i);
+ if (c == '_') {
+ upcaseNext = true;
+ } else {
+ if (upcaseNext) {
+ c = Character.toUpperCase(c);
+ }
+ upcaseNext = false;
+ sb.append(c);
+ }
+ }
+
+ return sb.toString();
+ }
}
diff --git a/sdk-common/src/main/java/com/android/ide/common/xml/XmlPrettyPrinter.java b/sdk-common/src/main/java/com/android/ide/common/xml/XmlPrettyPrinter.java
index b6b013c..ff97dcc 100644
--- a/sdk-common/src/main/java/com/android/ide/common/xml/XmlPrettyPrinter.java
+++ b/sdk-common/src/main/java/com/android/ide/common/xml/XmlPrettyPrinter.java
@@ -47,7 +47,6 @@
* Visitor which walks over the subtree of the DOM to be formatted and pretty prints
* the DOM into the given {@link StringBuilder}
*/
-@SuppressWarnings("restriction")
public class XmlPrettyPrinter {
/** The style to print the XML in */
@@ -62,6 +61,7 @@
/** Whether the visitor is currently in range */
private boolean mInRange;
/** Output builder */
+ @SuppressWarnings("StringBufferField")
private StringBuilder mOut;
/** String to insert for a single indentation level */
private String mIndentString;
@@ -316,7 +316,7 @@
}
case Node.CDATA_SECTION_NODE:
- printCharacterData(depth, node);
+ printCharacterData(node);
break;
case Node.PROCESSING_INSTRUCTION_NODE:
@@ -361,6 +361,7 @@
}
@Nullable
+ @SuppressWarnings("MethodMayBeStatic") // Intentionally instance method so it can be overridden
protected String getSource(@NonNull Node node) {
return null;
}
@@ -373,7 +374,7 @@
}
}
- private void printCharacterData(int depth, Node node) {
+ private void printCharacterData(Node node) {
String nodeValue = node.getNodeValue();
boolean separateLine = nodeValue.indexOf('\n') != -1;
if (separateLine && !endsWithLineSeparator()) {
@@ -404,7 +405,7 @@
// Most text nodes are just whitespace for formatting (which we're replacing)
// so look for actual text content and extract that part out
String trimmed = text.trim();
- if (trimmed.length() > 0) {
+ if (!trimmed.isEmpty()) {
// TODO: Reformat the contents if it is too wide?
// Note that we append the actual text content, NOT the trimmed content,
@@ -436,7 +437,10 @@
if (firstSuffixNewline == -1) {
firstSuffixNewline = text.length();
}
- text = text.substring(lastPrefixNewline + 1, firstSuffixNewline);
+ int stripFrom = lastPrefixNewline + 1;
+ if (firstSuffixNewline >= stripFrom) {
+ text = text.substring(stripFrom, firstSuffixNewline);
+ }
}
if (escape) {
@@ -521,7 +525,7 @@
}
if (newLines >= 2) {
mOut.append(mLineSeparator);
- } else if (text.trim().length() == 0 && curr.getPreviousSibling() == null) {
+ } else if (text.trim().isEmpty() && curr.getPreviousSibling() == null) {
// Comment before first child in node
mOut.append(mLineSeparator);
}
@@ -774,7 +778,7 @@
if (attributeCount > 0) {
// Sort the attributes
List<Attr> attributeList = new ArrayList<Attr>();
- for (int i = 0, n = attributeCount; i < n; i++) {
+ for (int i = 0; i < attributeCount; i++) {
attributeList.add((Attr) attributes.item(i));
}
Comparator<Attr> comparator = mPrefs.getAttributeComparator();
@@ -898,7 +902,7 @@
if (curr == null
|| curr.getNodeType() == Node.ELEMENT_NODE
|| (curr.getNodeType() == Node.TEXT_NODE
- && curr.getNodeValue().trim().length() == 0
+ && curr.getNodeValue().trim().isEmpty()
&& (curr.getPreviousSibling() == null
|| curr.getPreviousSibling().getNodeType()
== Node.ELEMENT_NODE))) {
@@ -918,7 +922,7 @@
break;
} else if (nodeType == Node.TEXT_NODE) {
String text = curr.getNodeValue();
- if (text.trim().length() > 0) {
+ if (!text.trim().isEmpty()) {
break;
}
// If there is just whitespace, continue looking for a previous sibling
@@ -1086,6 +1090,7 @@
* @param element the element to test
* @return true if this element should be an empty tag
*/
+ @SuppressWarnings("MethodMayBeStatic") // Intentionally instance method so it can be overridden
protected boolean isEmptyTag(Element element) {
if (element.getFirstChild() != null) {
return false;
diff --git a/sdk-common/src/test/java/com/android/ide/common/rendering/HardwareConfigHelperTest.java b/sdk-common/src/test/java/com/android/ide/common/rendering/HardwareConfigHelperTest.java
new file mode 100644
index 0000000..2c4b354
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/rendering/HardwareConfigHelperTest.java
@@ -0,0 +1,101 @@
+package com.android.ide.common.rendering;
+
+import static com.android.ide.common.rendering.HardwareConfigHelper.getGenericLabel;
+import static com.android.ide.common.rendering.HardwareConfigHelper.getNexusLabel;
+import static com.android.ide.common.rendering.HardwareConfigHelper.isGeneric;
+import static com.android.ide.common.rendering.HardwareConfigHelper.isNexus;
+import static com.android.ide.common.rendering.HardwareConfigHelper.nexusRank;
+
+import com.android.sdklib.devices.Device;
+import com.android.sdklib.devices.DeviceManager;
+import com.android.utils.StdLogger;
+import com.google.common.collect.Lists;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public class HardwareConfigHelperTest extends TestCase {
+ private static DeviceManager getDeviceManager() {
+ return DeviceManager.createInstance(null, new StdLogger(StdLogger.Level.INFO));
+ }
+
+ public void testNexus() {
+ DeviceManager deviceManager = getDeviceManager();
+ Device n1 = deviceManager.getDevice("Nexus One", "Google");
+ assertNotNull(n1);
+ assertEquals("Nexus One", n1.getId());
+ //noinspection deprecation
+ assertSame(n1.getId(), n1.getName());
+ assertEquals("Nexus One", n1.getDisplayName());
+ assertTrue(isNexus(n1));
+
+ assertEquals("Nexus One (3.7\", 480 \u00d7 800: hdpi)", getNexusLabel(n1));
+ assertFalse(isGeneric(n1));
+ assertFalse(isGeneric(n1));
+ }
+
+ public void testNexus7() {
+ DeviceManager deviceManager = getDeviceManager();
+ Device n7 = deviceManager.getDevice("Nexus 7", "Google");
+ Device n7b = deviceManager.getDevice("Nexus 7 2013", "Google");
+ assertNotNull(n7);
+ assertNotNull(n7b);
+ assertEquals("Nexus 7 (2012)", n7.getDisplayName());
+ assertEquals("Nexus 7", n7b.getDisplayName());
+ assertTrue(isNexus(n7));
+ assertTrue(isNexus(n7b));
+ assertTrue(nexusRank(n7b) > nexusRank(n7));
+
+ assertEquals("Nexus 7 (2012) (7.0\", 800 × 1280: tvdpi)", getNexusLabel(n7));
+ assertEquals("Nexus 7 (7.0\", 1200 × 1920: xhdpi)", getNexusLabel(n7b));
+ assertFalse(isGeneric(n7));
+ assertFalse(isGeneric(n7));
+ }
+
+ public void testGeneric() {
+ DeviceManager deviceManager = getDeviceManager();
+ Device qvga = deviceManager.getDevice("2.7in QVGA", "Generic");
+ assertNotNull(qvga);
+ assertEquals("2.7\" QVGA", qvga.getDisplayName());
+ assertEquals("2.7in QVGA", qvga.getId());
+ assertFalse(isNexus(qvga));
+ assertEquals(" 2.7\" QVGA (240 \u00d7 320: ldpi)", getGenericLabel(qvga));
+ assertTrue(isGeneric(qvga));
+ assertFalse(isNexus(qvga));
+ }
+
+ public void testNexusRank() {
+ List<Device> devices = Lists.newArrayList();
+ DeviceManager deviceManager = getDeviceManager();
+ for (String id : new String[] { "Nexus 7 2013", "Nexus 5", "Nexus 10", "Nexus 4", "Nexus 7",
+ "Galaxy Nexus", "Nexus S", "Nexus One"}) {
+ Device device = deviceManager.getDevice(id, "Google");
+ assertNotNull(device);
+ devices.add(device);
+ }
+ Collections.sort(devices, new Comparator<Device>() {
+ @Override
+ public int compare(Device device, Device device2) {
+ return nexusRank(device) - nexusRank(device2);
+ }
+ });
+ List<String> ids = Lists.newArrayList();
+ for (Device device : devices) {
+ ids.add(device.getId());
+ }
+ assertEquals(Arrays.asList(
+ "Nexus One",
+ "Nexus S",
+ "Galaxy Nexus",
+ "Nexus 7",
+ "Nexus 10",
+ "Nexus 4",
+ "Nexus 7 2013",
+ "Nexus 5"
+ ), ids);
+ }
+}
diff --git a/sdk-common/src/test/java/com/android/ide/common/rendering/RenderSecurityManagerTest.java b/sdk-common/src/test/java/com/android/ide/common/rendering/RenderSecurityManagerTest.java
new file mode 100644
index 0000000..b4b0211
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/rendering/RenderSecurityManagerTest.java
@@ -0,0 +1,710 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.common.rendering;
+
+import static java.io.File.separator;
+
+import com.android.ide.common.res2.RecordingLogger;
+import com.android.testutils.TestUtils;
+import com.android.utils.SdkUtils;
+import com.google.common.io.Files;
+
+import junit.framework.TestCase;
+
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FilePermission;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.security.Permission;
+import java.util.Collections;
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.imageio.ImageIO;
+import javax.swing.*;
+
+public class RenderSecurityManagerTest extends TestCase {
+
+ private Object myCredential = new Object();
+
+ public void testExec() throws Exception {
+ assertNull(RenderSecurityManager.getCurrent());
+ RenderSecurityManager manager = new RenderSecurityManager(null, null);
+ RecordingLogger logger = new RecordingLogger();
+ manager.setLogger(logger);
+ try {
+ assertNull(RenderSecurityManager.getCurrent());
+ manager.setActive(true, myCredential);
+ assertSame(manager, RenderSecurityManager.getCurrent());
+ if (new File("/bin/ls").exists()) {
+ Runtime.getRuntime().exec("/bin/ls");
+ } else {
+ manager.checkExec("/bin/ls");
+ }
+ fail("Should have thrown security exception");
+ } catch (SecurityException exception) {
+ //noinspection ConstantConditions
+ assertEquals(
+ RenderSecurityManager.RESTRICT_READS ?
+ "Read access not allowed during rendering (/bin/ls)" :
+ "Exec access not allowed during rendering (/bin/ls)",
+ exception.toString());
+ // pass
+ } finally {
+ manager.dispose(myCredential);
+ assertNull(RenderSecurityManager.getCurrent());
+ assertNull(System.getSecurityManager());
+ assertEquals(Collections.<String>emptyList(), logger.getWarningMsgs());
+ }
+ }
+
+ public void testSetSecurityManager() throws Exception {
+ RenderSecurityManager manager = new RenderSecurityManager(null, null);
+ try {
+ manager.setActive(true, myCredential);
+ System.setSecurityManager(null);
+ fail("Should have thrown security exception");
+ } catch (SecurityException exception) {
+ assertEquals("Security access not allowed during rendering", exception.toString());
+ // pass
+ } finally {
+ manager.dispose(myCredential);
+ }
+ }
+
+ public void testReadWrite() throws Exception {
+ RenderSecurityManager manager = new RenderSecurityManager(null, null);
+ try {
+ manager.setActive(true, myCredential);
+ manager.checkPermission(new FilePermission("/foo", "read,write"));
+ fail("Should have thrown security exception");
+ } catch (SecurityException exception) {
+ assertEquals("Write access not allowed during rendering (/foo)", exception.toString());
+ // pass
+ } finally {
+ manager.dispose(myCredential);
+ }
+ }
+
+ public void testExecute() throws Exception {
+ RenderSecurityManager manager = new RenderSecurityManager(null, null);
+ try {
+ manager.setActive(true, myCredential);
+ manager.checkPermission(new FilePermission("/foo", "execute"));
+ fail("Should have thrown security exception");
+ } catch (SecurityException exception) {
+ assertEquals("Write access not allowed during rendering (/foo)", exception.toString());
+ // pass
+ } finally {
+ manager.dispose(myCredential);
+ }
+ }
+
+ public void testDelete() throws Exception {
+ RenderSecurityManager manager = new RenderSecurityManager(null, null);
+ try {
+ manager.setActive(true, myCredential);
+ manager.checkPermission(new FilePermission("/foo", "delete"));
+ fail("Should have thrown security exception");
+ } catch (SecurityException exception) {
+ assertEquals("Write access not allowed during rendering (/foo)", exception.toString());
+ // pass
+ } finally {
+ manager.dispose(myCredential);
+ }
+ }
+
+ public void testLoadLibrary() throws Exception {
+ RenderSecurityManager manager = new RenderSecurityManager(null, null);
+ try {
+ manager.setActive(true, myCredential);
+
+ // Unit test only runs on OSX
+ if (SdkUtils.startsWithIgnoreCase(System.getProperty("os.name"), "Mac")
+ && new File("/usr/lib/libc.dylib").exists()) {
+ System.load("/usr/lib/libc.dylib");
+ fail("Should have thrown security exception");
+ }
+ } catch (SecurityException exception) {
+ assertEquals("Link access not allowed during rendering (/usr/lib/libc.dylib)",
+ exception.toString());
+ // pass
+ } finally {
+ manager.dispose(myCredential);
+ }
+ }
+
+ public void testAllowedLoadLibrary() throws Exception {
+ RenderSecurityManager manager = new RenderSecurityManager(null, null);
+ try {
+ manager.setActive(true, myCredential);
+
+ System.loadLibrary("fontmanager");
+ } catch (UnsatisfiedLinkError e) {
+ // pass - library may not be present on all JDKs
+ } finally {
+ manager.dispose(myCredential);
+ }
+ }
+
+ public void testInvalidRead() throws Exception {
+ RenderSecurityManager manager = new RenderSecurityManager(null, null);
+ try {
+ manager.setActive(true, myCredential);
+
+ if (RenderSecurityManager.RESTRICT_READS) {
+ try {
+ File file = new File(System.getProperty("user.home"));
+ //noinspection ResultOfMethodCallIgnored
+ file.lastModified();
+
+ fail("Should have thrown security exception");
+ } catch (SecurityException exception) {
+ assertEquals("Read access not allowed during rendering (" +
+ System.getProperty("user.home") + ")", exception.toString());
+ // pass
+ }
+ } else {
+ try {
+ File file = new File(System.getProperty("user.home"));
+ //noinspection ResultOfMethodCallIgnored
+ file.lastModified();
+ } catch (SecurityException exception) {
+ fail("Reading should be allowed");
+ }
+ }
+ } finally {
+ manager.dispose(myCredential);
+ }
+ }
+
+ public void testInvalidPropertyWrite() throws Exception {
+ RenderSecurityManager manager = new RenderSecurityManager(null, null);
+ try {
+ manager.setActive(true, myCredential);
+
+ // Try to make java.io.tmpdir point to user.home to grant myself access:
+ String userHome = System.getProperty("user.home");
+ System.setProperty("java.io.tmpdir", userHome);
+
+ fail("Should have thrown security exception");
+ } catch (SecurityException exception) {
+ assertEquals("Write access not allowed during rendering (java.io.tmpdir)",
+ exception.toString());
+ // pass
+ } finally {
+ manager.dispose(myCredential);
+ }
+ }
+
+ public void testReadOk() throws Exception {
+ RenderSecurityManager manager = new RenderSecurityManager(null, null);
+ try {
+ manager.setActive(true, myCredential);
+
+ File jdkHome = new File(System.getProperty("java.home"));
+ assertTrue(jdkHome.exists());
+ //noinspection ResultOfMethodCallIgnored
+ File[] files = jdkHome.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (file.isFile()) {
+ Files.toByteArray(file);
+ }
+ }
+ }
+ } finally {
+ manager.dispose(myCredential);
+ }
+ }
+
+ public void testProperties() throws Exception {
+ RenderSecurityManager manager = new RenderSecurityManager(null, null);
+ try {
+ manager.setActive(true, myCredential);
+
+ System.getProperties();
+
+ fail("Should have thrown security exception");
+ } catch (SecurityException exception) {
+ assertEquals("Property access not allowed during rendering", exception.toString());
+ // pass
+ } finally {
+ manager.dispose(myCredential);
+ }
+ }
+
+ public void testExit() throws Exception {
+ RenderSecurityManager manager = new RenderSecurityManager(null, null);
+ try {
+ manager.setActive(true, myCredential);
+
+ System.exit(-1);
+
+ fail("Should have thrown security exception");
+ } catch (SecurityException exception) {
+ assertEquals("Exit access not allowed during rendering (-1)", exception.toString());
+ // pass
+ } finally {
+ manager.dispose(myCredential);
+ }
+ }
+
+ public void testThread() throws Exception {
+ final AtomicBoolean failedUnexpectedly = new AtomicBoolean(false);
+ Thread otherThread = new Thread("other") {
+ @Override
+ public void run() {
+ try {
+ assertNull(RenderSecurityManager.getCurrent());
+ System.getProperties();
+ } catch (SecurityException e) {
+ failedUnexpectedly.set(true);
+ }
+ }
+ };
+ RenderSecurityManager manager = new RenderSecurityManager(null, null);
+ try {
+ manager.setActive(true, myCredential);
+
+ // Threads cloned from this one should inherit the same security constraints
+ final AtomicBoolean failedAsExpected = new AtomicBoolean(false);
+ final Thread renderThread = new Thread("render") {
+ @Override
+ public void run() {
+ try {
+ System.getProperties();
+ } catch (SecurityException e) {
+ failedAsExpected.set(true);
+ }
+ }
+ };
+ renderThread.start();
+ renderThread.join();
+ assertTrue(failedAsExpected.get());
+ otherThread.start();
+ otherThread.join();
+ assertFalse(failedUnexpectedly.get());
+ } finally {
+ manager.dispose(myCredential);
+ }
+ }
+
+ public void testActive() throws Exception {
+ RenderSecurityManager manager = new RenderSecurityManager(null, null);
+ try {
+ manager.setActive(true, myCredential);
+
+ try {
+ System.getProperties();
+ fail("Should have thrown security exception");
+ } catch (SecurityException exception) {
+ // pass
+ }
+
+ manager.setActive(false, myCredential);
+
+ try {
+ System.getProperties();
+ } catch (SecurityException exception) {
+ fail(exception.toString());
+ }
+
+ manager.setActive(true, myCredential);
+
+ try {
+ System.getProperties();
+ fail("Should have thrown security exception");
+ } catch (SecurityException exception) {
+ // pass
+ }
+ } finally {
+ manager.dispose(myCredential);
+ }
+ }
+
+ public void testThread2() throws Exception {
+ // Check that when a new thread is created simultaneously from an unrelated
+ // thread during rendering, that new thread does not pick up the security manager.
+ //
+ final CyclicBarrier barrier1 = new CyclicBarrier(2);
+ final CyclicBarrier barrier2 = new CyclicBarrier(2);
+ final CyclicBarrier barrier3 = new CyclicBarrier(4);
+ final CyclicBarrier barrier4 = new CyclicBarrier(4);
+ final CyclicBarrier barrier5 = new CyclicBarrier(4);
+ final CyclicBarrier barrier6 = new CyclicBarrier(4);
+
+ // First the threads reach barrier1. Then from barrier1 to barrier2, thread1
+ // installs the security manager. Then from barrier2 to barrier3, thread2
+ // checks that it does not have any security restrictions, and creates thread3.
+ // Thread1 will ensure that the security manager is working there, and it will
+ // create thread4. Then after barrier3 (where thread3 and thread4 are now also
+ // participating) thread3 will ensure that it too has no security restrictions,
+ // and thread4 will ensure that it does. At barrier4 the security manager gets
+ // uninstalled, and at barrier5 all threads will check that there are no more
+ // restrictions. At barrier6 all threads are done.
+
+ final Thread thread1 = new Thread("render") {
+ @Override
+ public void run() {
+ try {
+ barrier1.await();
+ assertNull(RenderSecurityManager.getCurrent());
+
+ RenderSecurityManager manager = new RenderSecurityManager(null, null);
+ manager.setActive(true, myCredential);
+
+ barrier2.await();
+
+ Thread thread4 = new Thread() {
+ @Override
+ public void run() {
+ try {
+ barrier3.await();
+
+ try {
+ System.getProperties();
+ fail("Should have thrown security exception");
+ } catch (SecurityException e) {
+ // pass
+ }
+
+ barrier4.await();
+ barrier5.await();
+ assertNull(RenderSecurityManager.getCurrent());
+ assertNull(System.getSecurityManager());
+ barrier6.await();
+ } catch (InterruptedException e) {
+ fail(e.toString());
+ } catch (BrokenBarrierException e) {
+ fail(e.toString());
+ }
+ }
+ };
+ thread4.start();
+
+ try {
+ System.getProperties();
+ fail("Should have thrown security exception");
+ } catch (SecurityException e) {
+ // expected
+ }
+
+ barrier3.await();
+ barrier4.await();
+ manager.dispose(myCredential);
+
+ assertNull(RenderSecurityManager.getCurrent());
+ assertNull(System.getSecurityManager());
+
+ barrier5.await();
+ barrier6.await();
+
+ } catch (InterruptedException e) {
+ fail(e.toString());
+ } catch (BrokenBarrierException e) {
+ fail(e.toString());
+ }
+
+ }
+ };
+
+ final Thread thread2 = new Thread("unrelated") {
+ @Override
+ public void run() {
+ try {
+ barrier1.await();
+ assertNull(RenderSecurityManager.getCurrent());
+ barrier2.await();
+ assertNull(RenderSecurityManager.getCurrent());
+ assertNotNull(System.getSecurityManager());
+
+ try {
+ System.getProperties();
+ } catch (SecurityException e) {
+ fail("Should not have been affected by security manager");
+ }
+
+ Thread thread3 = new Thread() {
+ @Override
+ public void run() {
+ try {
+ barrier3.await();
+
+ try {
+ System.getProperties();
+ } catch (SecurityException e) {
+ fail("Should not have been affected by security manager");
+ }
+
+ barrier4.await();
+ barrier5.await();
+ assertNull(RenderSecurityManager.getCurrent());
+ assertNull(System.getSecurityManager());
+ barrier6.await();
+
+ } catch (InterruptedException e) {
+ fail(e.toString());
+ } catch (BrokenBarrierException e) {
+ fail(e.toString());
+ }
+ }
+ };
+ thread3.start();
+
+ barrier3.await();
+ barrier4.await();
+ barrier5.await();
+ assertNull(RenderSecurityManager.getCurrent());
+ assertNull(System.getSecurityManager());
+ barrier6.await();
+
+ } catch (InterruptedException e) {
+ fail(e.toString());
+ } catch (BrokenBarrierException e) {
+ fail(e.toString());
+ }
+
+ }
+ };
+
+ thread1.start();
+ thread2.start();
+ thread1.join();
+ thread2.join();
+ }
+
+ public void testDisabled() throws Exception {
+ assertNull(RenderSecurityManager.getCurrent());
+
+ RenderSecurityManager manager = new RenderSecurityManager(null, null);
+ RenderSecurityManager.sEnabled = false;
+ try {
+ assertNull(RenderSecurityManager.getCurrent());
+ manager.setActive(true, myCredential);
+ assertSame(manager, System.getSecurityManager());
+ if (new File("/bin/ls").exists()) {
+ Runtime.getRuntime().exec("/bin/ls");
+ } else {
+ manager.checkExec("/bin/ls");
+ }
+ } catch (SecurityException exception) {
+ fail("Should have been disabled");
+ } finally {
+ RenderSecurityManager.sEnabled = true;
+ manager.dispose(myCredential);
+ assertNull(RenderSecurityManager.getCurrent());
+ assertNull(System.getSecurityManager());
+ }
+ }
+
+ public void testLogger() throws Exception {
+ assertNull(RenderSecurityManager.getCurrent());
+
+ final CyclicBarrier barrier1 = new CyclicBarrier(2);
+ final CyclicBarrier barrier2 = new CyclicBarrier(2);
+ final CyclicBarrier barrier3 = new CyclicBarrier(2);
+
+ Thread thread = new Thread() {
+ @Override
+ public void run() {
+ try {
+ barrier1.await();
+ barrier2.await();
+
+ System.setSecurityManager(new SecurityManager() {
+ @Override
+ public String toString() {
+ return "MyTestSecurityManager";
+ }
+
+ @Override
+ public void checkPermission(Permission permission) {
+ }
+ });
+
+ barrier3.await();
+ assertNull(RenderSecurityManager.getCurrent());
+ assertNotNull(System.getSecurityManager());
+ assertEquals("MyTestSecurityManager", System.getSecurityManager().toString());
+ } catch (InterruptedException e) {
+ fail(e.toString());
+ } catch (BrokenBarrierException e) {
+ fail(e.toString());
+ }
+ }
+ };
+ thread.start();
+
+ RenderSecurityManager manager = new RenderSecurityManager(null, null);
+ RecordingLogger logger = new RecordingLogger();
+ manager.setLogger(logger);
+ try {
+ barrier1.await();
+ assertNull(RenderSecurityManager.getCurrent());
+ manager.setActive(true, myCredential);
+ assertSame(manager, RenderSecurityManager.getCurrent());
+ barrier2.await();
+ barrier3.await();
+
+ assertNull(RenderSecurityManager.getCurrent());
+ manager.setActive(false, myCredential);
+ assertNull(RenderSecurityManager.getCurrent());
+
+ assertEquals(Collections.singletonList(
+ "RenderSecurityManager being replaced by another thread"),
+ logger.getWarningMsgs());
+ } catch (InterruptedException e) {
+ fail(e.toString());
+ } catch (BrokenBarrierException e) {
+ fail(e.toString());
+ } finally {
+ manager.dispose(myCredential);
+ assertNull(RenderSecurityManager.getCurrent());
+ assertNotNull(System.getSecurityManager());
+ assertEquals("MyTestSecurityManager", System.getSecurityManager().toString());
+ System.setSecurityManager(null);
+ }
+ }
+
+ public void testEnterExitSafeRegion() throws Exception {
+ RenderSecurityManager manager = new RenderSecurityManager(null, null);
+ Object credential = new Object();
+ try {
+ manager.setActive(true, credential);
+
+ boolean token = RenderSecurityManager.enterSafeRegion(credential);
+ manager.checkPermission(new FilePermission("/foo", "execute"));
+ RenderSecurityManager.exitSafeRegion(token);
+
+ assertNotNull(RenderSecurityManager.getCurrent());
+ boolean tokenOuter = RenderSecurityManager.enterSafeRegion(credential);
+ assertNull(RenderSecurityManager.getCurrent());
+ boolean tokenInner = RenderSecurityManager.enterSafeRegion(credential);
+ assertNull(RenderSecurityManager.getCurrent());
+ manager.checkPermission(new FilePermission("/foo", "execute"));
+ assertNull(RenderSecurityManager.getCurrent());
+ manager.checkPermission(new FilePermission("/foo", "execute"));
+ RenderSecurityManager.exitSafeRegion(tokenInner);
+ assertNull(RenderSecurityManager.getCurrent());
+ RenderSecurityManager.exitSafeRegion(tokenOuter);
+ assertNotNull(RenderSecurityManager.getCurrent());
+
+ // Wrong credential
+ Object wrongCredential = new Object();
+ try {
+ token = RenderSecurityManager.enterSafeRegion(wrongCredential);
+ manager.checkPermission(new FilePermission("/foo", "execute"));
+ RenderSecurityManager.exitSafeRegion(token);
+ fail("Should have thrown exception");
+ } catch (SecurityException e) {
+ // pass
+ }
+
+ // Try turning off the security manager
+ try {
+ manager.setActive(false, wrongCredential);
+ } catch (SecurityException e) {
+ // pass
+ }
+ try {
+ manager.setActive(false, null);
+ } catch (SecurityException e) {
+ // pass
+ }
+ try {
+ manager.dispose(wrongCredential);
+ } catch (SecurityException e) {
+ // pass
+ }
+
+ // Try looking up the secret
+ try {
+ Field field = RenderSecurityManager.class.getField("sCredential");
+ field.setAccessible(true);
+ Object secret = field.get(null);
+ manager.dispose(secret);
+ fail("Shouldn't be able to find our way to the credential");
+ } catch (Exception e) {
+ // pass
+ assertEquals("java.lang.NoSuchFieldException: sCredential", e.toString());
+ }
+
+ // Try looking up the secret (with getDeclaredField instead of getField)
+ try {
+ Field field = RenderSecurityManager.class.getDeclaredField("sCredential");
+ field.setAccessible(true);
+ Object secret = field.get(null);
+ manager.dispose(secret);
+ fail("Shouldn't be able to find our way to the credential");
+ } catch (Exception e) {
+ // pass
+ assertEquals("Reflection access not allowed during rendering "
+ + "(com.android.ide.common.rendering.RenderSecurityManager)",
+ e.toString());
+ }
+ } finally {
+ manager.dispose(credential);
+ }
+ }
+
+ public void testImageIo() throws Exception {
+ RenderSecurityManager manager = new RenderSecurityManager(null, null);
+ try {
+ manager.setActive(true, myCredential);
+
+ File root = TestUtils.getRoot("resources", "baseMerge");
+ assertNotNull(root);
+ assertTrue(root.exists());
+ final File icon = new File(root, "overlay" + separator + "drawable" + separator
+ + "icon2.png");
+ assertTrue(icon.exists());
+ final byte[] buf = Files.toByteArray(icon);
+ InputStream stream = new ByteArrayInputStream(buf);
+ assertNotNull(stream);
+ BufferedImage image = ImageIO.read(stream);
+ assertNotNull(image);
+ assertNull(ImageIO.getCacheDirectory());
+
+ // Also run in non AWT thread to test ImageIO thread locals cache dir behavior
+ Thread thread = new Thread() {
+ @Override
+ public void run() {
+ try {
+ assertFalse(SwingUtilities.isEventDispatchThread());
+ final byte[] buf = Files.toByteArray(icon);
+ InputStream stream = new ByteArrayInputStream(buf);
+ assertNotNull(stream);
+ BufferedImage image = ImageIO.read(stream);
+ assertNotNull(image);
+ assertNull(ImageIO.getCacheDirectory());
+ } catch (Throwable t) {
+ t.printStackTrace();
+ fail(t.toString());
+ }
+ }
+ };
+
+ thread.start();
+ thread.join();
+ } finally {
+ manager.dispose(myCredential);
+ }
+ }
+}
diff --git a/sdk-common/src/test/java/com/android/ide/common/repository/GradleCoordinateTest.java b/sdk-common/src/test/java/com/android/ide/common/repository/GradleCoordinateTest.java
new file mode 100644
index 0000000..1814994
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/repository/GradleCoordinateTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.common.repository;
+
+import com.android.ide.common.res2.BaseTestCase;
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+/**
+ * Test class for {@see GradleCoordinate}
+ */
+public class GradleCoordinateTest extends BaseTestCase {
+ public void testParseCoordinateString() throws Exception {
+ GradleCoordinate expected = new GradleCoordinate("a.b.c", "package", 5, 4, 2);
+ GradleCoordinate actual = GradleCoordinate.parseCoordinateString("a.b.c:package:5.4.2");
+ assertEquals(expected, actual);
+
+ expected = new GradleCoordinate("a.b.c", "package", 5, 4, GradleCoordinate.PLUS_REV);
+ actual = GradleCoordinate.parseCoordinateString("a.b.c:package:5.4.+");
+ assertEquals(expected, actual);
+
+ expected = new GradleCoordinate("a.b.c", "package", 5, GradleCoordinate.PLUS_REV);
+ actual = GradleCoordinate.parseCoordinateString("a.b.c:package:5.+");
+ assertEquals(expected, actual);
+
+ expected = new GradleCoordinate("a.b.c", "package", GradleCoordinate.PLUS_REV);
+ actual = GradleCoordinate.parseCoordinateString("a.b.c:package:+");
+ assertEquals(expected, actual);
+
+ List<Integer> revisionList = Lists.newArrayList(GradleCoordinate.PLUS_REV);
+ expected = new GradleCoordinate("a.b.c", "package", revisionList, GradleCoordinate.ArtifactType.JAR);
+ actual = GradleCoordinate.parseCoordinateString("a.b.c:package:+@jar");
+ assertEquals(expected, actual);
+
+ expected = new GradleCoordinate("a.b.c", "package", revisionList, GradleCoordinate.ArtifactType.AAR);
+ actual = GradleCoordinate.parseCoordinateString("a.b.c:package:+@AAR");
+ assertEquals(expected, actual);
+ }
+
+ public void testToString() throws Exception {
+ String expected = "a.b.c:package:5.4.2";
+ String actual = new GradleCoordinate("a.b.c", "package", 5, 4, 2).toString();
+ assertEquals(expected, actual);
+
+ expected = "a.b.c:package:5.4.+";
+ actual = new GradleCoordinate("a.b.c", "package", 5, 4, GradleCoordinate.PLUS_REV).toString();
+ assertEquals(expected, actual);
+
+ expected = "a.b.c:package:5.+";
+ actual = new GradleCoordinate("a.b.c", "package", 5, GradleCoordinate.PLUS_REV).toString();
+ assertEquals(expected, actual);
+
+ expected = "a.b.c:package:+";
+ actual = new GradleCoordinate("a.b.c", "package", GradleCoordinate.PLUS_REV).toString();
+ assertEquals(expected, actual);
+
+ expected = "a.b.c:package:+@jar";
+ List<Integer> revisionList = Lists.newArrayList(GradleCoordinate.PLUS_REV);
+ actual = new GradleCoordinate("a.b.c", "package", revisionList, GradleCoordinate.ArtifactType.JAR).toString();
+ assertEquals(expected, actual);
+
+ expected = "a.b.c:package:+@aar";
+ actual = new GradleCoordinate("a.b.c", "package", revisionList, GradleCoordinate.ArtifactType.AAR).toString();
+ assertEquals(expected, actual);
+ }
+
+ public void testIsSameArtifact() throws Exception {
+ GradleCoordinate a = new GradleCoordinate("a.b.c", "package", 5, 4, 2);
+ GradleCoordinate b = new GradleCoordinate("a.b.c", "package", 5, 5, 5);
+ assertTrue(a.isSameArtifact(b));
+ assertTrue(b.isSameArtifact(a));
+
+ a = new GradleCoordinate("a.b", "package", 5, 4, 2);
+ b = new GradleCoordinate("a.b.c", "package", 5, 5, 5);
+ assertFalse(a.isSameArtifact(b));
+ assertFalse(b.isSameArtifact(a));
+
+ a = new GradleCoordinate("a.b.c", "package", 5, 4, 2);
+ b = new GradleCoordinate("a.b.c", "feature", 5, 5, 5);
+ assertFalse(a.isSameArtifact(b));
+ assertFalse(b.isSameArtifact(a));
+ }
+
+ public void testCompareTo() throws Exception {
+ GradleCoordinate a = new GradleCoordinate("a.b.c", "package", 5, 4, 2);
+ GradleCoordinate b = new GradleCoordinate("a.b.c", "package", 5, 5, 5);
+ assertTrue(a.compareTo(b) < 0);
+ assertTrue(b.compareTo(a) > 0);
+
+ a = new GradleCoordinate("a.b.c", "package", 5, 4, 10);
+ b = new GradleCoordinate("a.b.c", "package", 5, 4, GradleCoordinate.PLUS_REV);
+ assertTrue(a.compareTo(b) > 0);
+
+ a = new GradleCoordinate("a.b.c", "package", 5, 6, GradleCoordinate.PLUS_REV);
+ b = new GradleCoordinate("a.b.c", "package", 6, 0, 0);
+ assertTrue(a.compareTo(b) < 0);
+
+ a = new GradleCoordinate("a.b.c", "package", 5, 6, 0);
+ b = new GradleCoordinate("a.b.c", "package", 5, 6, 0);
+ assertTrue(a.compareTo(b) == 0);
+
+ a = new GradleCoordinate("a.b.c", "package", 5, 4, 2);
+ b = new GradleCoordinate("a.b.c", "feature", 5, 4, 2);
+
+ assertTrue( (a.compareTo(b) < 0) == ("package".compareTo("feature") < 0));
+
+ a = new GradleCoordinate("a.b.c", "package", 5, 6, 0);
+ b = new GradleCoordinate("a.b.c", "package", 5, 6, GradleCoordinate.PLUS_REV);
+ assertTrue(a.compareTo(b) > 0);
+
+ a = new GradleCoordinate("a.b.c", "package", 5, 6, 0);
+ b = new GradleCoordinate("a.b.c", "package", 5, GradleCoordinate.PLUS_REV);
+ assertTrue(a.compareTo(b) > 0);
+ }
+}
diff --git a/sdk-common/src/test/java/com/android/ide/common/res2/AssetMergerTest.java b/sdk-common/src/test/java/com/android/ide/common/res2/AssetMergerTest.java
index 2a6428e..522bf8e 100755
--- a/sdk-common/src/test/java/com/android/ide/common/res2/AssetMergerTest.java
+++ b/sdk-common/src/test/java/com/android/ide/common/res2/AssetMergerTest.java
@@ -352,7 +352,7 @@
}
private static AssetMerger getAssetMerger()
- throws DuplicateDataException, IOException {
+ throws IOException, MergingException {
if (sAssetMerger == null) {
File root = TestUtils.getRoot("assets", "baseMerge");
@@ -374,8 +374,7 @@
return sAssetMerger;
}
- private static File getWrittenResources() throws DuplicateDataException, IOException,
- MergeConsumer.ConsumerException {
+ private static File getWrittenResources() throws MergingException, IOException {
AssetMerger assetMerger = getAssetMerger();
File folder = Files.createTempDir();
diff --git a/sdk-common/src/test/java/com/android/ide/common/res2/AssetSetTest.java b/sdk-common/src/test/java/com/android/ide/common/res2/AssetSetTest.java
index b5b45fd..a73f0ad 100644
--- a/sdk-common/src/test/java/com/android/ide/common/res2/AssetSetTest.java
+++ b/sdk-common/src/test/java/com/android/ide/common/res2/AssetSetTest.java
@@ -59,7 +59,7 @@
checkLogger(logger);
}
- static AssetSet getBaseAssetSet() throws DuplicateDataException, IOException {
+ static AssetSet getBaseAssetSet() throws MergingException, IOException {
if (sBaseResourceSet == null) {
File root = TestUtils.getRoot("assets", "baseSet");
diff --git a/sdk-common/src/test/java/com/android/ide/common/res2/MergingExceptionTest.java b/sdk-common/src/test/java/com/android/ide/common/res2/MergingExceptionTest.java
new file mode 100644
index 0000000..3483965
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/res2/MergingExceptionTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.res2;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+
+public class MergingExceptionTest extends TestCase {
+ @SuppressWarnings("ThrowableInstanceNeverThrown")
+ public void testGetMessage() {
+ File file = new File("/some/random/path");
+ assertEquals("Error: My error message",
+ new MergingException("My error message").getMessage());
+ assertEquals("Error: My error message",
+ new MergingException("Error: My error message").getMessage());
+ assertEquals("/some/random/path: Error: My error message",
+ new MergingException("My error message").setFile(file).getMessage());
+ assertEquals("/some/random/path:50: Error: My error message",
+ new MergingException("My error message").setFile(file).setLine(50).getMessage());
+ assertEquals("/some/random/path:50:4: Error: My error message",
+ new MergingException("My error message").setFile(file).setLine(50).setColumn(4)
+ .getMessage());
+ assertEquals("/some/random/path:50:4: Error: My error message",
+ new MergingException("My error message").setFile(file).setLine(50).setColumn(4)
+ .getLocalizedMessage());
+ assertEquals("/some/random/path: Error: My error message",
+ new MergingException("/some/random/path: My error message").setFile(file)
+ .getMessage());
+ assertEquals("/some/random/path: Error: My error message",
+ new MergingException("/some/random/path My error message").setFile(file)
+ .getMessage());
+
+ // end of string handling checks
+ assertEquals("/some/random/path: Error: ",
+ new MergingException("/some/random/path").setFile(file).getMessage());
+ assertEquals("/some/random/path: Error: ",
+ new MergingException("/some/random/path").setFile(file).getMessage());
+ assertEquals("/some/random/path: Error: ",
+ new MergingException("/some/random/path:").setFile(file).getMessage());
+ assertEquals("/some/random/path: Error: ",
+ new MergingException("/some/random/path: ").setFile(file).getMessage());
+ }
+}
diff --git a/sdk-common/src/test/java/com/android/ide/common/res2/NodeUtilsTest.java b/sdk-common/src/test/java/com/android/ide/common/res2/NodeUtilsTest.java
index 73f3c30..e8eec60 100644
--- a/sdk-common/src/test/java/com/android/ide/common/res2/NodeUtilsTest.java
+++ b/sdk-common/src/test/java/com/android/ide/common/res2/NodeUtilsTest.java
@@ -17,6 +17,7 @@
package com.android.ide.common.res2;
import junit.framework.TestCase;
+
import org.w3c.dom.Document;
import org.w3c.dom.Node;
diff --git a/sdk-common/src/test/java/com/android/ide/common/res2/RecordingLogger.java b/sdk-common/src/test/java/com/android/ide/common/res2/RecordingLogger.java
index 0259a1f..67bb79d 100644
--- a/sdk-common/src/test/java/com/android/ide/common/res2/RecordingLogger.java
+++ b/sdk-common/src/test/java/com/android/ide/common/res2/RecordingLogger.java
@@ -21,9 +21,6 @@
import com.android.utils.ILogger;
import com.google.common.collect.Lists;
-import java.lang.Override;
-import java.lang.String;
-import java.lang.Throwable;
import java.util.List;
/**
diff --git a/sdk-common/src/test/java/com/android/ide/common/res2/ResourceMergerTest.java b/sdk-common/src/test/java/com/android/ide/common/res2/ResourceMergerTest.java
index eaff62e..d9c402a 100755
--- a/sdk-common/src/test/java/com/android/ide/common/res2/ResourceMergerTest.java
+++ b/sdk-common/src/test/java/com/android/ide/common/res2/ResourceMergerTest.java
@@ -24,6 +24,7 @@
import com.android.resources.ResourceFolderType;
import com.android.resources.ResourceType;
import com.android.testutils.TestUtils;
+import com.android.utils.SdkUtils;
import com.google.common.base.Charsets;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Maps;
@@ -36,7 +37,6 @@
import java.io.File;
import java.io.IOException;
-import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -49,7 +49,7 @@
public void testMergeByCount() throws Exception {
ResourceMerger merger = getResourceMerger();
- assertEquals(27, merger.size());
+ assertEquals(29, merger.size());
}
public void testMergedResourcesByName() throws Exception {
@@ -79,6 +79,7 @@
"attr/flag_attr",
"attr/blah",
"attr/blah2",
+ "attr/flagAttr",
"declare-styleable/declare_styleable",
"dimen/dimen",
"id/item_id",
@@ -900,7 +901,7 @@
}
private static ResourceMerger getResourceMerger()
- throws DuplicateDataException, IOException {
+ throws MergingException, IOException {
File root = TestUtils.getRoot("resources", "baseMerge");
ResourceSet res = ResourceSetTest.getBaseResourceSet();
@@ -920,8 +921,7 @@
return resourceMerger;
}
- private static File getWrittenResources() throws DuplicateDataException, IOException,
- MergeConsumer.ConsumerException {
+ private static File getWrittenResources() throws MergingException, IOException {
ResourceMerger resourceMerger = getResourceMerger();
File folder = Files.createTempDir();
@@ -961,7 +961,7 @@
}
private static Map<String, String> quickStringOnlyValueFileParser(File file)
- throws IOException {
+ throws IOException, MergingException {
Map<String, String> result = Maps.newHashMap();
Document document = ValueResourceParser2.parseDocument(file);
@@ -980,6 +980,9 @@
if (node.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
+ if (node.getNodeName().equals(SdkConstants.TAG_EAT_COMMENT)) {
+ continue;
+ }
ResourceType type = ValueResourceParser2.getType(node);
if (type != ResourceType.STRING) {
@@ -1027,7 +1030,7 @@
int index = layoutXml.indexOf("From: ");
assertTrue(index != -1);
String path = layoutXml.substring(index + 6, layoutXml.indexOf(' ', index + 6));
- File file = new File(new URL(path).toURI());
+ File file = SdkUtils.urlToFile(path);
assertTrue(path, file.exists());
assertFalse(Arrays.equals(Files.toByteArray(file), Files.toByteArray(layout)));
@@ -1039,4 +1042,103 @@
File copied = new File(folder, FD_RES_DRAWABLE + File.separator + "icon.png");
assertTrue(Arrays.equals(Files.toByteArray(original), Files.toByteArray(copied)));
}
+
+ public void testWritePermission() throws Exception {
+ ResourceMerger merger = getResourceMerger();
+
+ File folder = Files.createTempDir();
+ boolean writable = folder.setWritable(false);
+ if (!writable) {
+ // Not supported on this platform
+ return;
+ }
+ try {
+ merger.writeBlobTo(folder,
+ new MergedResourceWriter(Files.createTempDir(), null /*aaptRunner*/));
+ } catch (MergingException e) {
+ File file = new File(folder, "merger.xml");
+ assertEquals(file.getPath() + ": Error: (Permission denied)",
+ e.getMessage());
+ return;
+ }
+ fail("Exception not thrown as expected");
+ }
+
+ public void testInvalidFileNames() throws Exception {
+ File root = TestUtils.getRoot("resources", "brokenSet5");
+ ResourceSet resourceSet = new ResourceSet("brokenSet5");
+ resourceSet.addSource(root);
+ RecordingLogger logger = new RecordingLogger();
+ resourceSet.loadFromFiles(logger);
+
+ ResourceMerger resourceMerger = new ResourceMerger();
+ resourceMerger.addDataSet(resourceSet);
+
+
+ File folder = Files.createTempDir();
+ try {
+ MergedResourceWriter writer = new MergedResourceWriter(folder, null /*aaptRunner*/);
+ resourceMerger.mergeData(writer, false /*doCleanUp*/);
+ } catch (MergingException e) {
+ File file = new File(root, "layout" + File.separator + "ActivityMain.xml");
+ file = file.getAbsoluteFile();
+ assertEquals(file.getPath() + ": Error: Invalid file name: must contain only "
+ + "lowercase letters and digits ([a-z0-9_.])",
+ e.getMessage());
+ return;
+ }
+ fail("Expected error");
+ }
+
+ public void testXmlParseError1() throws Exception {
+ File root = TestUtils.getRoot("resources", "brokenSet6");
+ try {
+ ResourceSet resourceSet = new ResourceSet("brokenSet6");
+ resourceSet.addSource(root);
+ RecordingLogger logger = new RecordingLogger();
+ resourceSet.loadFromFiles(logger);
+
+ ResourceMerger resourceMerger = new ResourceMerger();
+ resourceMerger.addDataSet(resourceSet);
+
+
+ File folder = Files.createTempDir();
+ MergedResourceWriter writer = new MergedResourceWriter(folder, null /*aaptRunner*/);
+ resourceMerger.mergeData(writer, false /*doCleanUp*/);
+ } catch (MergingException e) {
+ File file = new File(root, "values" + File.separator + "dimens.xml");
+ file = file.getAbsoluteFile();
+ assertEquals(file.getPath() + ":3:5: Error: The content of elements must consist "
+ + "of well-formed character data or markup.",
+ e.getMessage());
+ return;
+ }
+ fail("Expected error");
+ }
+
+ public void testXmlParseError7() throws Exception {
+ File root = TestUtils.getRoot("resources", "brokenSet7");
+ try {
+ ResourceSet resourceSet = new ResourceSet("brokenSet7");
+ resourceSet.addSource(root);
+ RecordingLogger logger = new RecordingLogger();
+ resourceSet.loadFromFiles(logger);
+
+ ResourceMerger resourceMerger = new ResourceMerger();
+ resourceMerger.addDataSet(resourceSet);
+
+
+ File folder = Files.createTempDir();
+ MergedResourceWriter writer = new MergedResourceWriter(folder, null /*aaptRunner*/);
+ resourceMerger.mergeData(writer, false /*doCleanUp*/);
+ } catch (MergingException e) {
+ File file = new File(root, "values" + File.separator + "dimens.xml");
+ file = file.getAbsoluteFile();
+ assertEquals(file.getPath() + ":1:16: Error: Open quote is expected for "
+ + "attribute \"{1}\" associated with an element type \"name\".",
+ e.getMessage());
+ return;
+ }
+ fail("Expected error");
+ }
}
diff --git a/sdk-common/src/test/java/com/android/ide/common/res2/ResourceRepositoryTest.java b/sdk-common/src/test/java/com/android/ide/common/res2/ResourceRepositoryTest.java
index fe13060..611f565 100755
--- a/sdk-common/src/test/java/com/android/ide/common/res2/ResourceRepositoryTest.java
+++ b/sdk-common/src/test/java/com/android/ide/common/res2/ResourceRepositoryTest.java
@@ -48,7 +48,7 @@
assertEquals(3, items.get(ResourceType.STRING).size());
assertEquals(1, items.get(ResourceType.STYLE).size());
assertEquals(1, items.get(ResourceType.ARRAY).size());
- assertEquals(6, items.get(ResourceType.ATTR).size());
+ assertEquals(7, items.get(ResourceType.ATTR).size());
assertEquals(1, items.get(ResourceType.DECLARE_STYLEABLE).size());
assertEquals(1, items.get(ResourceType.DIMEN).size());
assertEquals(1, items.get(ResourceType.ID).size());
@@ -82,6 +82,7 @@
"attr/flag_attr",
"attr/blah",
"attr/blah2",
+ "attr/flagAttr",
"declare-styleable/declare_styleable",
"dimen/dimen",
"id/item_id",
@@ -527,12 +528,9 @@
/**
* Returns a merger with the baseSet and baseMerge content.
- * @return
- * @throws DuplicateDataException
- * @throws IOException
*/
private static ResourceMerger getBaseResourceMerger()
- throws DuplicateDataException, IOException {
+ throws MergingException, IOException {
File root = TestUtils.getRoot("resources", "baseMerge");
ResourceSet res = ResourceSetTest.getBaseResourceSet();
@@ -555,14 +553,9 @@
/**
* Returns a merger from incMergeData initialized from the files, not from the merger
* state blog.
- *
- * @param rootName
- * @return
- * @throws DuplicateDataException
- * @throws IOException
*/
private static ResourceMerger getIncResourceMerger(String rootName, String... sets)
- throws DuplicateDataException, IOException {
+ throws MergingException, IOException {
File root = getIncMergeRoot(rootName);
RecordingLogger logger = new RecordingLogger();
@@ -582,7 +575,7 @@
}
private ResourceRepository getResourceRepository()
- throws DuplicateDataException, IOException, MergeConsumer.ConsumerException {
+ throws MergingException, IOException {
ResourceMerger merger = getBaseResourceMerger();
ResourceRepository repo = new ResourceRepository(false);
diff --git a/sdk-common/src/test/java/com/android/ide/common/res2/ResourceRepositoryTest2.java b/sdk-common/src/test/java/com/android/ide/common/res2/ResourceRepositoryTest2.java
index b22d8b1..6f127c5 100644
--- a/sdk-common/src/test/java/com/android/ide/common/res2/ResourceRepositoryTest2.java
+++ b/sdk-common/src/test/java/com/android/ide/common/res2/ResourceRepositoryTest2.java
@@ -22,6 +22,7 @@
import static com.android.SdkConstants.FD_RES_VALUES;
import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.resources.TestResourceRepository;
import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.ide.common.resources.configuration.LanguageQualifier;
import com.android.ide.common.resources.configuration.ScreenOrientationQualifier;
@@ -313,4 +314,40 @@
mResourceMerger.mergeData(mRepository.createMergeConsumer(), true /*doCleanUp*/);
assertTrue(mRepository.hasResourceItem("@layout/layout5"));
}
+
+ @SuppressWarnings("ConstantConditions")
+ public void testXliff() throws Exception {
+ ResourceRepository resources = TestResourceRepository.createRes2(false, new Object[]{
+ "values/strings.xml", ""
+ + "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\" >\n"
+ + " <string name=\"share_with_application\">\n"
+ + " Share your score of <xliff:g id=\"score\" example=\"1337\">%1$s</xliff:g>\n"
+ + " with <xliff:g id=\"application_name\" example=\"Bluetooth\">%2$s</xliff:g>!\n"
+ + " </string>\n"
+ + " <string name=\"callDetailsDurationFormat\"><xliff:g id=\"minutes\" example=\"42\">%s</xliff:g> mins <xliff:g id=\"seconds\" example=\"28\">%s</xliff:g> secs</string>\n"
+ + " <string name=\"description_call\">Call <xliff:g id=\"name\">%1$s</xliff:g></string>\n"
+ + " <string name=\"other\"><xliff:g id=\"number_of_sessions\">%1$s</xliff:g> sessions removed from your schedule</string>\n"
+ + " <!-- Format string used to add a suffix like \"KB\" or \"MB\" to a number\n"
+ + " to display a size in kilobytes, megabytes, or other size units.\n"
+ + " Some languages (like French) will want to add a space between\n"
+ + " the placeholders. -->\n"
+ + " <string name=\"fileSizeSuffix\"><xliff:g id=\"number\" example=\"123\">%1$s</xliff:g><xliff:g id=\"unit\" example=\"KB\">%2$s</xliff:g></string>"
+ + "</resources>\n"
+ });
+ assertFalse(resources.isFramework());
+ assertNotNull(resources);
+
+ assertNotNull(resources);
+ assertEquals("Share your score of (1337) with (Bluetooth)!",
+ resources.getResourceItem(ResourceType.STRING, "share_with_application").get(0).getResourceValue(false).getValue());
+ assertEquals("Call ${name}",
+ resources.getResourceItem(ResourceType.STRING, "description_call").get(0).getResourceValue(false).getValue());
+ assertEquals("(42) mins (28) secs",
+ resources.getResourceItem(ResourceType.STRING, "callDetailsDurationFormat").get(0).getResourceValue(false).getValue());
+ assertEquals("${number_of_sessions} sessions removed from your schedule",
+ resources.getResourceItem(ResourceType.STRING, "other").get(0).getResourceValue(false).getValue());
+ assertEquals("(123)(KB)",
+ resources.getResourceItem(ResourceType.STRING, "fileSizeSuffix").get(0).getResourceValue(false).getValue());
+
+ }
}
diff --git a/sdk-common/src/test/java/com/android/ide/common/res2/ResourceSetTest.java b/sdk-common/src/test/java/com/android/ide/common/res2/ResourceSetTest.java
index bbff8a4..665aa3b 100644
--- a/sdk-common/src/test/java/com/android/ide/common/res2/ResourceSetTest.java
+++ b/sdk-common/src/test/java/com/android/ide/common/res2/ResourceSetTest.java
@@ -16,6 +16,8 @@
package com.android.ide.common.res2;
+import static java.io.File.separator;
+
import com.android.testutils.TestUtils;
import java.io.File;
@@ -25,7 +27,7 @@
public void testBaseResourceSetByCount() throws Exception {
ResourceSet resourceSet = getBaseResourceSet();
- assertEquals(25, resourceSet.size());
+ assertEquals(27, resourceSet.size());
}
public void testBaseResourceSetByName() throws Exception {
@@ -53,10 +55,12 @@
"attr/flag_attr",
"attr/blah",
"attr/blah2",
+ "attr/flagAttr",
"declare-styleable/declare_styleable",
"dimen/dimen",
"id/item_id",
- "integer/integer"
+ "integer/integer",
+ "plurals/plurals"
);
}
@@ -72,6 +76,15 @@
set.loadFromFiles(logger);
} catch (DuplicateDataException e) {
gotException = true;
+ String message = e.getMessage();
+ // Clean up paths etc for unit test
+ int index = message.indexOf("dupSet");
+ assertTrue(index != -1);
+ String prefix = message.substring(0, index);
+ message = message.replaceAll(prefix, "<PREFIX>").replace('\\','/');
+ assertEquals("<PREFIX>dupSet/res2/drawable/icon.png: Error: Duplicate resources: "
+ + "<PREFIX>dupSet/res2/drawable/icon.png:drawable/icon, "
+ + "<PREFIX>dupSet/res1/drawable/icon.png:drawable/icon", message);
}
checkLogger(logger);
@@ -88,8 +101,11 @@
RecordingLogger logger = new RecordingLogger();
try {
set.loadFromFiles(logger);
- } catch (IOException e) {
+ } catch (MergingException e) {
gotException = true;
+ assertEquals(new File(root, "values" + separator + "dimens.xml").getAbsolutePath() +
+ ":0:0: Error: Content is not allowed in prolog.",
+ e.getMessage());
}
assertTrue("ResourceSet processing should have failed, but didn't", gotException);
@@ -106,8 +122,11 @@
RecordingLogger logger = new RecordingLogger();
try {
set.loadFromFiles(logger);
- } catch (IOException e) {
+ } catch (MergingException e) {
gotException = true;
+ assertEquals(new File(root, "values" + separator + "values.xml").getAbsolutePath() +
+ ": Error: Found item String/app_name more than one time",
+ e.getMessage());
}
assertTrue("ResourceSet processing should have failed, but didn't", gotException);
@@ -124,15 +143,40 @@
RecordingLogger logger = new RecordingLogger();
try {
set.loadFromFiles(logger);
- } catch (IOException e) {
+ } catch (MergingException e) {
gotException = true;
+ assertEquals(new File(root, "values" + separator + "values.xml").getAbsolutePath() +
+ ": Error: Found item Attr/d_common_attr more than one time",
+ e.getMessage());
}
assertTrue("ResourceSet processing should have failed, but didn't", gotException);
assertFalse(logger.getErrorMsgs().isEmpty());
}
- static ResourceSet getBaseResourceSet() throws DuplicateDataException, IOException {
+ public void testBrokenSet4() throws Exception {
+ File root = TestUtils.getRoot("resources", "brokenSet4");
+
+ ResourceSet set = new ResourceSet("main");
+ set.addSource(root);
+
+ boolean gotException = false;
+ RecordingLogger logger = new RecordingLogger();
+ try {
+ set.loadFromFiles(logger);
+ } catch (MergingException e) {
+ gotException = true;
+ assertEquals(new File(root, "values" + separator + "values.xml").getAbsolutePath() +
+ ":6:5: Error: The element type \"declare-styleable\" "
+ + "must be terminated by the matching end-tag \"</declare-styleable>\".",
+ e.getMessage());
+ }
+
+ assertTrue("ResourceSet processing should have failed, but didn't", gotException);
+ assertFalse(logger.getErrorMsgs().isEmpty());
+ }
+
+ static ResourceSet getBaseResourceSet() throws MergingException, IOException {
File root = TestUtils.getRoot("resources", "baseSet");
ResourceSet resourceSet = new ResourceSet("main");
diff --git a/sdk-common/src/test/java/com/android/ide/common/res2/ValueResourceParser2Test.java b/sdk-common/src/test/java/com/android/ide/common/res2/ValueResourceParser2Test.java
index faf56e7..91153f3 100644
--- a/sdk-common/src/test/java/com/android/ide/common/res2/ValueResourceParser2Test.java
+++ b/sdk-common/src/test/java/com/android/ide/common/res2/ValueResourceParser2Test.java
@@ -20,7 +20,6 @@
import com.google.common.collect.Maps;
import java.io.File;
-import java.io.IOException;
import java.util.List;
import java.util.Map;
@@ -33,7 +32,7 @@
public void testParsedResourcesByCount() throws Exception {
List<ResourceItem> resources = getParsedResources();
- assertEquals(20, resources.size());
+ assertEquals(22, resources.size());
}
public void testParsedResourcesByName() throws Exception {
@@ -58,11 +57,13 @@
"attr/flag_attr",
"attr/blah",
"attr/blah2",
+ "attr/flagAttr",
"declare-styleable/declare_styleable",
"dimen/dimen",
"id/item_id",
"integer/integer",
- "layout/layout_ref"
+ "layout/layout_ref",
+ "plurals/plurals"
};
for (String name : resourceNames) {
@@ -70,7 +71,7 @@
}
}
- private static List<ResourceItem> getParsedResources() throws IOException {
+ private static List<ResourceItem> getParsedResources() throws MergingException {
if (sResources == null) {
File root = TestUtils.getRoot("resources", "baseSet");
File values = new File(root, "values");
diff --git a/sdk-common/src/test/java/com/android/ide/common/res2/ValueXmlHelperTest.java b/sdk-common/src/test/java/com/android/ide/common/res2/ValueXmlHelperTest.java
index a622f95..ed9c47f 100644
--- a/sdk-common/src/test/java/com/android/ide/common/res2/ValueXmlHelperTest.java
+++ b/sdk-common/src/test/java/com/android/ide/common/res2/ValueXmlHelperTest.java
@@ -149,4 +149,33 @@
assertTrue(isEscaped( "\\\\\\\\y ", 3));
assertFalse(isEscaped("\\\\\\\\y ", 4));
}
+
+ public void testRewriteSpaces() throws Exception {
+ // Ensure that \n's in the input are rewritten as spaces, and multiple spaces
+ // collapsed into a single one
+ assertEquals("This is a test",
+ unescapeResourceString("This is\na test", true, true));
+ assertEquals("This is a test",
+ unescapeResourceString("This is\n a test\n ", true, true));
+ assertEquals("This is\na test",
+ unescapeResourceString("\"This is\na test\"", true, true));
+ assertEquals("Multiple words",
+ unescapeResourceString("Multiple words", true, true));
+ assertEquals("Multiple words",
+ unescapeResourceString("\"Multiple words\"", true, true));
+ assertEquals("This is a\n test",
+ unescapeResourceString("This is a\\n test", true, true));
+ assertEquals("This is a\n test",
+ unescapeResourceString("This is\n a\\n test", true, true));
+ }
+
+ public void testHtmlEntities() throws Exception {
+ assertEquals("Entity \u00a9 \u00a9 Copyright",
+ unescapeResourceString("Entity © © Copyright", true, true));
+ }
+
+ public void testMarkupConcatenation() throws Exception {
+ assertEquals("<b>Sign in</b> or register",
+ unescapeResourceString("\n <b>Sign in</b>\n or register\n", true, true));
+ }
}
diff --git a/sdk-common/src/test/java/com/android/ide/common/resources/ResourceItemResolverTest.java b/sdk-common/src/test/java/com/android/ide/common/resources/ResourceItemResolverTest.java
new file mode 100644
index 0000000..d29c7eb
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/resources/ResourceItemResolverTest.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.common.resources;
+
+import com.android.annotations.Nullable;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.res2.ResourceRepository;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.resources.ResourceType;
+import com.google.common.collect.Lists;
+
+import junit.framework.TestCase;
+
+import java.util.List;
+import java.util.Map;
+
+public class ResourceItemResolverTest extends TestCase {
+ @SuppressWarnings("ConstantConditions")
+ public void test() throws Exception {
+ final TestResourceRepository frameworkResources = TestResourceRepository.create(true,
+ new Object[]{
+ "values/strings.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <string name=\"ok\">Ok</string>\n"
+ + "</resources>\n",
+
+ "values/themes.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <style name=\"Theme\">\n"
+ + " <item name=\"colorForeground\">@android:color/bright_foreground_dark</item>\n"
+ + " <item name=\"colorBackground\">@android:color/background_dark</item>\n"
+ + " </style>\n"
+ + " <style name=\"Theme.Light\">\n"
+ + " <item name=\"colorBackground\">@android:color/background_light</item>\n"
+ + " <item name=\"colorForeground\">@color/bright_foreground_light</item>\n"
+ + " </style>\n"
+ + "</resources>\n",
+
+ "values/colors.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <color name=\"background_dark\">#ff000000</color>\n"
+ + " <color name=\"background_light\">#ffffffff</color>\n"
+ + " <color name=\"bright_foreground_dark\">@android:color/background_light</color>\n"
+ + " <color name=\"bright_foreground_light\">@android:color/background_dark</color>\n"
+ + "</resources>\n",
+ });
+
+ final ResourceRepository appResources = TestResourceRepository.createRes2(false,
+ new Object[]{
+ "layout/layout1.xml", "<!--contents doesn't matter-->",
+
+ "layout/layout2.xml", "<!--contents doesn't matter-->",
+
+ "layout-land/layout1.xml", "<!--contents doesn't matter-->",
+
+ "layout-land/onlyLand.xml", "<!--contents doesn't matter-->",
+
+ "drawable/graphic.9.png", new byte[0],
+
+ "values/styles.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <style name=\"MyTheme\" parent=\"android:Theme.Light\">\n"
+ + " <item name=\"android:textColor\">#999999</item>\n"
+ + " <item name=\"foo\">?android:colorForeground</item>\n"
+ + " </style>\n"
+ + " <style name=\"MyTheme.Dotted1\" parent=\"\">\n"
+ + " </style>"
+ + " <style name=\"MyTheme.Dotted2\">\n"
+ + " </style>"
+ + " <style name=\"RandomStyle\">\n"
+ + " </style>"
+ + " <style name=\"RandomStyle2\" parent=\"RandomStyle\">\n"
+ + " </style>"
+ + "</resources>\n",
+
+ "values/strings.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <item type=\"id\" name=\"action_bar_refresh\" />\n"
+ + " <item type=\"dimen\" name=\"dialog_min_width_major\">45%</item>\n"
+ + " <string name=\"home_title\">Home Sample</string>\n"
+ + " <string name=\"show_all_apps\">All</string>\n"
+ + " <string name=\"menu_wallpaper\">Wallpaper</string>\n"
+ + " <string name=\"menu_search\">Search</string>\n"
+ + " <string name=\"menu_settings\">Settings</string>\n"
+ + " <string name=\"dummy\" translatable=\"false\">Ignore Me</string>\n"
+ + " <string name=\"wallpaper_instructions\">Tap picture to set portrait wallpaper</string>\n"
+ + "</resources>\n",
+
+ "values-es/strings.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <string name=\"show_all_apps\">Todo</string>\n"
+ + "</resources>\n",
+ });
+
+ final FolderConfiguration config = FolderConfiguration.getConfigForFolder("values-es-land");
+ assertFalse(appResources.isFramework());
+ assertNotNull(config);
+
+ final LayoutLog logger = new LayoutLog() {
+ @Override
+ public void warning(String tag, String message, Object data) {
+ fail(message);
+ }
+
+ @Override
+ public void fidelityWarning(String tag, String message, Throwable throwable,
+ Object data) {
+ fail(message);
+ }
+
+ @Override
+ public void error(String tag, String message, Object data) {
+ fail(message);
+ }
+
+ @Override
+ public void error(String tag, String message, Throwable throwable, Object data) {
+ fail(message);
+ }
+ };
+
+ ResourceItemResolver.ResourceProvider provider = new ResourceItemResolver.ResourceProvider() {
+ private ResourceResolver mResolver;
+
+ @Nullable
+ @Override
+ public ResourceResolver getResolver(boolean createIfNecessary) {
+ if (mResolver == null && createIfNecessary) {
+ Map<ResourceType, Map<String, ResourceValue>> appResourceMap =
+ appResources.getConfiguredResources(config);
+ Map<ResourceType, Map<String, ResourceValue>> frameworkResourcesMap =
+ frameworkResources.getConfiguredResources(config);
+ assertNotNull(appResourceMap);
+ mResolver = ResourceResolver.create(appResourceMap, frameworkResourcesMap,
+ "MyTheme", true);
+ assertNotNull(mResolver);
+ mResolver.setLogger(logger);
+ }
+
+ return mResolver;
+ }
+
+ @Nullable
+ @Override
+ public com.android.ide.common.resources.ResourceRepository getFrameworkResources() {
+ return frameworkResources;
+ }
+
+ @Nullable
+ @Override
+ public ResourceRepository getAppResources() {
+ return appResources;
+ }
+ };
+
+ ResourceItemResolver resolver = new ResourceItemResolver(config, provider, logger);
+
+ // findResValue
+ assertNotNull(resolver.findResValue("@string/show_all_apps", false));
+ assertNotNull(resolver.findResValue("@android:string/ok", false));
+ assertNotNull(resolver.findResValue("@android:string/ok", true));
+ assertEquals("Todo", resolver.findResValue("@string/show_all_apps", false).getValue());
+ assertEquals("Home Sample", resolver.findResValue("@string/home_title", false).getValue());
+ assertEquals("45%", resolver.findResValue("@dimen/dialog_min_width_major",
+ false).getValue());
+ assertNotNull(resolver.findResValue("@android:color/bright_foreground_dark", true));
+ assertEquals("@android:color/background_light",
+ resolver.findResValue("@android:color/bright_foreground_dark", true).getValue());
+ assertEquals("#ffffffff",
+ resolver.findResValue("@android:color/background_light", true).getValue());
+
+ // resolveResValue
+ // android:color/bright_foreground_dark => @android:color/background_light => white
+ assertEquals("Todo", resolver.resolveResValue(
+ resolver.findResValue("@string/show_all_apps", false)).getValue());
+ assertEquals("#ffffffff", resolver.resolveResValue(
+ resolver.findResValue("@android:color/bright_foreground_dark", false)).getValue());
+
+
+ // Now do everything over again, but this time without a resource resolver.
+ // Also set a lookup chain.
+ resolver = new ResourceItemResolver(config, frameworkResources, appResources,
+ logger);
+ List<ResourceValue> chain = Lists.newArrayList();
+ resolver.setLookupChainList(chain);
+
+ // findResValue
+ assertNotNull(resolver.findResValue("@string/show_all_apps", false));
+ assertNotNull(resolver.findResValue("@android:string/ok", false));
+ assertNotNull(resolver.findResValue("@android:string/ok", true));
+ assertEquals("Todo", resolver.findResValue("@string/show_all_apps", false).getValue());
+ assertEquals("Home Sample", resolver.findResValue("@string/home_title", false).getValue());
+ assertEquals("45%", resolver.findResValue("@dimen/dialog_min_width_major",
+ false).getValue());
+ assertNotNull(resolver.findResValue("@android:color/bright_foreground_dark", true));
+ assertEquals("@android:color/background_light",
+ resolver.findResValue("@android:color/bright_foreground_dark", true).getValue());
+ assertEquals("#ffffffff",
+ resolver.findResValue("@android:color/background_light", true).getValue());
+ assertEquals("Todo", resolver.resolveResValue(
+ resolver.findResValue("@string/show_all_apps", false)).getValue());
+
+ chain.clear();
+ ResourceValue v = resolver.findResValue("@android:color/bright_foreground_dark", false);
+ assertEquals("@android:color/bright_foreground_dark => @android:color/background_light",
+ ResourceItemResolver.getDisplayString(ResourceType.COLOR, "bright_foreground_dark",
+ true, chain));
+ chain.clear();
+ assertEquals("#ffffffff", resolver.resolveResValue(v).getValue());
+ assertEquals("@android:color/bright_foreground_dark => @android:color/background_light "
+ + "=> #ffffffff",
+ ResourceItemResolver.getDisplayString("@android:color/bright_foreground_dark",
+ chain));
+
+ // Try to resolve style attributes
+ resolver = new ResourceItemResolver(config, provider, logger);
+ resolver.setLookupChainList(chain);
+ chain.clear();
+ ResourceValue target = new ResourceValue(ResourceType.STRING, "dummy", false);
+ target.setValue("?foo");
+ assertEquals("#ff000000", resolver.resolveResValue(target).getValue());
+ assertEquals("?foo => ?android:colorForeground => @color/bright_foreground_light => "
+ + "@android:color/background_dark => #ff000000",
+ ResourceItemResolver.getDisplayString("?foo", chain));
+
+ assertEquals("?foo => ?android:colorForeground => @color/bright_foreground_light => "
+ + "@android:color/background_dark => #ff000000",
+ ResourceItemResolver.getDisplayString("?foo", chain));
+ }
+}
diff --git a/sdk-common/src/test/java/com/android/ide/common/resources/ResourceRepositoryTest.java b/sdk-common/src/test/java/com/android/ide/common/resources/ResourceRepositoryTest.java
index 5b82314..df3d874 100644
--- a/sdk-common/src/test/java/com/android/ide/common/resources/ResourceRepositoryTest.java
+++ b/sdk-common/src/test/java/com/android/ide/common/resources/ResourceRepositoryTest.java
@@ -18,18 +18,12 @@
import static com.android.SdkConstants.FD_RES;
import static com.android.SdkConstants.FD_RES_DRAWABLE;
import static com.android.SdkConstants.FD_RES_LAYOUT;
-import static com.android.SdkConstants.FD_RES_VALUES;
-import static com.android.resources.ResourceType.ATTR;
-import static com.android.resources.ResourceType.DIMEN;
-import static com.android.resources.ResourceType.LAYOUT;
-import com.android.annotations.NonNull;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.ide.common.resources.configuration.LanguageQualifier;
import com.android.ide.common.resources.configuration.ScreenOrientationQualifier;
import com.android.io.FileWrapper;
-import com.android.io.FolderWrapper;
import com.android.io.IAbstractFile;
import com.android.io.IAbstractFolder;
import com.android.resources.ResourceFolderType;
@@ -48,97 +42,44 @@
@SuppressWarnings("javadoc")
public class ResourceRepositoryTest extends TestCase {
- private File mTempDir;
- private ResourceRepository mRepository;
+ private TestResourceRepository mRepository;
- @SuppressWarnings("ResultOfMethodCallIgnored")
@Override
protected void setUp() throws Exception {
super.setUp();
- mTempDir = Files.createTempDir();
- File res = new File(mTempDir, FD_RES);
- res.mkdirs();
- File layout = new File(res, FD_RES_LAYOUT);
- File layoutLand = new File(res, FD_RES_LAYOUT + "-land");
- File values = new File(res, FD_RES_VALUES);
- File valuesEs = new File(res, FD_RES_VALUES + "-es");
- File drawable = new File(res, FD_RES_DRAWABLE);
- layout.mkdirs();
- layoutLand.mkdirs();
- values.mkdirs();
- valuesEs.mkdirs();
- drawable.mkdirs();
- new File(layout, "layout1.xml").createNewFile();
- new File(layoutLand, "layout1.xml").createNewFile();
- new File(layoutLand, "onlyLand.xml").createNewFile();
- new File(layout, "layout2.xml").createNewFile();
- new File(drawable, "graphic.9.png").createNewFile();
- File strings = new File(values, "strings.xml");
- Files.write(""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <item type=\"id\" name=\"action_bar_refresh\" />\n"
- + " <item type=\"dimen\" name=\"dialog_min_width_major\">45%</item>\n"
- + " <string name=\"home_title\">Home Sample</string>\n"
- + " <string name=\"show_all_apps\">All</string>\n"
- + " <string name=\"menu_wallpaper\">Wallpaper</string>\n"
- + " <string name=\"menu_search\">Search</string>\n"
- + " <string name=\"menu_settings\">Settings</string>\n"
- + " <string name=\"dummy\" translatable=\"false\">Ignore Me</string>\n"
- + " <string name=\"wallpaper_instructions\">Tap picture to set portrait wallpaper</string>\n"
- + "</resources>\n", strings, Charsets.UTF_8);
-
- Files.write(""
- + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
- + "<resources>\n"
- + " <string name=\"show_all_apps\">Todo</string>\n"
- + "</resources>\n", new File(valuesEs, "strings.xml"), Charsets.UTF_8);
-
- IAbstractFolder resFolder = new FolderWrapper(new File(mTempDir, FD_RES));
- mRepository = new TestResourceRepository(resFolder, false);
+ mRepository = TestResourceRepository.create(false, new Object[]{
+ "layout/layout1.xml", "<!--contents doesn't matter-->",
+ "layout/layout2.xml", "<!--contents doesn't matter-->",
+ "layout-land/layout1.xml", "<!--contents doesn't matter-->",
+ "layout-land/onlyLand.xml", "<!--contents doesn't matter-->",
+ "drawable/graphic.9.png", new byte[0],
+ "values/strings.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <item type=\"id\" name=\"action_bar_refresh\" />\n"
+ + " <item type=\"dimen\" name=\"dialog_min_width_major\">45%</item>\n"
+ + " <string name=\"home_title\">Home Sample</string>\n"
+ + " <string name=\"show_all_apps\">All</string>\n"
+ + " <string name=\"menu_wallpaper\">Wallpaper</string>\n"
+ + " <string name=\"menu_search\">Search</string>\n"
+ + " <string name=\"menu_settings\">Settings</string>\n"
+ + " <string name=\"dummy\" translatable=\"false\">Ignore Me</string>\n"
+ + " <string name=\"wallpaper_instructions\">Tap picture to set portrait wallpaper</string>\n"
+ + "</resources>\n",
+ "values-es/strings.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <string name=\"show_all_apps\">Todo</string>\n"
+ + "</resources>\n",
+ });
assertFalse(mRepository.isFrameworkRepository());
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
-
- deleteFile(mTempDir);
- }
-
- private static void deleteFile(File dir) {
- if (dir.isDirectory()) {
- File[] files = dir.listFiles();
- if (files != null) {
- for (File f : files) {
- deleteFile(f);
- }
- }
- } else if (dir.isFile()) {
- assertTrue(dir.getPath(), dir.delete());
- }
- }
-
- public void testParseResource() {
- assertNull(ResourceRepository.parseResource(""));
- assertNull(ResourceRepository.parseResource("not_a_resource"));
-
- assertEquals(LAYOUT, ResourceRepository.parseResource("@layout/foo").getFirst());
- assertEquals(DIMEN, ResourceRepository.parseResource("@dimen/foo").getFirst());
- assertEquals(DIMEN, ResourceRepository.parseResource("@android:dimen/foo").getFirst());
- assertEquals("foo", ResourceRepository.parseResource("@layout/foo").getSecond());
- assertEquals("foo", ResourceRepository.parseResource("@dimen/foo").getSecond());
- assertEquals("foo", ResourceRepository.parseResource("@android:dimen/foo").getSecond());
-
- assertEquals(ATTR, ResourceRepository.parseResource("?attr/foo").getFirst());
- assertEquals("foo", ResourceRepository.parseResource("?attr/foo").getSecond());
-
- assertEquals(ATTR, ResourceRepository.parseResource("?foo").getFirst());
- assertEquals("foo", ResourceRepository.parseResource("?foo").getSecond());
-
- assertEquals(ATTR, ResourceRepository.parseResource("?android:foo").getFirst());
- assertEquals("foo", ResourceRepository.parseResource("?android:foo").getSecond());
+ mRepository.dispose();
}
public void testBasic() throws Exception {
@@ -338,7 +279,7 @@
// add files
assertFalse(mRepository.hasResourceItem("@layout/layout5"));
- File res = new File(mTempDir, FD_RES);
+ File res = new File(mRepository.getDir(), FD_RES);
File layout = new File(res, FD_RES_LAYOUT);
File newFile = new File(layout, "layout5.xml");
boolean created = newFile.createNewFile();
@@ -385,23 +326,4 @@
getParentFile()));
assertNull(mRepository.findResourceFile(new File("/tmp")));
}
-
- private static class TestResourceRepository extends ResourceRepository {
- private TestResourceRepository(@NonNull IAbstractFolder resFolder,
- boolean isFrameworkRepository) {
- super(resFolder, isFrameworkRepository);
- }
-
- @NonNull
- @Override
- protected ResourceItem createResourceItem(@NonNull String name) {
- return new TestResourceItem(name);
- }
- }
-
- private static class TestResourceItem extends ResourceItem {
- TestResourceItem(String name) {
- super(name);
- }
- }
}
diff --git a/sdk-common/src/test/java/com/android/ide/common/resources/ResourceResolverTest.java b/sdk-common/src/test/java/com/android/ide/common/resources/ResourceResolverTest.java
new file mode 100644
index 0000000..29616a4
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/resources/ResourceResolverTest.java
@@ -0,0 +1,434 @@
+package com.android.ide.common.resources;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.StyleResourceValue;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.resources.ResourceType;
+import com.google.common.collect.Lists;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class ResourceResolverTest extends TestCase {
+ public void test() throws Exception {
+ TestResourceRepository frameworkRepository = TestResourceRepository.create(true,
+ new Object[]{
+ "values/strings.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <string name=\"ok\">Ok</string>\n"
+ + "</resources>\n",
+
+ "values/themes.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <style name=\"Theme\">\n"
+ + " <item name=\"colorForeground\">@android:color/bright_foreground_dark</item>\n"
+ + " <item name=\"colorBackground\">@android:color/background_dark</item>\n"
+ + " </style>\n"
+ + " <style name=\"Theme.Light\">\n"
+ + " <item name=\"colorBackground\">@android:color/background_light</item>\n"
+ + " <item name=\"colorForeground\">@color/bright_foreground_light</item>\n"
+ + " </style>\n"
+ + "</resources>\n",
+
+ "values/colors.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <color name=\"background_dark\">#ff000000</color>\n"
+ + " <color name=\"background_light\">#ffffffff</color>\n"
+ + " <color name=\"bright_foreground_dark\">@android:color/background_light</color>\n"
+ + " <color name=\"bright_foreground_light\">@android:color/background_dark</color>\n"
+ + "</resources>\n",
+ });
+
+ TestResourceRepository projectRepository = TestResourceRepository.create(false,
+ new Object[]{
+ "layout/layout1.xml", "<!--contents doesn't matter-->",
+
+ "layout/layout2.xml", "<!--contents doesn't matter-->",
+
+ "layout-land/layout1.xml", "<!--contents doesn't matter-->",
+
+ "layout-land/onlyLand.xml", "<!--contents doesn't matter-->",
+
+ "drawable/graphic.9.png", new byte[0],
+
+ "values/styles.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <style name=\"MyTheme\" parent=\"android:Theme.Light\">\n"
+ + " <item name=\"android:textColor\">#999999</item>\n"
+ + " <item name=\"foo\">?android:colorForeground</item>\n"
+ + " </style>\n"
+ + " <style name=\"MyTheme.Dotted1\" parent=\"\">\n"
+ + " </style>"
+ + " <style name=\"MyTheme.Dotted2\">\n"
+ + " </style>"
+ + " <style name=\"RandomStyle\">\n"
+ + " <item name=\"android:text\">© Copyright</item>\n"
+ + " </style>"
+ + " <style name=\"RandomStyle2\" parent=\"RandomStyle\">\n"
+ + " </style>"
+ + "</resources>\n",
+
+ "values/strings.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <item type=\"id\" name=\"action_bar_refresh\" />\n"
+ + " <item type=\"dimen\" name=\"dialog_min_width_major\">45%</item>\n"
+ + " <string name=\"home_title\">Home Sample</string>\n"
+ + " <string name=\"show_all_apps\">All</string>\n"
+ + " <string name=\"menu_wallpaper\">Wallpaper</string>\n"
+ + " <string name=\"menu_search\">Search</string>\n"
+ + " <string name=\"menu_settings\">Settings</string>\n"
+ + " <string name=\"dummy\" translatable=\"false\">Ignore Me</string>\n"
+ + " <string name=\"wallpaper_instructions\">Tap picture to set portrait wallpaper</string>\n"
+ + "</resources>\n",
+
+ "values-es/strings.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <string name=\"show_all_apps\">Todo</string>\n"
+ + "</resources>\n",
+ });
+
+ assertFalse(projectRepository.isFrameworkRepository());
+ FolderConfiguration config = FolderConfiguration.getConfigForFolder("values-es-land");
+ assertNotNull(config);
+ Map<ResourceType, Map<String, ResourceValue>> projectResources =
+ projectRepository.getConfiguredResources(config);
+ Map<ResourceType, Map<String, ResourceValue>> frameworkResources =
+ frameworkRepository.getConfiguredResources(config);
+ assertNotNull(projectResources);
+ ResourceResolver resolver = ResourceResolver.create(projectResources, frameworkResources,
+ "MyTheme", true);
+ assertNotNull(resolver);
+
+ LayoutLog logger = new LayoutLog() {
+ @Override
+ public void warning(String tag, String message, Object data) {
+ fail(message);
+ }
+
+ @Override
+ public void fidelityWarning(String tag, String message, Throwable throwable,
+ Object data) {
+ fail(message);
+ }
+
+ @Override
+ public void error(String tag, String message, Object data) {
+ fail(message);
+ }
+
+ @Override
+ public void error(String tag, String message, Throwable throwable, Object data) {
+ fail(message);
+ }
+ };
+ resolver.setLogger(logger);
+
+ assertEquals("MyTheme", resolver.getThemeName());
+ assertTrue(resolver.isProjectTheme());
+
+ // findResValue
+ assertNotNull(resolver.findResValue("@string/show_all_apps", false));
+ assertNotNull(resolver.findResValue("@android:string/ok", false));
+ assertNotNull(resolver.findResValue("@android:string/ok", true));
+ assertEquals("Todo", resolver.findResValue("@string/show_all_apps", false).getValue());
+ assertEquals("Home Sample", resolver.findResValue("@string/home_title", false).getValue());
+ assertEquals("45%", resolver.findResValue("@dimen/dialog_min_width_major",
+ false).getValue());
+ assertNotNull(resolver.findResValue("@android:color/bright_foreground_dark", true));
+ assertEquals("@android:color/background_light",
+ resolver.findResValue("@android:color/bright_foreground_dark", true).getValue());
+ assertEquals("#ffffffff",
+ resolver.findResValue("@android:color/background_light", true).getValue());
+
+ // getTheme
+ StyleResourceValue myTheme = resolver.getTheme("MyTheme", false);
+ assertNotNull(myTheme);
+ assertSame(resolver.findResValue("@style/MyTheme", false), myTheme);
+ assertNull(resolver.getTheme("MyTheme", true));
+ assertNull(resolver.getTheme("MyNonexistentTheme", true));
+ StyleResourceValue themeLight = resolver.getTheme("Theme.Light", true);
+ assertNotNull(themeLight);
+ StyleResourceValue theme = resolver.getTheme("Theme", true);
+ assertNotNull(theme);
+
+ // themeIsParentOf
+ assertTrue(resolver.themeIsParentOf(themeLight, myTheme));
+ assertFalse(resolver.themeIsParentOf(myTheme, themeLight));
+ assertTrue(resolver.themeIsParentOf(theme, themeLight));
+ assertFalse(resolver.themeIsParentOf(themeLight, theme));
+ assertTrue(resolver.themeIsParentOf(theme, myTheme));
+ assertFalse(resolver.themeIsParentOf(myTheme, theme));
+ StyleResourceValue dotted1 = resolver.getTheme("MyTheme.Dotted1", false);
+ assertNotNull(dotted1);
+ StyleResourceValue dotted2 = resolver.getTheme("MyTheme.Dotted2", false);
+ assertNotNull(dotted2);
+ assertTrue(resolver.themeIsParentOf(myTheme, dotted2));
+ assertFalse(resolver.themeIsParentOf(myTheme, dotted1)); // because parent=""
+
+ // isTheme
+ assertFalse(resolver.isTheme(resolver.findResValue("@style/RandomStyle", false), null));
+ assertFalse(resolver.isTheme(resolver.findResValue("@style/RandomStyle2", false), null));
+ // check XML escaping in value resources
+ StyleResourceValue randomStyle = (StyleResourceValue) resolver.findResValue(
+ "@style/RandomStyle", false);
+ assertEquals("\u00a9 Copyright", randomStyle.findValue("text", true).getValue());
+ assertTrue(resolver.isTheme(resolver.findResValue("@style/MyTheme.Dotted2", false), null));
+ assertFalse(resolver.isTheme(resolver.findResValue("@style/MyTheme.Dotted1", false),
+ null));
+ assertTrue(resolver.isTheme(resolver.findResValue("@style/MyTheme", false), null));
+ assertTrue(resolver.isTheme(resolver.findResValue("@android:style/Theme.Light", false),
+ null));
+ assertTrue(resolver.isTheme(resolver.findResValue("@android:style/Theme", false), null));
+
+ // findItemInStyle
+ assertNotNull(resolver.findItemInStyle(myTheme, "colorForeground", true));
+ assertEquals("@color/bright_foreground_light",
+ resolver.findItemInStyle(myTheme, "colorForeground", true).getValue());
+ assertNotNull(resolver.findItemInStyle(dotted2, "colorForeground", true));
+ assertNull(resolver.findItemInStyle(dotted1, "colorForeground", true));
+
+ // findItemInTheme
+ assertNotNull(resolver.findItemInTheme("colorForeground", true));
+ assertEquals("@color/bright_foreground_light",
+ resolver.findItemInTheme("colorForeground", true).getValue());
+ assertEquals("@color/bright_foreground_light",
+ resolver.findResValue("?colorForeground", true).getValue());
+ ResourceValue target = new ResourceValue(ResourceType.STRING, "dummy", false);
+ target.setValue("?foo");
+ assertEquals("#ff000000", resolver.resolveResValue(target).getValue());
+
+ // getFrameworkResource
+ assertNull(resolver.getFrameworkResource(ResourceType.STRING, "show_all_apps"));
+ assertNotNull(resolver.getFrameworkResource(ResourceType.STRING, "ok"));
+ assertEquals("Ok", resolver.getFrameworkResource(ResourceType.STRING, "ok").getValue());
+
+ // getProjectResource
+ assertNull(resolver.getProjectResource(ResourceType.STRING, "ok"));
+ assertNotNull(resolver.getProjectResource(ResourceType.STRING, "show_all_apps"));
+ assertEquals("Todo", resolver.getProjectResource(ResourceType.STRING,
+ "show_all_apps").getValue());
+
+
+ // resolveResValue
+ // android:color/bright_foreground_dark => @android:color/background_light => white
+ assertEquals("Todo", resolver.resolveResValue(
+ resolver.findResValue("@string/show_all_apps", false)).getValue());
+ assertEquals("#ffffffff", resolver.resolveResValue(
+ resolver.findResValue("@android:color/bright_foreground_dark", false)).getValue());
+
+ // resolveValue
+ assertEquals("#ffffffff",
+ resolver.resolveValue(ResourceType.STRING, "bright_foreground_dark",
+ "@android:color/background_light", true).getValue());
+
+ // themeExtends
+ assertTrue(resolver.themeExtends("@android:style/Theme", "@android:style/Theme"));
+ assertTrue(resolver.themeExtends("@android:style/Theme", "@android:style/Theme.Light"));
+ assertFalse(resolver.themeExtends("@android:style/Theme.Light", "@android:style/Theme"));
+ assertTrue(resolver.themeExtends("@style/MyTheme.Dotted2", "@style/MyTheme.Dotted2"));
+ assertTrue(resolver.themeExtends("@style/MyTheme", "@style/MyTheme.Dotted2"));
+ assertTrue(resolver.themeExtends("@android:style/Theme.Light", "@style/MyTheme.Dotted2"));
+ assertTrue(resolver.themeExtends("@android:style/Theme", "@style/MyTheme.Dotted2"));
+ assertFalse(resolver.themeExtends("@style/MyTheme.Dotted1", "@style/MyTheme.Dotted2"));
+
+ // Switch to MyTheme.Dotted1 (to make sure the parent="" inheritance works properly.)
+ // To do that we need to create a new resource resolver.
+ resolver = ResourceResolver.create(projectResources, frameworkResources,
+ "MyTheme.Dotted1", true);
+ resolver.setLogger(logger);
+ assertNotNull(resolver);
+ assertEquals("MyTheme.Dotted1", resolver.getThemeName());
+ assertTrue(resolver.isProjectTheme());
+ assertNull(resolver.findItemInTheme("colorForeground", true));
+
+ resolver = ResourceResolver.create(projectResources, frameworkResources,
+ "MyTheme.Dotted2", true);
+ resolver.setLogger(logger);
+ assertNotNull(resolver);
+ assertEquals("MyTheme.Dotted2", resolver.getThemeName());
+ assertTrue(resolver.isProjectTheme());
+ assertNotNull(resolver.findItemInTheme("colorForeground", true));
+
+ // Test recording resolver
+ List<ResourceValue> chain = Lists.newArrayList();
+ resolver = ResourceResolver.create(projectResources, frameworkResources, "MyTheme", true);
+ resolver = resolver.createRecorder(chain);
+ assertNotNull(resolver.findResValue("@android:color/bright_foreground_dark", true));
+ ResourceValue v = resolver.findResValue("@android:color/bright_foreground_dark", false);
+ chain.clear();
+ assertEquals("#ffffffff", resolver.resolveResValue(v).getValue());
+ assertEquals("@android:color/bright_foreground_dark => "
+ + "@android:color/background_light => #ffffffff",
+ ResourceItemResolver.getDisplayString("@android:color/bright_foreground_dark",
+ chain));
+
+ frameworkRepository.dispose();
+ projectRepository.dispose();
+ }
+
+ public void testMissingMessage() throws Exception {
+ TestResourceRepository projectRepository = TestResourceRepository.create(false,
+ new Object[]{
+ "values/colors.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <color name=\"loop1\">@color/loop1</color>\n"
+ + " <color name=\"loop2a\">@color/loop2b</color>\n"
+ + " <color name=\"loop2b\">@color/loop2a</color>\n"
+ + "</resources>\n",
+
+ });
+
+ assertFalse(projectRepository.isFrameworkRepository());
+ FolderConfiguration config = FolderConfiguration.getConfigForFolder("values-es-land");
+ assertNotNull(config);
+ Map<ResourceType, Map<String, ResourceValue>> projectResources =
+ projectRepository.getConfiguredResources(config);
+ assertNotNull(projectResources);
+ ResourceResolver resolver = ResourceResolver.create(projectResources, projectResources,
+ "MyTheme", true);
+ final AtomicBoolean wasWarned = new AtomicBoolean(false);
+ LayoutLog logger = new LayoutLog() {
+ @Override
+ public void warning(String tag, String message, Object data) {
+ if ("Couldn't resolve resource @android:string/show_all_apps".equals(message)) {
+ wasWarned.set(true);
+ } else {
+ fail(message);
+ }
+ }
+ };
+ resolver.setLogger(logger);
+ assertNull(resolver.findResValue("@string/show_all_apps", true));
+ assertTrue(wasWarned.get());
+ projectRepository.dispose();
+ }
+
+ public void testLoop() throws Exception {
+ TestResourceRepository projectRepository = TestResourceRepository.create(false,
+ new Object[]{
+ "values/colors.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <color name=\"loop1\">@color/loop1</color>\n"
+ + " <color name=\"loop2a\">@color/loop2b</color>\n"
+ + " <color name=\"loop2b\">@color/loop2a</color>\n"
+ + "</resources>\n",
+
+ });
+
+ assertFalse(projectRepository.isFrameworkRepository());
+ FolderConfiguration config = FolderConfiguration.getConfigForFolder("values-es-land");
+ assertNotNull(config);
+ Map<ResourceType, Map<String, ResourceValue>> projectResources =
+ projectRepository.getConfiguredResources(config);
+ assertNotNull(projectResources);
+ ResourceResolver resolver = ResourceResolver.create(projectResources, projectResources,
+ "MyTheme", true);
+ assertNotNull(resolver);
+
+ final AtomicBoolean wasWarned = new AtomicBoolean(false);
+ LayoutLog logger = new LayoutLog() {
+ @Override
+ public void error(String tag, String message, Object data) {
+ if (("Potential stack overflow trying to resolve "
+ + "'@color/loop1': cyclic resource definitions?"
+ + " Render may not be accurate.").equals(message)) {
+ wasWarned.set(true);
+ } else if (("Potential stack overflow trying to resolve "
+ + "'@color/loop2b': cyclic resource definitions? "
+ + "Render may not be accurate.").equals(message)) {
+ wasWarned.set(true);
+ } else {
+ fail(message);
+ }
+ }
+ };
+ resolver.setLogger(logger);
+
+ assertNotNull(resolver.findResValue("@color/loop1", false));
+ resolver.resolveResValue(resolver.findResValue("@color/loop1", false));
+ assertTrue(wasWarned.get());
+
+ wasWarned.set(false);
+ assertNotNull(resolver.findResValue("@color/loop2a", false));
+ resolver.resolveResValue(resolver.findResValue("@color/loop2a", false));
+ assertTrue(wasWarned.get());
+
+ projectRepository.dispose();
+ }
+
+ public void testParentCycle() throws IOException {
+ TestResourceRepository projectRepository = TestResourceRepository.create(false,
+ new Object[]{
+ "values/styles.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<resources>\n"
+ + " <style name=\"ButtonStyle.Base\">\n"
+ + " <item name=\"android:textColor\">#ff0000</item>\n"
+ + " </style>\n"
+ + " <style name=\"ButtonStyle\" parent=\"ButtonStyle.Base\">\n"
+ + " <item name=\"android:layout_height\">40dp</item>\n"
+ + " </style>\n"
+ + "</resources>\n",
+
+ "layouts/layout.xml", ""
+ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ + " android:layout_width=\"match_parent\"\n"
+ + " android:layout_height=\"match_parent\">\n"
+ + "\n"
+ + " <TextView\n"
+ + " style=\"@style/ButtonStyle\"\n"
+ + " android:layout_width=\"wrap_content\"\n"
+ + " android:layout_height=\"wrap_content\" />\n"
+ + "\n"
+ + "</RelativeLayout>\n",
+
+ });
+ assertFalse(projectRepository.isFrameworkRepository());
+ FolderConfiguration config = FolderConfiguration.getConfigForFolder("values-es-land");
+ assertNotNull(config);
+ Map<ResourceType, Map<String, ResourceValue>> projectResources =
+ projectRepository.getConfiguredResources(config);
+ assertNotNull(projectResources);
+ ResourceResolver resolver = ResourceResolver.create(projectResources, projectResources,
+ "ButtonStyle", true);
+ assertNotNull(resolver);
+
+ final AtomicBoolean wasWarned = new AtomicBoolean(false);
+ LayoutLog logger = new LayoutLog() {
+ @Override
+ public void error(String tag, String message, Object data) {
+ assertEquals("Cyclic style parent definitions: \"ButtonStyle\" specifies "
+ + "parent \"ButtonStyle.Base\" implies parent \"ButtonStyle\"", message);
+ assertEquals(LayoutLog.TAG_BROKEN, tag);
+ wasWarned.set(true);
+ }
+ };
+ resolver.setLogger(logger);
+
+ StyleResourceValue buttonStyle = (StyleResourceValue) resolver.findResValue(
+ "@style/ButtonStyle", false);
+ ResourceValue textColor = resolver.findItemInStyle(buttonStyle, "textColor", true);
+ assertNotNull(textColor);
+ assertEquals("#ff0000", textColor.getValue());
+ assertFalse(wasWarned.get());
+ ResourceValue missing = resolver.findItemInStyle(buttonStyle, "missing", true);
+ assertNull(missing);
+ assertTrue(wasWarned.get());
+
+ projectRepository.dispose();
+ }
+}
diff --git a/sdk-common/src/test/java/com/android/ide/common/resources/ResourceUrlTest.java b/sdk-common/src/test/java/com/android/ide/common/resources/ResourceUrlTest.java
new file mode 100644
index 0000000..d59f919
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/resources/ResourceUrlTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.common.resources;
+
+import static com.android.resources.ResourceType.ATTR;
+import static com.android.resources.ResourceType.DIMEN;
+import static com.android.resources.ResourceType.ID;
+import static com.android.resources.ResourceType.LAYOUT;
+
+import junit.framework.TestCase;
+
+public class ResourceUrlTest extends TestCase {
+ @SuppressWarnings("ConstantConditions")
+ public void testParseResource() {
+ assertNull(ResourceUrl.parse(""));
+ assertNull(ResourceUrl.parse("not_a_resource"));
+ assertNull(ResourceUrl.parse("@null"));
+ assertNull(ResourceUrl.parse("@?"));
+ assertNull(ResourceUrl.parse("@android:layout"));
+ assertNull(ResourceUrl.parse("@layout"));
+
+ assertEquals("foo", ResourceUrl.parse("@id/foo").name);
+ assertEquals(ID, ResourceUrl.parse("@id/foo").type);
+ assertFalse(ResourceUrl.parse("@id/foo").framework);
+ assertFalse(ResourceUrl.parse("@id/foo").create);
+ assertFalse(ResourceUrl.parse("@id/foo").theme);
+
+ assertEquals("foo", ResourceUrl.parse("@+id/foo").name);
+ assertEquals(ID, ResourceUrl.parse("@+id/foo").type);
+ assertFalse(ResourceUrl.parse("@+id/foo").framework);
+ assertTrue(ResourceUrl.parse("@+id/foo").create);
+
+ assertEquals(LAYOUT, ResourceUrl.parse("@layout/foo").type);
+ assertEquals(DIMEN, ResourceUrl.parse("@dimen/foo").type);
+ assertFalse(ResourceUrl.parse("@dimen/foo").framework);
+ assertEquals("foo", ResourceUrl.parse("@android:dimen/foo").name);
+ assertEquals(DIMEN, ResourceUrl.parse("@android:dimen/foo").type);
+ assertTrue(ResourceUrl.parse("@android:dimen/foo").framework);
+ assertEquals("foo", ResourceUrl.parse("@layout/foo").name);
+ assertEquals("foo", ResourceUrl.parse("@dimen/foo").name);
+ assertEquals(ATTR, ResourceUrl.parse("?attr/foo").type);
+ assertTrue(ResourceUrl.parse("?attr/foo").theme);
+ assertEquals("foo", ResourceUrl.parse("?attr/foo").name);
+ assertFalse(ResourceUrl.parse("?attr/foo").framework);
+ assertEquals(ATTR, ResourceUrl.parse("?foo").type);
+ assertEquals("foo", ResourceUrl.parse("?foo").name);
+ assertFalse(ResourceUrl.parse("?foo").framework);
+ assertEquals(ATTR, ResourceUrl.parse("?android:foo").type);
+ assertEquals("foo", ResourceUrl.parse("?android:foo").name);
+ assertTrue(ResourceUrl.parse("?android:foo").framework);
+ assertTrue(ResourceUrl.parse("?android:foo").theme);
+
+ assertEquals("@+id/foo", ResourceUrl.parse("@+id/foo").toString());
+ assertEquals("@layout/foo", ResourceUrl.parse("@layout/foo").toString());
+ assertEquals("@android:layout/foo", ResourceUrl.parse("@android:layout/foo").toString());
+ assertEquals("?android:attr/foo", ResourceUrl.parse("?android:foo").toString());
+
+ assertTrue(ResourceUrl.parse("@id/foo").hasValidName());
+ assertFalse(ResourceUrl.parse("@id/foo bar").hasValidName());
+ assertFalse(ResourceUrl.parse("@id/").hasValidName());
+ assertFalse(ResourceUrl.parse("@id/?").hasValidName());
+ assertFalse(ResourceUrl.parse("@id/123").hasValidName());
+ assertFalse(ResourceUrl.parse("@id/ab+").hasValidName());
+
+ }
+}
diff --git a/sdk-common/src/test/java/com/android/ide/common/resources/TestResourceRepository.java b/sdk-common/src/test/java/com/android/ide/common/resources/TestResourceRepository.java
new file mode 100644
index 0000000..492ea1c
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/resources/TestResourceRepository.java
@@ -0,0 +1,151 @@
+package com.android.ide.common.resources;
+
+import static com.android.SdkConstants.FD_RES;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import com.android.annotations.NonNull;
+import com.android.ide.common.res2.MergingException;
+import com.android.ide.common.res2.RecordingLogger;
+import com.android.ide.common.res2.ResourceMerger;
+import com.android.ide.common.res2.ResourceSet;
+import com.android.io.FolderWrapper;
+import com.android.io.IAbstractFolder;
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.IOException;
+
+public class TestResourceRepository extends ResourceRepository {
+ private final File mDir;
+
+ TestResourceRepository(@NonNull IAbstractFolder resFolder, boolean isFrameworkRepository,
+ File dir) {
+ super(resFolder, isFrameworkRepository);
+ mDir = dir;
+ }
+
+ @NonNull
+ @Override
+ protected ResourceItem createResourceItem(@NonNull String name) {
+ return new TestResourceItem(name);
+ }
+
+ public File getDir() {
+ return mDir;
+ }
+
+ public void dispose() {
+ deleteFile(mDir);
+ }
+
+ private static void deleteFile(File dir) {
+ if (dir.isDirectory()) {
+ File[] files = dir.listFiles();
+ if (files != null) {
+ for (File f : files) {
+ deleteFile(f);
+ }
+ }
+ } else if (dir.isFile()) {
+ assertTrue(dir.getPath(), dir.delete());
+ }
+ }
+
+ /**
+ * Creates a resource repository for a resource folder whose contents is identified
+ * by the pairs of relative paths and file contents
+ */
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ @NonNull
+ public static TestResourceRepository create(boolean isFramework, Object[] data)
+ throws IOException {
+ File dir = Files.createTempDir();
+ File res = new File(dir, FD_RES);
+ res.mkdirs();
+
+ assertTrue("Expected even number of items (path,contents)", data.length % 2 == 0);
+ for (int i = 0; i < data.length; i += 2) {
+ Object relativePathObject = data[i];
+ assertTrue(relativePathObject instanceof String);
+ String relativePath = (String) relativePathObject;
+ relativePath = relativePath.replace('/', File.separatorChar);
+ File file = new File(res, relativePath);
+ File parent = file.getParentFile();
+ parent.mkdirs();
+
+ Object fileContents = data[i + 1];
+ if (fileContents instanceof String) {
+ String text = (String) fileContents;
+ Files.write(text, file, Charsets.UTF_8);
+ } else if (fileContents instanceof byte[]) {
+ byte[] bytes = (byte[]) fileContents;
+ Files.write(bytes, file);
+ } else {
+ fail("File contents must be Strings or byte[]'s");
+ }
+ }
+
+ IAbstractFolder resFolder = new FolderWrapper(dir, FD_RES);
+ return new TestResourceRepository(resFolder, isFramework, dir);
+ }
+
+ /**
+ * Creates a res2 resource repository for a resource folder whose contents is identified
+ * by the pairs of relative paths and file contents
+ *
+ * @see #create(boolean, Object[])
+ */
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ @NonNull
+ public static com.android.ide.common.res2.ResourceRepository createRes2(
+ boolean isFramework, Object[] data)
+ throws IOException, MergingException {
+ File dir = Files.createTempDir();
+ File res = new File(dir, FD_RES);
+ res.mkdirs();
+
+ assertTrue("Expected even number of items (path,contents)", data.length % 2 == 0);
+ for (int i = 0; i < data.length; i += 2) {
+ Object relativePathObject = data[i];
+ assertTrue(relativePathObject instanceof String);
+ String relativePath = (String) relativePathObject;
+ relativePath = relativePath.replace('/', File.separatorChar);
+ File file = new File(res, relativePath);
+ File parent = file.getParentFile();
+ parent.mkdirs();
+
+ Object fileContents = data[i + 1];
+ if (fileContents instanceof String) {
+ String text = (String) fileContents;
+ Files.write(text, file, Charsets.UTF_8);
+ } else if (fileContents instanceof byte[]) {
+ byte[] bytes = (byte[]) fileContents;
+ Files.write(bytes, file);
+ } else {
+ fail("File contents must be Strings or byte[]'s");
+ }
+ }
+
+ File resFolder = new File(dir, FD_RES);
+
+ ResourceMerger merger = new ResourceMerger();
+ ResourceSet resourceSet = new ResourceSet("main");
+ resourceSet.addSource(resFolder);
+ resourceSet.loadFromFiles(new RecordingLogger());
+ merger.addDataSet(resourceSet);
+
+ com.android.ide.common.res2.ResourceRepository repository;
+ repository = new com.android.ide.common.res2.ResourceRepository(isFramework);
+ merger.mergeData(repository.createMergeConsumer(), true /*doCleanUp*/);
+
+ return repository;
+ }
+
+ private static class TestResourceItem extends ResourceItem {
+ TestResourceItem(String name) {
+ super(name);
+ }
+ }
+}
diff --git a/sdk-common/src/test/java/com/android/ide/common/resources/configuration/FolderConfigurationTest.java b/sdk-common/src/test/java/com/android/ide/common/resources/configuration/FolderConfigurationTest.java
index 3f850ab..085f0ff 100644
--- a/sdk-common/src/test/java/com/android/ide/common/resources/configuration/FolderConfigurationTest.java
+++ b/sdk-common/src/test/java/com/android/ide/common/resources/configuration/FolderConfigurationTest.java
@@ -16,6 +16,7 @@
package com.android.ide.common.resources.configuration;
+import com.android.resources.ResourceFolderType;
import junit.framework.TestCase;
import java.util.ArrayList;
@@ -99,6 +100,15 @@
assertNull(configForFolder.getLayoutDirectionQualifier());
}
+ public void testToStrings() {
+ FolderConfiguration configForFolder = FolderConfiguration.getConfigForFolder("values-en-rUS");
+ assertNotNull(configForFolder);
+ assertEquals("Locale Language en_Region US", configForFolder.toDisplayString());
+ assertEquals("en,US", configForFolder.toShortDisplayString());
+ assertEquals("layout-en-rUS", configForFolder.getFolderName(ResourceFolderType.LAYOUT));
+ assertEquals("-en-rUS", configForFolder.getUniqueKey());
+ }
+
// --- helper methods
private final static class MockConfigurable implements Configurable {
diff --git a/sdk-common/src/test/java/com/android/ide/common/sdk/SdkVersionInfoTest.java b/sdk-common/src/test/java/com/android/ide/common/sdk/SdkVersionInfoTest.java
new file mode 100644
index 0000000..f7be54b
--- /dev/null
+++ b/sdk-common/src/test/java/com/android/ide/common/sdk/SdkVersionInfoTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.common.sdk;
+
+import static com.android.ide.common.sdk.SdkVersionInfo.HIGHEST_KNOWN_API;
+import static com.android.ide.common.sdk.SdkVersionInfo.camelCaseToUnderlines;
+import static com.android.ide.common.sdk.SdkVersionInfo.getApiByBuildCode;
+import static com.android.ide.common.sdk.SdkVersionInfo.getApiByPreviewName;
+import static com.android.ide.common.sdk.SdkVersionInfo.getBuildCode;
+import static com.android.ide.common.sdk.SdkVersionInfo.underlinesToCamelCase;
+
+import junit.framework.TestCase;
+
+public class SdkVersionInfoTest extends TestCase {
+
+ public void testGetAndroidName() {
+ assertEquals("API 16: Android 4.1 (Jelly Bean)", SdkVersionInfo.getAndroidName(16));
+ }
+
+ public void testGetBuildCode() {
+ assertEquals("JELLY_BEAN", getBuildCode(16));
+ }
+
+ public void testGetApiByPreviewName() {
+ assertEquals(5, getApiByPreviewName("Eclair", false));
+ assertEquals(18, getApiByPreviewName("JellyBeanMR2", false));
+ assertEquals(-1, getApiByPreviewName("UnknownName", false));
+ assertEquals(HIGHEST_KNOWN_API + 1, getApiByPreviewName("UnknownName", true));
+ }
+
+ public void testGetApiByBuildCode() {
+ assertEquals(7, getApiByBuildCode("ECLAIR_MR1", false));
+ assertEquals(16, getApiByBuildCode("JELLY_BEAN", false));
+
+ for (int api = 1; api <= HIGHEST_KNOWN_API; api++) {
+ assertEquals(api, getApiByBuildCode(getBuildCode(api), false));
+ }
+
+ assertEquals(-1, getApiByBuildCode("K_SURPRISE_SURPRISE", false));
+ assertEquals(HIGHEST_KNOWN_API + 1, getApiByBuildCode("K_SURPRISE_SURPRISE", true));
+ }
+
+ public void testCamelCaseToUnderlines() {
+ assertEquals("", camelCaseToUnderlines(""));
+ assertEquals("foo", camelCaseToUnderlines("foo"));
+ assertEquals("foo", camelCaseToUnderlines("Foo"));
+ assertEquals("foo_bar", camelCaseToUnderlines("FooBar"));
+ assertEquals("test_xml", camelCaseToUnderlines("testXML"));
+ assertEquals("test_foo", camelCaseToUnderlines("testFoo"));
+ assertEquals("jelly_bean_mr2", camelCaseToUnderlines("JellyBeanMR2"));
+ }
+
+ public void testUnderlinesToCamelCase() {
+ assertEquals("", underlinesToCamelCase(""));
+ assertEquals("", underlinesToCamelCase("_"));
+ assertEquals("Foo", underlinesToCamelCase("foo"));
+ assertEquals("FooBar", underlinesToCamelCase("foo_bar"));
+ assertEquals("FooBar", underlinesToCamelCase("foo__bar"));
+ assertEquals("Foo", underlinesToCamelCase("foo_"));
+ assertEquals("JellyBeanMr2", underlinesToCamelCase("jelly_bean_mr2"));
+ }
+}
diff --git a/sdk-common/src/test/resources/testData/resources/baseSet/values/values.xml b/sdk-common/src/test/resources/testData/resources/baseSet/values/values.xml
index 168813e..9ce3842 100644
--- a/sdk-common/src/test/resources/testData/resources/baseSet/values/values.xml
+++ b/sdk-common/src/test/resources/testData/resources/baseSet/values/values.xml
@@ -70,6 +70,10 @@
<attr name="android:colorForegroundInverse" />
<attr name="blah2" />
+ <attr name="flagAttr">
+ <flag name="flag1a" value="0x30" />
+ <flag name="flag1b" value="0x40" />
+ </attr>
</declare-styleable>
<!-- The width that is used when creating thumbnails of applications. -->
@@ -82,4 +86,7 @@
<item type="layout" name="layout_ref">@layout/ref</item>
<item type="layout" name="alias_replaced_by_file">@layout/ref</item>
+ <plurals name="plurals">
+ <item quantity="one">test2 <xliff:g xmlns="urn:oasis:names:tc:xliff:document:1.2" id="test3">%s</xliff:g> test4</item>
+ </plurals>
</resources>
diff --git a/sdk-common/src/test/resources/testData/resources/brokenSet4/values/values.xml b/sdk-common/src/test/resources/testData/resources/brokenSet4/values/values.xml
new file mode 100644
index 0000000..cd83017
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/resources/brokenSet4/values/values.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <attr name="d_common_attr" format="string"/>
+
+ <declare-styleable name="StyleableExample">
+ <attr name="d_common_attr" format="string"/>
+ </wrong-end-tag>
+</resources>
diff --git a/sdk-common/src/test/resources/testData/resources/brokenSet5/layout/ActivityMain.xml b/sdk-common/src/test/resources/testData/resources/brokenSet5/layout/ActivityMain.xml
new file mode 100644
index 0000000..37ba9b5
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/resources/brokenSet5/layout/ActivityMain.xml
@@ -0,0 +1,3 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
diff --git a/sdk-common/src/test/resources/testData/resources/brokenSet6/values/dimens.xml b/sdk-common/src/test/resources/testData/resources/brokenSet6/values/dimens.xml
new file mode 100644
index 0000000..f505973
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/resources/brokenSet6/values/dimens.xml
@@ -0,0 +1,5 @@
+<resources>
+ <dimen name="activity_horizontal_margin">16dp</dimen>
+ <dimen name="activity_vertical_margin">16dp</dimen>
+ <
+</resources>
diff --git a/sdk-common/src/test/resources/testData/resources/brokenSet7/values/dimens.xml b/sdk-common/src/test/resources/testData/resources/brokenSet7/values/dimens.xml
new file mode 100644
index 0000000..7c43f4b
--- /dev/null
+++ b/sdk-common/src/test/resources/testData/resources/brokenSet7/values/dimens.xml
@@ -0,0 +1,4 @@
+<resources>
+ <dimen name=activity_horizontal_margin">16dp</dimen>
+ <dimen name="activity_vertical_margin">16dp</dimen>
+</resources>
diff --git a/sdklib/build.gradle b/sdklib/build.gradle
index 40bab39..5b78836 100644
--- a/sdklib/build.gradle
+++ b/sdklib/build.gradle
@@ -1,3 +1,8 @@
+apply plugin: 'java'
+apply plugin: 'distrib'
+
+evaluationDependsOn(':dvlib')
+
group = 'com.android.tools'
archivesBaseName = 'sdklib'
@@ -5,7 +10,6 @@
compile project(':layoutlib-api')
compile project(':dvlib')
- compile 'org.bouncycastle:bcpkix-jdk15on:1.48'
compile 'org.apache.commons:commons-compress:1.0'
compile 'org.apache.httpcomponents:httpclient:4.1.1'
compile 'org.apache.httpcomponents:httpmime:4.1'
@@ -46,45 +50,11 @@
buildDistributionJar.dependsOn copyXsd
-uploadArchives {
- repositories {
- mavenDeployer {
- beforeDeployment { MavenDeployment deployment ->
- if (!project.has("release")) {
- throw new StopExecutionException("uploadArchives must be called with the release.gradle init script")
- }
- signing.signPom(deployment)
- }
+project.ext.pomName = 'Android Tools sdklib'
+project.ext.pomDesc = 'A library to parse and download the Android SDK.'
- repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
- authentication(userName: project.ext.sonatypeUsername, password: project.ext.sonatypePassword)
- }
+apply from: '../baseVersion.gradle'
+apply from: '../publish.gradle'
+apply from: '../javadoc.gradle'
- pom.project {
- name 'Android Tools sdklib'
- description 'A library to parse and download the Android SDK.'
- url 'http://tools.android.com'
- inceptionYear '2007'
-
- licenses {
- license {
- name 'The Apache Software License, Version 2.0'
- url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
- distribution 'repo'
- }
- }
-
- scm {
- url "https://android.googlesource.com/platform/tools/base"
- connection "git://android.googlesource.com/platform/toos/base.git"
- }
- developers {
- developer {
- name 'The Android Open Source Project'
- }
- }
- }
- }
- }
-}
diff --git a/sdklib/sdklib.iml b/sdklib/sdklib.iml
index da285ac..77e969f 100644
--- a/sdklib/sdklib.iml
+++ b/sdklib/sdklib.iml
@@ -16,7 +16,6 @@
<orderEntry type="library" exported="" name="http-client" level="project" />
<orderEntry type="library" exported="" name="commons-compress" level="project" />
<orderEntry type="library" scope="TEST" name="JUnit3" level="project" />
- <orderEntry type="library" exported="" name="bouncy-castle" level="project" />
</component>
</module>
diff --git a/sdklib/src/main/java/com/android/sdklib/AddOnTarget.java b/sdklib/src/main/java/com/android/sdklib/AddOnTarget.java
deleted file mode 100644
index c7e7ea9..0000000
--- a/sdklib/src/main/java/com/android/sdklib/AddOnTarget.java
+++ /dev/null
@@ -1,470 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-
-import java.io.File;
-import java.io.FileFilter;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
-/**
- * Represents an add-on target in the SDK.
- * An add-on extends a standard {@link PlatformTarget}.
- */
-final class AddOnTarget implements IAndroidTarget {
-
- private static final class OptionalLibrary implements IOptionalLibrary {
- private final String mJarName;
- private final String mJarPath;
- private final String mName;
- private final String mDescription;
-
- OptionalLibrary(String jarName, String jarPath, String name, String description) {
- mJarName = jarName;
- mJarPath = jarPath;
- mName = name;
- mDescription = description;
- }
-
- @Override
- public String getJarName() {
- return mJarName;
- }
-
- @Override
- public String getJarPath() {
- return mJarPath;
- }
-
- @Override
- public String getName() {
- return mName;
- }
-
- @Override
- public String getDescription() {
- return mDescription;
- }
- }
-
- private final String mLocation;
- private final PlatformTarget mBasePlatform;
- private final String mName;
- private final ISystemImage[] mSystemImages;
- private final String mVendor;
- private final int mRevision;
- private final String mDescription;
- private final boolean mHasRenderingLibrary;
- private final boolean mHasRenderingResources;
-
- private String[] mSkins;
- private String mDefaultSkin;
- private IOptionalLibrary[] mLibraries;
- private int mVendorId = NO_USB_ID;
-
- /**
- * Creates a new add-on
- * @param location the OS path location of the add-on
- * @param name the name of the add-on
- * @param vendor the vendor name of the add-on
- * @param revision the revision of the add-on
- * @param description the add-on description
- * @param systemImages list of supported system images. Can be null or empty.
- * @param libMap A map containing the optional libraries. The map key is the fully-qualified
- * library name. The value is a 2 string array with the .jar filename, and the description.
- * @param hasRenderingLibrary whether the addon has a custom layoutlib.jar
- * @param hasRenderingResources whether the add has custom framework resources.
- * @param basePlatform the platform the add-on is extending.
- */
- AddOnTarget(
- String location,
- String name,
- String vendor,
- int revision,
- String description,
- ISystemImage[] systemImages,
- Map<String, String[]> libMap,
- boolean hasRenderingLibrary,
- boolean hasRenderingResources,
- PlatformTarget basePlatform) {
- if (location.endsWith(File.separator) == false) {
- location = location + File.separator;
- }
-
- mLocation = location;
- mName = name;
- mVendor = vendor;
- mRevision = revision;
- mDescription = description;
- mHasRenderingLibrary = hasRenderingLibrary;
- mHasRenderingResources = hasRenderingResources;
- mBasePlatform = basePlatform;
-
- // If the add-on does not have any system-image of its own, the list here
- // is empty and it's up to the callers to query the parent platform.
- mSystemImages = systemImages == null ? new ISystemImage[0] : systemImages;
- Arrays.sort(mSystemImages);
-
- // handle the optional libraries.
- if (libMap != null) {
- mLibraries = new IOptionalLibrary[libMap.size()];
- int index = 0;
- for (Entry<String, String[]> entry : libMap.entrySet()) {
- String jarFile = entry.getValue()[0];
- String desc = entry.getValue()[1];
- mLibraries[index++] = new OptionalLibrary(jarFile,
- mLocation + SdkConstants.OS_ADDON_LIBS_FOLDER + jarFile,
- entry.getKey(), desc);
- }
- }
- }
-
- @Override
- public String getLocation() {
- return mLocation;
- }
-
- @Override
- public String getName() {
- return mName;
- }
-
- @Override
- public ISystemImage getSystemImage(String abiType) {
- for (ISystemImage sysImg : mSystemImages) {
- if (sysImg.getAbiType().equals(abiType)) {
- return sysImg;
- }
- }
- return null;
- }
-
- @Override
- public ISystemImage[] getSystemImages() {
- return mSystemImages;
- }
-
- @Override
- public String getVendor() {
- return mVendor;
- }
-
- @Override
- public String getFullName() {
- return String.format("%1$s (%2$s)", mName, mVendor);
- }
-
- @Override
- public String getClasspathName() {
- return String.format("%1$s [%2$s]", mName, mBasePlatform.getClasspathName());
- }
-
- @Override
- public String getShortClasspathName() {
- return String.format("%1$s [%2$s]", mName, mBasePlatform.getVersionName());
- }
-
- @Override
- public String getDescription() {
- return mDescription;
- }
-
- @Override
- public AndroidVersion getVersion() {
- // this is always defined by the base platform
- return mBasePlatform.getVersion();
- }
-
- @Override
- public String getVersionName() {
- return mBasePlatform.getVersionName();
- }
-
- @Override
- public int getRevision() {
- return mRevision;
- }
-
- @Override
- public boolean isPlatform() {
- return false;
- }
-
- @Override
- public IAndroidTarget getParent() {
- return mBasePlatform;
- }
-
- @Override
- public String getPath(int pathId) {
- switch (pathId) {
- case SKINS:
- return mLocation + SdkConstants.OS_SKINS_FOLDER;
- case DOCS:
- return mLocation + SdkConstants.FD_DOCS + File.separator
- + SdkConstants.FD_DOCS_REFERENCE;
-
- case LAYOUT_LIB:
- if (mHasRenderingLibrary) {
- return mLocation + SdkConstants.FD_DATA + File.separator
- + SdkConstants.FN_LAYOUTLIB_JAR;
- }
- return mBasePlatform.getPath(pathId);
-
- case RESOURCES:
- if (mHasRenderingResources) {
- return mLocation + SdkConstants.FD_DATA + File.separator
- + SdkConstants.FD_RES;
- }
- return mBasePlatform.getPath(pathId);
-
- case FONTS:
- if (mHasRenderingResources) {
- return mLocation + SdkConstants.FD_DATA + File.separator
- + SdkConstants.FD_FONTS;
- }
- return mBasePlatform.getPath(pathId);
-
- case SAMPLES:
- // only return the add-on samples folder if there is actually a sample (or more)
- File sampleLoc = new File(mLocation, SdkConstants.FD_SAMPLES);
- if (sampleLoc.isDirectory()) {
- File[] files = sampleLoc.listFiles(new FileFilter() {
- @Override
- public boolean accept(File pathname) {
- return pathname.isDirectory();
- }
-
- });
- if (files != null && files.length > 0) {
- return sampleLoc.getAbsolutePath();
- }
- }
- //$FALL-THROUGH$
- default :
- return mBasePlatform.getPath(pathId);
- }
- }
-
- @Override
- public BuildToolInfo getBuildToolInfo() {
- return mBasePlatform.getBuildToolInfo();
- }
-
- @Override @NonNull
- public List<String> getBootClasspath() {
- return Collections.singletonList(getPath(IAndroidTarget.ANDROID_JAR));
- }
-
- @Override
- public boolean hasRenderingLibrary() {
- return mHasRenderingLibrary || mHasRenderingResources;
- }
-
- @Override
- public String[] getSkins() {
- return mSkins;
- }
-
- @Override
- public String getDefaultSkin() {
- return mDefaultSkin;
- }
-
- @Override
- public IOptionalLibrary[] getOptionalLibraries() {
- return mLibraries;
- }
-
- /**
- * Returns the list of libraries of the underlying platform.
- *
- * {@inheritDoc}
- */
- @Override
- public String[] getPlatformLibraries() {
- return mBasePlatform.getPlatformLibraries();
- }
-
- @Override
- public String getProperty(String name) {
- return mBasePlatform.getProperty(name);
- }
-
- @Override
- public Integer getProperty(String name, Integer defaultValue) {
- return mBasePlatform.getProperty(name, defaultValue);
- }
-
- @Override
- public Boolean getProperty(String name, Boolean defaultValue) {
- return mBasePlatform.getProperty(name, defaultValue);
- }
-
- @Override
- public Map<String, String> getProperties() {
- return mBasePlatform.getProperties();
- }
-
- @Override
- public int getUsbVendorId() {
- return mVendorId;
- }
-
- @Override
- public boolean canRunOn(IAndroidTarget target) {
- // basic test
- if (target == this) {
- return true;
- }
-
- /*
- * The method javadoc indicates:
- * Returns whether the given target is compatible with the receiver.
- * <p/>A target is considered compatible if applications developed for the receiver can
- * run on the given target.
- */
-
- // The receiver is an add-on. There are 2 big use cases: The add-on has libraries
- // or the add-on doesn't (in which case we consider it a platform).
- if (mLibraries == null || mLibraries.length == 0) {
- return mBasePlatform.canRunOn(target);
- } else {
- // the only targets that can run the receiver are the same add-on in the same or later
- // versions.
- // first check: vendor/name
- if (mVendor.equals(target.getVendor()) == false ||
- mName.equals(target.getName()) == false) {
- return false;
- }
-
- // now check the version. At this point since we checked the add-on part,
- // we can revert to the basic check on version/codename which are done by the
- // base platform already.
- return mBasePlatform.canRunOn(target);
- }
-
- }
-
- @Override
- public String hashString() {
- return String.format(AndroidTargetHash.ADD_ON_FORMAT, mVendor, mName,
- mBasePlatform.getVersion().getApiString());
- }
-
- @Override
- public int hashCode() {
- return hashString().hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof AddOnTarget) {
- AddOnTarget addon = (AddOnTarget)obj;
-
- return mVendor.equals(addon.mVendor) && mName.equals(addon.mName) &&
- mBasePlatform.getVersion().equals(addon.mBasePlatform.getVersion());
- }
-
- return false;
- }
-
- /*
- * Order by API level (preview/n count as between n and n+1).
- * At the same API level, order as: Platform first, then add-on ordered by vendor and then name
- * (non-Javadoc)
- * @see java.lang.Comparable#compareTo(java.lang.Object)
- */
- @Override
- public int compareTo(IAndroidTarget target) {
- // quick check.
- if (this == target) {
- return 0;
- }
-
- int versionDiff = getVersion().compareTo(target.getVersion());
-
- // only if the version are the same do we care about platform/add-ons.
- if (versionDiff == 0) {
- // platforms go before add-ons.
- if (target.isPlatform()) {
- return +1;
- } else {
- AddOnTarget targetAddOn = (AddOnTarget)target;
-
- // both are add-ons of the same version. Compare per vendor then by name
- int vendorDiff = mVendor.compareTo(targetAddOn.mVendor);
- if (vendorDiff == 0) {
- return mName.compareTo(targetAddOn.mName);
- } else {
- return vendorDiff;
- }
- }
-
- }
-
- return versionDiff;
- }
-
- /**
- * Returns a string representation suitable for debugging.
- * The representation is not intended for display to the user.
- *
- * The representation is also purposely compact. It does not describe _all_ the properties
- * of the target, only a few key ones.
- *
- * @see #getDescription()
- */
- @Override
- public String toString() {
- return String.format("AddonTarget %1$s rev %2$d (based on %3$s)", //$NON-NLS-1$
- getVersion(),
- getRevision(),
- getParent().toString());
- }
-
- // ---- local methods.
-
- void setSkins(String[] skins, String defaultSkin) {
- mDefaultSkin = defaultSkin;
-
- // we mix the add-on and base platform skins
- HashSet<String> skinSet = new HashSet<String>();
- skinSet.addAll(Arrays.asList(skins));
- skinSet.addAll(Arrays.asList(mBasePlatform.getSkins()));
-
- mSkins = skinSet.toArray(new String[skinSet.size()]);
- }
-
- /**
- * Sets the USB vendor id in the add-on.
- */
- void setUsbVendorId(int vendorId) {
- if (vendorId == 0) {
- throw new IllegalArgumentException( "VendorId must be > 0");
- }
-
- mVendorId = vendorId;
- }
-}
diff --git a/sdklib/src/main/java/com/android/sdklib/AndroidTargetHash.java b/sdklib/src/main/java/com/android/sdklib/AndroidTargetHash.java
index de5d276..05d12d3 100755
--- a/sdklib/src/main/java/com/android/sdklib/AndroidTargetHash.java
+++ b/sdklib/src/main/java/com/android/sdklib/AndroidTargetHash.java
@@ -17,6 +17,7 @@
package com.android.sdklib;
import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
@@ -35,7 +36,7 @@
* String to compute hash for add-on targets.
* Format is vendor:name:apiVersion
* */
- static final String ADD_ON_FORMAT = "%s:%s:%s"; //$NON-NLS-1$
+ public static final String ADD_ON_FORMAT = "%s:%s:%s"; //$NON-NLS-1$
/**
* String used to get a hash to the platform target.
@@ -49,10 +50,35 @@
* @param version A non-null platform version.
* @return A non-null hash string uniquely representing this platform target.
*/
+ @NonNull
public static String getPlatformHashString(@NonNull AndroidVersion version) {
return String.format(AndroidTargetHash.PLATFORM_HASH, version.getApiString());
}
+ /**
+ * Returns the {@link com.android.sdklib.AndroidVersion} for the given hash string,
+ * if it represents a platform. If the hash string represents a preview platform,
+ * the returned {@link AndroidVersion} will have an unknown API level (set to 1).
+ *
+ * @param hashString the hash string
+ * @return a platform, or null
+ */
+ @Nullable
+ public static AndroidVersion getPlatformVersion(@NonNull String hashString) {
+ if (hashString.startsWith(PLATFORM_HASH_PREFIX)) {
+ String suffix = hashString.substring(PLATFORM_HASH_PREFIX.length());
+ if (!suffix.isEmpty()) {
+ if (Character.isDigit(suffix.charAt(0))) {
+ int api = Integer.parseInt(suffix);
+ return new AndroidVersion(api, null);
+ } else {
+ return new AndroidVersion(1, suffix);
+ }
+ }
+ }
+
+ return null;
+ }
/**
* Returns the hash string for a given add-on.
@@ -89,4 +115,15 @@
}
}
+ /**
+ * Given a hash string, indicates whether this is a platform hash string.
+ * If not, it's an addon hash string.
+ *
+ * @param hashString The hash string to test.
+ * @return True if this hash string starts by the platform prefix.
+ */
+ public static boolean isPlatform(@NonNull String hashString) {
+ return hashString.startsWith(PLATFORM_HASH_PREFIX);
+ }
+
}
diff --git a/sdklib/src/main/java/com/android/sdklib/AndroidVersion.java b/sdklib/src/main/java/com/android/sdklib/AndroidVersion.java
index c6a7c87..05efe52 100644
--- a/sdklib/src/main/java/com/android/sdklib/AndroidVersion.java
+++ b/sdklib/src/main/java/com/android/sdklib/AndroidVersion.java
@@ -63,7 +63,7 @@
* Creates an {@link AndroidVersion} with the given api level and codename.
* Codename should be null for a release version, otherwise it's a preview codename.
*/
- public AndroidVersion(int apiLevel, String codename) {
+ public AndroidVersion(int apiLevel, @Nullable String codename) {
mApiLevel = apiLevel;
mCodename = sanitizeCodename(codename);
}
@@ -74,7 +74,9 @@
* <p/>The {@link Properties} is expected to have been filled with
* {@link #saveProperties(Properties)}.
*/
- public AndroidVersion(Properties properties, int defaultApiLevel, String defaultCodeName) {
+ public AndroidVersion(@Nullable Properties properties,
+ int defaultApiLevel,
+ @Nullable String defaultCodeName) {
if (properties == null) {
mApiLevel = defaultApiLevel;
mCodename = sanitizeCodename(defaultCodeName);
@@ -93,7 +95,7 @@
*
* @see #saveProperties(Properties)
*/
- public AndroidVersion(Properties properties) throws AndroidVersionException {
+ public AndroidVersion(@NonNull Properties properties) throws AndroidVersionException {
Exception error = null;
String apiLevel = properties.getProperty(PkgProps.VERSION_API_LEVEL, null/*defaultValue*/);
@@ -153,7 +155,7 @@
}
}
- public void saveProperties(Properties props) {
+ public void saveProperties(@NonNull Properties props) {
props.setProperty(PkgProps.VERSION_API_LEVEL, Integer.toString(mApiLevel));
if (mCodename != null) {
props.setProperty(PkgProps.VERSION_CODENAME, mCodename);
@@ -178,6 +180,7 @@
* <p/>If the codename is non null, then the API level should be ignored, and this should be
* used as a unique identifier of the target instead.
*/
+ @Nullable
public String getCodename() {
return mCodename;
}
@@ -185,6 +188,7 @@
/**
* Returns a string representing the API level and/or the code name.
*/
+ @NonNull
public String getApiString() {
if (mCodename != null) {
return mCodename;
@@ -212,7 +216,7 @@
* access to the list of optional libraries), this method can give a good indication of whether
* there is a chance the application could run, or if there's a direct incompatibility.
*/
- public boolean canRun(AndroidVersion appVersion) {
+ public boolean canRun(@NonNull AndroidVersion appVersion) {
// if the application is compiled for a preview version, the device must be running exactly
// the same.
if (appVersion.mCodename != null) {
@@ -311,7 +315,7 @@
return compareTo(o.mApiLevel, o.mCodename);
}
- public int compareTo(int apiLevel, String codename) {
+ public int compareTo(int apiLevel, @Nullable String codename) {
if (mCodename == null) {
if (codename == null) {
return mApiLevel - apiLevel;
diff --git a/sdklib/src/main/java/com/android/sdklib/BuildToolInfo.java b/sdklib/src/main/java/com/android/sdklib/BuildToolInfo.java
index a4c4307..68d3c6a 100755
--- a/sdklib/src/main/java/com/android/sdklib/BuildToolInfo.java
+++ b/sdklib/src/main/java/com/android/sdklib/BuildToolInfo.java
@@ -26,8 +26,6 @@
import java.io.File;
import java.util.Map;
-
-
/**
* Information on a specific build-tool folder.
*/
@@ -35,19 +33,56 @@
public enum PathId {
/** OS Path to the target's version of the aapt tool. */
- AAPT,
+ AAPT("1.0.0"),
/** OS Path to the target's version of the aidl tool. */
- AIDL,
- /** OS Path to the target's version of the dx too. */
- DX,
+ AIDL("1.0.0"),
+ /** OS Path to the target's version of the dx tool. */
+ DX("1.0.0"),
/** OS Path to the target's version of the dx.jar file. */
- DX_JAR,
- ///** OS Path to the llvm-rs-cc binary for Renderscript. */
- LLVM_RS_CC,
- ///** OS Path to the Renderscript include folder. */
- ANDROID_RS,
- ///** OS Path to the Renderscript(clang) include folder. */
- ANDROID_RS_CLANG,
+ DX_JAR("1.0.0"),
+ /** OS Path to the llvm-rs-cc binary for Renderscript. */
+ LLVM_RS_CC("1.0.0"),
+ /** OS Path to the Renderscript include folder. */
+ ANDROID_RS("1.0.0"),
+ /** OS Path to the Renderscript(clang) include folder. */
+ ANDROID_RS_CLANG("1.0.0"),
+
+ // --- NEW IN 18.1.0 ---
+
+ /** OS Path to the bcc_compat tool. */
+ BCC_COMPAT("18.1.0"),
+ /** OS Path to the ARM linker. */
+ LD_ARM("18.1.0"),
+ /** OS Path to the X86 linker. */
+ LD_X86("18.1.0"),
+ /** OS Path to the MIPS linker. */
+ LD_MIPS("18.1.0");
+
+ /**
+ * min revision this element was introduced.
+ * Controls {@link BuildToolInfo#isValid(ILogger)}
+ */
+ private final FullRevision mMinRevision;
+
+ /**
+ * Creates the enum with a min revision in which this
+ * tools appeared in the build tools.
+ *
+ * @param minRevision the min revision.
+ */
+ PathId(@NonNull String minRevision) {
+ mMinRevision = FullRevision.parseRevision(minRevision);
+ }
+
+ /**
+ * Returns whether the enum of present in a given rev of the build tools.
+ *
+ * @param fullRevision the build tools revision.
+ * @return true if the tool is present.
+ */
+ boolean isPresentIn(@NonNull FullRevision fullRevision) {
+ return fullRevision.compareTo(mMinRevision) >= 0;
+ }
}
/** The build-tool revision. */
@@ -68,6 +103,10 @@
add(PathId.LLVM_RS_CC, SdkConstants.FN_RENDERSCRIPT);
add(PathId.ANDROID_RS, SdkConstants.OS_FRAMEWORK_RS);
add(PathId.ANDROID_RS_CLANG, SdkConstants.OS_FRAMEWORK_RS_CLANG);
+ add(PathId.BCC_COMPAT, SdkConstants.FN_BCC_COMPAT);
+ add(PathId.LD_ARM, SdkConstants.FN_LD_ARM);
+ add(PathId.LD_X86, SdkConstants.FN_LD_X86);
+ add(PathId.LD_MIPS, SdkConstants.FN_LD_MIPS);
}
public BuildToolInfo(FullRevision revision, @NonNull File mainPath,
@@ -77,7 +116,11 @@
@NonNull File dxJar,
@NonNull File llmvRsCc,
@NonNull File androidRs,
- @NonNull File androidRsClang) {
+ @NonNull File androidRsClang,
+ @Nullable File bccCompat,
+ @Nullable File ldArm,
+ @Nullable File ldX86,
+ @Nullable File ldMips) {
mRevision = revision;
mPath = mainPath;
add(PathId.AAPT, aapt);
@@ -87,6 +130,29 @@
add(PathId.LLVM_RS_CC, llmvRsCc);
add(PathId.ANDROID_RS, androidRs);
add(PathId.ANDROID_RS_CLANG, androidRsClang);
+
+ if (bccCompat != null) {
+ add(PathId.BCC_COMPAT, bccCompat);
+ } else if (PathId.BCC_COMPAT.isPresentIn(revision)) {
+ throw new IllegalArgumentException("BCC_COMPAT required in " + revision.toString());
+ }
+ if (ldArm != null) {
+ add(PathId.LD_ARM, ldArm);
+ } else if (PathId.LD_ARM.isPresentIn(revision)) {
+ throw new IllegalArgumentException("LD_ARM required in " + revision.toString());
+ }
+
+ if (ldX86 != null) {
+ add(PathId.LD_X86, ldX86);
+ } else if (PathId.LD_X86.isPresentIn(revision)) {
+ throw new IllegalArgumentException("LD_X86 required in " + revision.toString());
+ }
+
+ if (ldMips != null) {
+ add(PathId.LD_MIPS, ldMips);
+ } else if (PathId.LD_MIPS.isPresentIn(revision)) {
+ throw new IllegalArgumentException("LD_MIPS required in " + revision.toString());
+ }
}
private void add(PathId id, String leaf) {
@@ -128,6 +194,8 @@
* Null if the path-id is unknown.
*/
public String getPath(PathId pathId) {
+ assert pathId.isPresentIn(mRevision);
+
return mPaths.get(pathId);
}
@@ -142,7 +210,9 @@
public boolean isValid(@Nullable ILogger log) {
for (Map.Entry<PathId, String> entry : mPaths.entrySet()) {
File f = new File(entry.getValue());
- if (!f.exists()) {
+ // check if file is missing. It's only ok if the revision of the build-tools
+ // is lower than the min rev of the element.
+ if (!f.exists() && entry.getKey().isPresentIn(mRevision)) {
if (log != null) {
log.warning("Build-tool %1$s is missing %2$s at %3$s", //$NON-NLS-1$
mRevision.toString(),
@@ -163,8 +233,25 @@
StringBuilder builder = new StringBuilder();
builder.append("<BuildToolInfo rev=").append(mRevision); //$NON-NLS-1$
builder.append(", mPath=").append(mPath); //$NON-NLS-1$
- builder.append(", mPaths=").append(mPaths); //$NON-NLS-1$
+ builder.append(", mPaths=").append(getPathString()); //$NON-NLS-1$
builder.append(">"); //$NON-NLS-1$
return builder.toString();
}
+
+ private String getPathString() {
+ StringBuilder sb = new StringBuilder("{");
+
+ for (Map.Entry<PathId, String> entry : mPaths.entrySet()) {
+ if (entry.getKey().isPresentIn(mRevision)) {
+ if (sb.length() > 1) {
+ sb.append(", ");
+ }
+ sb.append(entry.getKey()).append('=').append(entry.getValue());
+ }
+ }
+
+ sb.append('}');
+
+ return sb.toString();
+ }
}
diff --git a/sdklib/src/main/java/com/android/sdklib/PlatformTarget.java b/sdklib/src/main/java/com/android/sdklib/PlatformTarget.java
deleted file mode 100644
index e5c3537..0000000
--- a/sdklib/src/main/java/com/android/sdklib/PlatformTarget.java
+++ /dev/null
@@ -1,426 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.sdklib.SdkManager.LayoutlibVersion;
-import com.android.utils.SparseArray;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Represents a platform target in the SDK.
- */
-final class PlatformTarget implements IAndroidTarget {
-
- private static final String PLATFORM_VENDOR = "Android Open Source Project";
-
- private static final String PLATFORM_NAME = "Android %s";
- private static final String PLATFORM_NAME_PREVIEW = "Android %s (Preview)";
-
- /** the OS path to the root folder of the platform component. */
- private final String mRootFolderOsPath;
- private final String mName;
- private final AndroidVersion mVersion;
- private final String mVersionName;
- private final int mRevision;
- private final Map<String, String> mProperties;
- private final SparseArray<String> mPaths = new SparseArray<String>();
- private String[] mSkins;
- private final ISystemImage[] mSystemImages;
- private final LayoutlibVersion mLayoutlibVersion;
- private final BuildToolInfo mBuildToolInfo;
-
- /**
- * Creates a Platform target.
- *
- * @param sdkOsPath the root folder of the SDK
- * @param platformOSPath the root folder of the platform component
- * @param apiVersion the API Level + codename.
- * @param versionName the version name of the platform.
- * @param revision the revision of the platform component.
- * @param layoutlibVersion The {@link LayoutlibVersion}. May be null.
- * @param systemImages list of supported system images
- * @param properties the platform properties
- */
- @SuppressWarnings("deprecation")
- PlatformTarget(
- String sdkOsPath,
- String platformOSPath,
- AndroidVersion apiVersion,
- String versionName,
- int revision,
- LayoutlibVersion layoutlibVersion,
- ISystemImage[] systemImages,
- Map<String, String> properties,
- @NonNull BuildToolInfo buildToolInfo) {
- if (!platformOSPath.endsWith(File.separator)) {
- platformOSPath = platformOSPath + File.separator;
- }
- mRootFolderOsPath = platformOSPath;
- mProperties = Collections.unmodifiableMap(properties);
- mVersion = apiVersion;
- mVersionName = versionName;
- mRevision = revision;
- mLayoutlibVersion = layoutlibVersion;
- mBuildToolInfo = buildToolInfo;
- mSystemImages = systemImages == null ? new ISystemImage[0] : systemImages;
- Arrays.sort(mSystemImages);
-
- if (mVersion.isPreview()) {
- mName = String.format(PLATFORM_NAME_PREVIEW, mVersionName);
- } else {
- mName = String.format(PLATFORM_NAME, mVersionName);
- }
-
- // pre-build the path to the platform components
- mPaths.put(ANDROID_JAR, mRootFolderOsPath + SdkConstants.FN_FRAMEWORK_LIBRARY);
- mPaths.put(UI_AUTOMATOR_JAR, mRootFolderOsPath + SdkConstants.FN_UI_AUTOMATOR_LIBRARY);
- mPaths.put(SOURCES, mRootFolderOsPath + SdkConstants.FD_ANDROID_SOURCES);
- mPaths.put(ANDROID_AIDL, mRootFolderOsPath + SdkConstants.FN_FRAMEWORK_AIDL);
- mPaths.put(SAMPLES, mRootFolderOsPath + SdkConstants.OS_PLATFORM_SAMPLES_FOLDER);
- mPaths.put(SKINS, mRootFolderOsPath + SdkConstants.OS_SKINS_FOLDER);
- mPaths.put(TEMPLATES, mRootFolderOsPath + SdkConstants.OS_PLATFORM_TEMPLATES_FOLDER);
- mPaths.put(DATA, mRootFolderOsPath + SdkConstants.OS_PLATFORM_DATA_FOLDER);
- mPaths.put(ATTRIBUTES, mRootFolderOsPath + SdkConstants.OS_PLATFORM_ATTRS_XML);
- mPaths.put(MANIFEST_ATTRIBUTES,
- mRootFolderOsPath + SdkConstants.OS_PLATFORM_ATTRS_MANIFEST_XML);
- mPaths.put(RESOURCES, mRootFolderOsPath + SdkConstants.OS_PLATFORM_RESOURCES_FOLDER);
- mPaths.put(FONTS, mRootFolderOsPath + SdkConstants.OS_PLATFORM_FONTS_FOLDER);
- mPaths.put(LAYOUT_LIB, mRootFolderOsPath + SdkConstants.OS_PLATFORM_DATA_FOLDER +
- SdkConstants.FN_LAYOUTLIB_JAR);
- mPaths.put(WIDGETS, mRootFolderOsPath + SdkConstants.OS_PLATFORM_DATA_FOLDER +
- SdkConstants.FN_WIDGETS);
- mPaths.put(ACTIONS_ACTIVITY, mRootFolderOsPath + SdkConstants.OS_PLATFORM_DATA_FOLDER +
- SdkConstants.FN_INTENT_ACTIONS_ACTIVITY);
- mPaths.put(ACTIONS_BROADCAST, mRootFolderOsPath + SdkConstants.OS_PLATFORM_DATA_FOLDER +
- SdkConstants.FN_INTENT_ACTIONS_BROADCAST);
- mPaths.put(ACTIONS_SERVICE, mRootFolderOsPath + SdkConstants.OS_PLATFORM_DATA_FOLDER +
- SdkConstants.FN_INTENT_ACTIONS_SERVICE);
- mPaths.put(CATEGORIES, mRootFolderOsPath + SdkConstants.OS_PLATFORM_DATA_FOLDER +
- SdkConstants.FN_INTENT_CATEGORIES);
- mPaths.put(ANT, mRootFolderOsPath + SdkConstants.OS_PLATFORM_ANT_FOLDER);
- }
-
- /**
- * Returns the {@link LayoutlibVersion}. May be null.
- */
- public LayoutlibVersion getLayoutlibVersion() {
- return mLayoutlibVersion;
- }
-
- @Override
- public ISystemImage getSystemImage(String abiType) {
- for (ISystemImage sysImg : mSystemImages) {
- if (sysImg.getAbiType().equals(abiType)) {
- return sysImg;
- }
- }
- return null;
- }
-
- @Override
- public ISystemImage[] getSystemImages() {
- return mSystemImages;
- }
-
- @Override
- public String getLocation() {
- return mRootFolderOsPath;
- }
-
- /**
- * {@inheritDoc}
- * <p/>
- * For Platform, the vendor name is always "Android".
- *
- * @see com.android.sdklib.IAndroidTarget#getVendor()
- */
- @Override
- public String getVendor() {
- return PLATFORM_VENDOR;
- }
-
- @Override
- public String getName() {
- return mName;
- }
-
- @Override
- public String getFullName() {
- return mName;
- }
-
- @Override
- public String getClasspathName() {
- return mName;
- }
-
- @Override
- public String getShortClasspathName() {
- return mName;
- }
-
- /*
- * (non-Javadoc)
- *
- * Description for the Android platform is dynamically generated.
- *
- * @see com.android.sdklib.IAndroidTarget#getDescription()
- */
- @Override
- public String getDescription() {
- return String.format("Standard Android platform %s", mVersionName);
- }
-
- @Override
- public AndroidVersion getVersion() {
- return mVersion;
- }
-
- @Override
- public String getVersionName() {
- return mVersionName;
- }
-
- @Override
- public int getRevision() {
- return mRevision;
- }
-
- @Override
- public boolean isPlatform() {
- return true;
- }
-
- @Override
- public IAndroidTarget getParent() {
- return null;
- }
-
- @Override
- public String getPath(int pathId) {
- return mPaths.get(pathId);
- }
-
- @Override
- public BuildToolInfo getBuildToolInfo() {
- return mBuildToolInfo;
- }
-
- @Override @NonNull
- public List<String> getBootClasspath() {
- return Collections.singletonList(getPath(IAndroidTarget.ANDROID_JAR));
- }
-
- /**
- * Returns whether the target is able to render layouts. This is always true for platforms.
- */
- @Override
- public boolean hasRenderingLibrary() {
- return true;
- }
-
-
- @Override
- public String[] getSkins() {
- return mSkins;
- }
-
- @Override
- public String getDefaultSkin() {
- // only one skin? easy.
- if (mSkins.length == 1) {
- return mSkins[0];
- }
-
- // look for the skin name in the platform props
- String skinName = mProperties.get(SdkConstants.PROP_SDK_DEFAULT_SKIN);
- if (skinName != null) {
- return skinName;
- }
-
- // otherwise try to find a good default.
- if (mVersion.getApiLevel() >= 4) {
- // at this time, this is the default skin for all older platforms that had 2+ skins.
- return "WVGA800";
- }
-
- return "HVGA"; // this is for 1.5 and earlier.
- }
-
- /**
- * Always returns null, as a standard platform ha no optional libraries.
- *
- * {@inheritDoc}
- * @see com.android.sdklib.IAndroidTarget#getOptionalLibraries()
- */
- @Override
- public IOptionalLibrary[] getOptionalLibraries() {
- return null;
- }
-
- /**
- * Currently always return a fixed list with "android.test.runner" in it.
- * <p/>
- * TODO change the fixed library list to be build-dependent later.
- * {@inheritDoc}
- */
- @Override
- public String[] getPlatformLibraries() {
- return new String[] { SdkConstants.ANDROID_TEST_RUNNER_LIB };
- }
-
- /**
- * The platform has no USB Vendor Id: always return {@link IAndroidTarget#NO_USB_ID}.
- * {@inheritDoc}
- */
- @Override
- public int getUsbVendorId() {
- return NO_USB_ID;
- }
-
- @Override
- public boolean canRunOn(IAndroidTarget target) {
- // basic test
- if (target == this) {
- return true;
- }
-
- // if the platform has a codename (ie it's a preview of an upcoming platform), then
- // both platforms must be exactly identical.
- if (mVersion.getCodename() != null) {
- return mVersion.equals(target.getVersion());
- }
-
- // target is compatible wit the receiver as long as its api version number is greater or
- // equal.
- return target.getVersion().getApiLevel() >= mVersion.getApiLevel();
- }
-
- @Override
- public String hashString() {
- return AndroidTargetHash.getPlatformHashString(mVersion);
- }
-
- @Override
- public int hashCode() {
- return hashString().hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof PlatformTarget) {
- PlatformTarget platform = (PlatformTarget)obj;
-
- return mVersion.equals(platform.getVersion());
- }
-
- return false;
- }
-
- /*
- * Order by API level (preview/n count as between n and n+1).
- * At the same API level, order as: Platform first, then add-on ordered by vendor and then name
- * (non-Javadoc)
- * @see java.lang.Comparable#compareTo(java.lang.Object)
- */
- @Override
- public int compareTo(IAndroidTarget target) {
- // quick check.
- if (this == target) {
- return 0;
- }
-
- int versionDiff = mVersion.compareTo(target.getVersion());
-
- // only if the version are the same do we care about add-ons.
- if (versionDiff == 0) {
- // platforms go before add-ons.
- if (target.isPlatform() == false) {
- return -1;
- }
- }
-
- return versionDiff;
- }
-
- /**
- * Returns a string representation suitable for debugging.
- * The representation is not intended for display to the user.
- *
- * The representation is also purposely compact. It does not describe _all_ the properties
- * of the target, only a few key ones.
- *
- * @see #getDescription()
- */
- @Override
- public String toString() {
- return String.format("PlatformTarget %1$s rev %2$d", //$NON-NLS-1$
- getVersion(),
- getRevision());
- }
-
- @Override
- public String getProperty(String name) {
- return mProperties.get(name);
- }
-
- @Override
- public Integer getProperty(String name, Integer defaultValue) {
- try {
- String value = getProperty(name);
- if (value != null) {
- return Integer.decode(value);
- }
- } catch (NumberFormatException e) {
- // ignore, return default value;
- }
-
- return defaultValue;
- }
-
- @Override
- public Boolean getProperty(String name, Boolean defaultValue) {
- String value = getProperty(name);
- if (value != null) {
- return Boolean.valueOf(value);
- }
-
- return defaultValue;
- }
-
- @Override
- public Map<String, String> getProperties() {
- return mProperties; // mProperties is unmodifiable.
- }
-
- // ---- platform only methods.
-
- void setSkins(String[] skins) {
- mSkins = skins;
- }
-
- void setSamplesPath(String osLocation) {
- mPaths.put(SAMPLES, osLocation);
- }
-}
diff --git a/sdklib/src/main/java/com/android/sdklib/SdkManager.java b/sdklib/src/main/java/com/android/sdklib/SdkManager.java
index 928b1dc..018098f 100644
--- a/sdklib/src/main/java/com/android/sdklib/SdkManager.java
+++ b/sdklib/src/main/java/com/android/sdklib/SdkManager.java
@@ -21,44 +21,29 @@
import com.android.annotations.Nullable;
import com.android.annotations.VisibleForTesting;
import com.android.annotations.VisibleForTesting.Visibility;
-import com.android.io.FileWrapper;
import com.android.prefs.AndroidLocation;
import com.android.prefs.AndroidLocation.AndroidLocationException;
-import com.android.sdklib.AndroidVersion.AndroidVersionException;
-import com.android.sdklib.ISystemImage.LocationType;
-import com.android.sdklib.internal.project.ProjectProperties;
-import com.android.sdklib.internal.repository.LocalSdkParser;
-import com.android.sdklib.internal.repository.NullTaskMonitor;
-import com.android.sdklib.internal.repository.archives.Archive;
-import com.android.sdklib.internal.repository.packages.ExtraPackage;
-import com.android.sdklib.internal.repository.packages.Package;
-import com.android.sdklib.internal.repository.packages.PlatformToolPackage;
-import com.android.sdklib.io.FileOp;
+import com.android.sdklib.internal.androidTarget.AddOnTarget;
+import com.android.sdklib.internal.androidTarget.PlatformTarget;
+import com.android.sdklib.local.LocalExtraPkgInfo;
+import com.android.sdklib.local.LocalPkgInfo;
+import com.android.sdklib.local.LocalPlatformPkgInfo;
+import com.android.sdklib.local.LocalSdk;
import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.PkgProps;
import com.android.utils.ILogger;
-import com.android.utils.NullLogger;
-import com.android.utils.Pair;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
-import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.zip.Adler32;
/**
* The SDK manager parses the SDK folder and gives access to the content.
@@ -69,35 +54,6 @@
private static final boolean DEBUG = System.getenv("SDKMAN_DEBUG") != null; //$NON-NLS-1$
- public static final String PROP_VERSION_SDK = "ro.build.version.sdk"; //$NON-NLS-1$
- public static final String PROP_VERSION_CODENAME = "ro.build.version.codename"; //$NON-NLS-1$
- public static final String PROP_VERSION_RELEASE = "ro.build.version.release"; //$NON-NLS-1$
-
- public static final String ADDON_NAME = "name"; //$NON-NLS-1$
- public static final String ADDON_VENDOR = "vendor"; //$NON-NLS-1$
- public static final String ADDON_API = "api"; //$NON-NLS-1$
- public static final String ADDON_DESCRIPTION = "description"; //$NON-NLS-1$
- public static final String ADDON_LIBRARIES = "libraries"; //$NON-NLS-1$
- public static final String ADDON_DEFAULT_SKIN = "skin"; //$NON-NLS-1$
- public static final String ADDON_USB_VENDOR = "usb-vendor"; //$NON-NLS-1$
- public static final String ADDON_REVISION = "revision"; //$NON-NLS-1$
- public static final String ADDON_REVISION_OLD = "version"; //$NON-NLS-1$
-
-
- private static final Pattern PATTERN_LIB_DATA = Pattern.compile(
- "^([a-zA-Z0-9._-]+\\.jar);(.*)$", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
-
- // usb ids are 16-bit hexadecimal values.
- private static final Pattern PATTERN_USB_IDS = Pattern.compile(
- "^0x[a-f0-9]{4}$", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
-
- /** List of items in the platform to check when parsing it. These paths are relative to the
- * platform root folder. */
- private static final String[] sPlatformContentList = new String[] {
- SdkConstants.FN_FRAMEWORK_LIBRARY,
- SdkConstants.FN_FRAMEWORK_AIDL,
- };
-
/** Preference file containing the usb ids for adb */
private static final String ADB_INI_FILE = "adb_usb.ini"; //$NON-NLS-1$
//0--------90--------90--------90--------90--------90--------90--------90--------9
@@ -106,15 +62,11 @@
"# USE 'android update adb' TO GENERATE.\n" + //$NON-NLS-1$
"# 1 USB VENDOR ID PER LINE.\n"; //$NON-NLS-1$
- /** The location of the SDK as an OS path */
- private final String mOsSdkPath;
- /** Valid targets that have been loaded. Can be empty but not null. */
- private IAndroidTarget[] mTargets = new IAndroidTarget[0];
- /** Valid build-tool folders that have been loaded. Can be empty but not null. */
- private Map<FullRevision, BuildToolInfo> mBuildTools = Maps.newTreeMap();
- /** A map to keep information on directories to see if they change later. */
- private final Map<File, DirInfo> mVisistedDirs = new HashMap<File, SdkManager.DirInfo>();
+ /** Embedded reference to the new local SDK object. */
+ private final LocalSdk mLocalSdk;
+ /** Cache of targets from local sdk. See {@link #getTargets()}. */
+ private IAndroidTarget[] mCachedTargets;
/**
* Create a new {@link SdkManager} instance.
@@ -124,7 +76,7 @@
*/
@VisibleForTesting(visibility=Visibility.PRIVATE)
protected SdkManager(@NonNull String osSdkPath) {
- mOsSdkPath = osSdkPath;
+ mLocalSdk = new LocalSdk(new File(osSdkPath));
}
/**
@@ -149,70 +101,19 @@
return null;
}
+ @NonNull
+ public LocalSdk getLocalSdk() {
+ return mLocalSdk;
+ }
+
/**
* Reloads the content of the SDK.
*
* @param log the ILogger object receiving warning/error from the parsing.
*/
public void reloadSdk(@NonNull ILogger log) {
- // get the current target list.
- mVisistedDirs.clear();
-
- // load the buildtools
- Map<FullRevision, BuildToolInfo> buildTools = Maps.newHashMap();
- loadBuildTools(mOsSdkPath, buildTools, mVisistedDirs, log);
- setBuildTools(buildTools);
-
- BuildToolInfo latestBuildTools = getLatestBuildTool();
- if (latestBuildTools == null) {
- // no build tools? Check version of platform-tools. if <17 and lower this means
- // an older SDK (while this code is used in an external tool) and so we go in
- // compatibility mode with a BuildToolInfo mapping the path to the platform-tools.
- // If platform-tools is newer, then we fail with a broken SDK.
- String platformToolsVersion = getPlatformToolsVersion();
- if (platformToolsVersion == null) {
- log.error(null, "Missing platform-tools");
- } else {
- FullRevision fullRevision = FullRevision.parseRevision(platformToolsVersion);
- if (fullRevision.compareTo(new FullRevision(17)) < 0) {
- // older SDK, create a compatible buildtools
- latestBuildTools = getCompatibilityBuildTools(fullRevision);
- }
- }
- }
-
- ArrayList<IAndroidTarget> targets = Lists.newArrayList();
- loadPlatforms(mOsSdkPath, targets, mVisistedDirs, latestBuildTools, log);
- loadAddOns(mOsSdkPath, targets, mVisistedDirs, log);
-
- // For now replace the old list with the new one.
- // In the future we may want to keep the current objects, so that ADT doesn't have to deal
- // with new IAndroidTarget objects when a target didn't actually change.
-
- // sort the targets/add-ons
- Collections.sort(targets);
- setTargets(targets.toArray(new IAndroidTarget[targets.size()]));
-
- // load the samples, after the targets have been set.
- initializeSamplePaths(log);
- }
-
- private BuildToolInfo getCompatibilityBuildTools(FullRevision fullRevision) {
- File platformTools = new File(mOsSdkPath, SdkConstants.FD_PLATFORM_TOOLS);
- File platformToolsLib = new File(platformTools, SdkConstants.FD_LIB);
- File platformToolsRs = new File(platformTools, SdkConstants.FN_FRAMEWORK_RENDERSCRIPT);
-
- return new BuildToolInfo(
- fullRevision,
- platformTools,
- new File(platformTools, SdkConstants.FN_AAPT),
- new File(platformTools, SdkConstants.FN_AIDL),
- new File(platformTools, SdkConstants.FN_DX),
- new File(platformToolsLib, SdkConstants.FN_DX_JAR),
- new File(platformTools, SdkConstants.FN_RENDERSCRIPT),
- new File(platformToolsRs, SdkConstants.FN_FRAMEWORK_INCLUDE),
- new File(platformToolsRs, SdkConstants.FN_FRAMEWORK_INCLUDE_CLANG)
- );
+ mCachedTargets = null;
+ mLocalSdk.clearLocalPkg(LocalSdk.PKG_ALL);
}
/**
@@ -235,70 +136,9 @@
* @return True if at least one directory or source.prop has changed.
*/
public boolean hasChanged(@Nullable ILogger log) {
- Set<File> visited = new HashSet<File>();
- boolean changed = false;
-
- for (String dirName : new String[] { SdkConstants.FD_PLATFORMS,
- SdkConstants.FD_ADDONS,
- SdkConstants.FD_BUILD_TOOLS }) {
-
- File folder = new File(mOsSdkPath, dirName);
- if (folder.isDirectory()) {
- File[] subFolders = folder.listFiles();
- if (subFolders == null) {
- continue;
- }
- for (File subFolder : subFolders) {
- if (!subFolder.isDirectory()) {
- continue;
- }
- visited.add(subFolder);
- DirInfo dirInfo = mVisistedDirs.get(subFolder);
- if (dirInfo == null) {
- // This is a new platform directory.
- changed = true;
- } else {
- changed = changed || dirInfo.hasChanged();
- }
- if (changed) {
- String s = "SDK changed due to " + //$NON-NLS-1$
- (dirInfo != null ? dirInfo.toString() : subFolder.getPath());
-
- if (log != null) {
- log.verbose("%s", s); //$NON-NLS-1$
- }
- if (DEBUG) {
- System.out.println(s);
- }
- break;
- }
- }
- }
- }
-
-
- if (!changed) {
- // Check whether some pre-existing target directories have vanished.
- for (File previousDir : mVisistedDirs.keySet()) {
- if (!visited.contains(previousDir)) {
- // This directory is no longer present.
- changed = true;
-
- String s = String.format("SDK changed: %s removed", //$NON-NLS-1$
- previousDir.getPath());
-
- if (log != null) {
- log.verbose("%s", s); //$NON-NLS-1$
- }
- if (DEBUG) {
- System.out.println(s);
- }
- break;
- }
- }
- }
-
- return changed;
+ return mLocalSdk.hasChanged(LocalSdk.PKG_PLATFORMS |
+ LocalSdk.PKG_ADDONS |
+ LocalSdk.PKG_BUILD_TOOLS);
}
/**
@@ -306,39 +146,56 @@
*/
@NonNull
public String getLocation() {
- return mOsSdkPath;
+ File f = mLocalSdk.getLocation();
+ // Our LocalSdk is created with a file path, so we know the location won't be null.
+ assert f != null;
+ return f.getPath();
}
/**
- * Returns the targets that are available in the SDK.
+ * Returns the targets (platforms & addons) that are available in the SDK.
+ * The target list is created on demand the first time then cached.
+ * It will not refreshed unless {@link #reloadSdk(ILogger)} is called.
* <p/>
* The array can be empty but not null.
*/
@NonNull
public IAndroidTarget[] getTargets() {
- return mTargets;
+ if (mCachedTargets == null) {
+ LocalPkgInfo[] pkgsInfos = mLocalSdk.getPkgsInfos(LocalSdk.PKG_PLATFORMS |
+ LocalSdk.PKG_ADDONS);
+ int n = pkgsInfos.length;
+ List<IAndroidTarget> targets = new ArrayList<IAndroidTarget>(n);
+ for (int i = 0; i < n; i++) {
+ LocalPkgInfo info = pkgsInfos[i];
+ assert info instanceof LocalPlatformPkgInfo;
+ if (info instanceof LocalPlatformPkgInfo) {
+ IAndroidTarget target = ((LocalPlatformPkgInfo) info).getAndroidTarget();
+ if (target != null) {
+ targets.add(target);
+ }
+ }
+ }
+ mCachedTargets = targets.toArray(new IAndroidTarget[targets.size()]);
+ }
+ return mCachedTargets;
}
/**
- * Sets the targets that are available in the SDK.
- * <p/>
- * The array can be empty but not null.
+ * Returns an unmodifiable set of known build-tools revisions. Can be empty but not null.
+ * Deprecated. I don't think anything uses this.
*/
- @VisibleForTesting(visibility=Visibility.PRIVATE)
- protected void setTargets(@NonNull IAndroidTarget[] targets) {
- assert targets != null;
- mTargets = targets;
- }
-
- private void setBuildTools(@NonNull Map<FullRevision, BuildToolInfo> buildTools) {
- assert buildTools != null;
- mBuildTools = buildTools;
- }
-
- /** Returns an unmodifiable set of known build-tools revisions. Can be empty but not null. */
+ @Deprecated
@NonNull
public Set<FullRevision> getBuildTools() {
- return Collections.unmodifiableSet(mBuildTools.keySet());
+ LocalPkgInfo[] pkgs = mLocalSdk.getPkgsInfos(LocalSdk.PKG_BUILD_TOOLS);
+ TreeSet<FullRevision> bt = new TreeSet<FullRevision>();
+ for (LocalPkgInfo pkg : pkgs) {
+ if (pkg.hasFullRevision()) {
+ bt.add(pkg.getFullRevision());
+ }
+ }
+ return Collections.unmodifiableSet(bt);
}
/**
@@ -348,17 +205,7 @@
*/
@Nullable
public BuildToolInfo getLatestBuildTool() {
- if (mBuildTools.isEmpty()) {
- return null;
- }
-
- FullRevision max = null;
- for (FullRevision r : mBuildTools.keySet()) {
- if (max == null || r.compareTo(max) > 0) {
- max = r;
- }
- }
- return mBuildTools.get(max);
+ return mLocalSdk.getLatestBuildTool();
}
/**
@@ -370,7 +217,7 @@
*/
@Nullable
public BuildToolInfo getBuildTool(@Nullable FullRevision revision) {
- return mBuildTools.get(revision);
+ return mLocalSdk.getBuildTool(revision);
}
/**
@@ -381,15 +228,7 @@
*/
@Nullable
public IAndroidTarget getTargetFromHashString(@Nullable String hash) {
- if (hash != null) {
- for (IAndroidTarget target : mTargets) {
- if (hash.equals(target.hashString())) {
- return target;
- }
- }
- }
-
- return null;
+ return mLocalSdk.getTargetFromHashString(hash);
}
/**
@@ -468,32 +307,25 @@
*/
@NonNull
public Map<File, String> getExtraSamples() {
- LocalSdkParser parser = new LocalSdkParser();
- Package[] packages = parser.parseSdk(mOsSdkPath,
- this,
- LocalSdkParser.PARSE_EXTRAS,
- new NullTaskMonitor(NullLogger.getLogger()));
+ LocalPkgInfo[] pkgsInfos = mLocalSdk.getPkgsInfos(LocalSdk.PKG_EXTRAS);
Map<File, String> samples = new HashMap<File, String>();
- for (Package pkg : packages) {
- if (pkg instanceof ExtraPackage && pkg.isLocal()) {
- // isLocal()==true implies there's a single locally-installed archive.
- assert pkg.getArchives() != null && pkg.getArchives().length == 1;
- Archive a = pkg.getArchives()[0];
- assert a != null;
- File path = new File(a.getLocalOsPath(), SdkConstants.FD_SAMPLES);
- if (path.isDirectory()) {
- samples.put(path, pkg.getListDescription());
- continue;
- }
- // Some old-style extras simply have a single "sample" directory.
- // Accept it if it contains an AndroidManifest.xml.
- path = new File(a.getLocalOsPath(), SdkConstants.FD_SAMPLE);
- if (path.isDirectory() &&
- new File(path, SdkConstants.FN_ANDROID_MANIFEST_XML).isFile()) {
- samples.put(path, pkg.getListDescription());
- }
+ for (LocalPkgInfo info : pkgsInfos) {
+ assert info instanceof LocalExtraPkgInfo;
+
+ File root = info.getLocalDir();
+ File path = new File(root, SdkConstants.FD_SAMPLES);
+ if (path.isDirectory()) {
+ samples.put(path, info.getListDescription());
+ continue;
+ }
+ // Some old-style extras simply have a single "sample" directory.
+ // Accept it if it contains an AndroidManifest.xml.
+ path = new File(root, SdkConstants.FD_SAMPLE);
+ if (path.isDirectory() &&
+ new File(path, SdkConstants.FN_ANDROID_MANIFEST_XML).isFile()) {
+ samples.put(path, info.getListDescription());
}
}
@@ -507,23 +339,23 @@
* The version is the incremental integer major revision of the package.
*
* @return A non-null possibly empty map of { string "vendor/path" => integer major revision }
+ * @deprecated Starting with add-on schema 6, extras can have full revisions instead of just
+ * major revisions. This API only returns the major revision. Callers should be modified
+ * to use the new {code LocalSdk.getPkgInfo(LocalSdk.PKG_EXTRAS)} API instead.
*/
+ @Deprecated
@NonNull
public Map<String, Integer> getExtrasVersions() {
- LocalSdkParser parser = new LocalSdkParser();
- Package[] packages = parser.parseSdk(mOsSdkPath,
- this,
- LocalSdkParser.PARSE_EXTRAS,
- new NullTaskMonitor(NullLogger.getLogger()));
-
+ LocalPkgInfo[] pkgsInfos = mLocalSdk.getPkgsInfos(LocalSdk.PKG_EXTRAS);
Map<String, Integer> extraVersions = new TreeMap<String, Integer>();
- for (Package pkg : packages) {
- if (pkg instanceof ExtraPackage && pkg.isLocal()) {
- ExtraPackage ep = (ExtraPackage) pkg;
- String vendor = ep.getVendorId();
- String path = ep.getPath();
- int majorRev = ep.getRevision().getMajor();
+ for (LocalPkgInfo info : pkgsInfos) {
+ assert info instanceof LocalExtraPkgInfo;
+ if (info instanceof LocalExtraPkgInfo) {
+ LocalExtraPkgInfo ei = (LocalExtraPkgInfo) info;
+ String vendor = ei.getVendorId();
+ String path = ei.getExtraPath();
+ int majorRev = ei.getFullRevision().getMajor();
extraVersions.put(vendor + '/' + path, majorRev);
}
@@ -535,915 +367,15 @@
/** Returns the platform tools version if installed, null otherwise. */
@Nullable
public String getPlatformToolsVersion() {
- LocalSdkParser parser = new LocalSdkParser();
- Package[] packages = parser.parseSdk(mOsSdkPath, this, LocalSdkParser.PARSE_PLATFORM_TOOLS,
- new NullTaskMonitor(NullLogger.getLogger()));
-
- for (Package pkg : packages) {
- if (pkg instanceof PlatformToolPackage && pkg.isLocal()) {
- return pkg.getRevision().toShortString();
- }
+ LocalPkgInfo info = mLocalSdk.getPkgInfo(LocalSdk.PKG_PLATFORM_TOOLS);
+ if (info != null && info.hasFullRevision()) {
+ return info.getFullRevision().toShortString();
}
return null;
}
- // -------- private methods ----------
-
- /**
- * Loads the Platforms from the SDK.
- * Creates the "platforms" folder if necessary.
- *
- * @param sdkOsPath Location of the SDK
- * @param targets the list to fill with the platforms.
- * @param dirInfos a map to keep information on directories to see if they change later.
- * @param latestBuildTools the latestBuildTools
- * @param log the ILogger object receiving warning/error from the parsing.
- * @throws RuntimeException when the "platforms" folder is missing and cannot be created.
- */
- private static void loadPlatforms(
- @NonNull String sdkOsPath,
- @NonNull ArrayList<IAndroidTarget> targets,
- @NonNull Map<File, DirInfo> dirInfos,
- BuildToolInfo latestBuildTools,
- @NonNull ILogger log) {
- File platformFolder = new File(sdkOsPath, SdkConstants.FD_PLATFORMS);
-
- if (platformFolder.isDirectory()) {
- File[] platforms = platformFolder.listFiles();
-
- if (platforms != null) {
- for (File platform : platforms) {
- PlatformTarget target;
- if (platform.isDirectory()) {
- target = loadPlatform(sdkOsPath, platform, latestBuildTools, log);
- if (target != null) {
- targets.add(target);
- }
- // Remember we visited this file/directory,
- // even if we failed to load anything from it.
- dirInfos.put(platform, new DirInfo(platform));
- } else {
- log.warning("Ignoring platform '%1$s', not a folder.", platform.getName());
- }
- }
- }
-
- return;
- }
-
- // Try to create it or complain if something else is in the way.
- if (!platformFolder.exists()) {
- if (!platformFolder.mkdir()) {
- throw new RuntimeException(
- String.format("Failed to create %1$s.",
- platformFolder.getAbsolutePath()));
- }
- } else {
- throw new RuntimeException(
- String.format("%1$s is not a folder.",
- platformFolder.getAbsolutePath()));
- }
- }
-
- /**
- * Loads a specific Platform at a given location.
- * @param sdkOsPath Location of the SDK
- * @param platformFolder the root folder of the platform.
- * @param latestBuildTools the latestBuildTools
- * @param log the ILogger object receiving warning/error from the parsing.
- */
- @Nullable
- private static PlatformTarget loadPlatform(
- @NonNull String sdkOsPath,
- @NonNull File platformFolder,
- BuildToolInfo latestBuildTools,
- @NonNull ILogger log) {
- FileWrapper buildProp = new FileWrapper(platformFolder, SdkConstants.FN_BUILD_PROP);
- FileWrapper sourcePropFile = new FileWrapper(platformFolder, SdkConstants.FN_SOURCE_PROP);
-
- if (buildProp.isFile() && sourcePropFile.isFile()) {
- Map<String, String> platformProp = new HashMap<String, String>();
-
- // add all the property files
- Map<String, String> map = ProjectProperties.parsePropertyFile(buildProp, log);
- if (map != null) {
- platformProp.putAll(map);
- }
-
- map = ProjectProperties.parsePropertyFile(sourcePropFile, log);
- if (map != null) {
- platformProp.putAll(map);
- }
-
- FileWrapper sdkPropFile = new FileWrapper(platformFolder, SdkConstants.FN_SDK_PROP);
- if (sdkPropFile.isFile()) { // obsolete platforms don't have this.
- map = ProjectProperties.parsePropertyFile(sdkPropFile, log);
- if (map != null) {
- platformProp.putAll(map);
- }
- }
-
- // look for some specific values in the map.
-
- // api level
- int apiNumber;
- String stringValue = platformProp.get(PROP_VERSION_SDK);
- if (stringValue == null) {
- log.warning(
- "Ignoring platform '%1$s': %2$s is missing from '%3$s'",
- platformFolder.getName(), PROP_VERSION_SDK,
- SdkConstants.FN_BUILD_PROP);
- return null;
- } else {
- try {
- apiNumber = Integer.parseInt(stringValue);
- } catch (NumberFormatException e) {
- // looks like apiNumber does not parse to a number.
- // Ignore this platform.
- log.warning(
- "Ignoring platform '%1$s': %2$s is not a valid number in %3$s.",
- platformFolder.getName(), PROP_VERSION_SDK,
- SdkConstants.FN_BUILD_PROP);
- return null;
- }
- }
-
- // Codename must be either null or a platform codename.
- // REL means it's a release version and therefore the codename should be null.
- AndroidVersion apiVersion =
- new AndroidVersion(apiNumber, platformProp.get(PROP_VERSION_CODENAME));
-
- // version string
- String apiName = platformProp.get(PkgProps.PLATFORM_VERSION);
- if (apiName == null) {
- apiName = platformProp.get(PROP_VERSION_RELEASE);
- }
- if (apiName == null) {
- log.warning(
- "Ignoring platform '%1$s': %2$s is missing from '%3$s'",
- platformFolder.getName(), PROP_VERSION_RELEASE,
- SdkConstants.FN_BUILD_PROP);
- return null;
- }
-
- // platform rev number & layoutlib version are extracted from the source.properties
- // saved by the SDK Manager when installing the package.
-
- int revision = 1;
- LayoutlibVersion layoutlibVersion = null;
- try {
- revision = Integer.parseInt(platformProp.get(PkgProps.PKG_REVISION));
- } catch (NumberFormatException e) {
- // do nothing, we'll keep the default value of 1.
- }
-
- try {
- String propApi = platformProp.get(PkgProps.LAYOUTLIB_API);
- String propRev = platformProp.get(PkgProps.LAYOUTLIB_REV);
- int llApi = propApi == null ? LayoutlibVersion.NOT_SPECIFIED :
- Integer.parseInt(propApi);
- int llRev = propRev == null ? LayoutlibVersion.NOT_SPECIFIED :
- Integer.parseInt(propRev);
- if (llApi > LayoutlibVersion.NOT_SPECIFIED &&
- llRev >= LayoutlibVersion.NOT_SPECIFIED) {
- layoutlibVersion = new LayoutlibVersion(llApi, llRev);
- }
- } catch (NumberFormatException e) {
- // do nothing, we'll ignore the layoutlib version if it's invalid
- }
-
- // api number and name look valid, perform a few more checks
- if (checkPlatformContent(platformFolder, log) == false) {
- return null;
- }
-
- ISystemImage[] systemImages =
- getPlatformSystemImages(sdkOsPath, platformFolder, apiVersion);
-
- // create the target.
- PlatformTarget target = new PlatformTarget(
- sdkOsPath,
- platformFolder.getAbsolutePath(),
- apiVersion,
- apiName,
- revision,
- layoutlibVersion,
- systemImages,
- platformProp,
- latestBuildTools);
-
- // need to parse the skins.
- String[] skins = parseSkinFolder(target.getPath(IAndroidTarget.SKINS));
- target.setSkins(skins);
-
- return target;
- } else {
- log.warning("Ignoring platform '%1$s': %2$s is missing.", //$NON-NLS-1$
- platformFolder.getName(),
- SdkConstants.FN_BUILD_PROP);
- }
-
- return null;
- }
-
- /**
- * Get all the system images supported by an add-on target.
- * For an add-on, we first look for sub-folders in the addon/images directory.
- * If none are found but the directory exists and is not empty, assume it's a legacy
- * arm eabi system image.
- * <p/>
- * Note that it's OK for an add-on to have no system-images at all, since it can always
- * rely on the ones from its base platform.
- *
- * @param root Root of the add-on target being loaded.
- * @return an array of ISystemImage containing all the system images for the target.
- * The list can be empty but not null.
- */
- @NonNull
- private static ISystemImage[] getAddonSystemImages(@NonNull File root) {
- Set<ISystemImage> found = new TreeSet<ISystemImage>();
-
- root = new File(root, SdkConstants.OS_IMAGES_FOLDER);
- File[] files = root.listFiles();
- boolean hasImgFiles = false;
-
- if (files != null) {
- // Look for sub-directories
- for (File file : files) {
- if (file.isDirectory()) {
- found.add(new SystemImage(
- file,
- LocationType.IN_PLATFORM_SUBFOLDER,
- file.getName()));
- } else if (!hasImgFiles && file.isFile()) {
- if (file.getName().endsWith(".img")) { //$NON-NLS-1$
- hasImgFiles = true;
- }
- }
- }
- }
-
- if (found.size() == 0 && hasImgFiles && root.isDirectory()) {
- // We found no sub-folder system images but it looks like the top directory
- // has some img files in it. It must be a legacy ARM EABI system image folder.
- found.add(new SystemImage(
- root,
- LocationType.IN_PLATFORM_LEGACY,
- SdkConstants.ABI_ARMEABI));
- }
-
- return found.toArray(new ISystemImage[found.size()]);
- }
-
- /**
- * Get all the system images supported by a platform target.
- * For a platform, we first look in the new sdk/system-images folders then we
- * look for sub-folders in the platform/images directory and/or the one legacy
- * folder.
- * If any given API appears twice or more, the first occurrence wins.
- *
- * @param sdkOsPath The path to the SDK.
- * @param root Root of the platform target being loaded.
- * @param version API level + codename of platform being loaded.
- * @return an array of ISystemImage containing all the system images for the target.
- * The list can be empty but not null.
- */
- @NonNull
- private static ISystemImage[] getPlatformSystemImages(
- @NonNull String sdkOsPath,
- @NonNull File root,
- @NonNull AndroidVersion version) {
- Set<ISystemImage> found = new TreeSet<ISystemImage>();
- Set<String> abiFound = new HashSet<String>();
-
- // First look in the SDK/system-image/platform-n/abi folders.
- // We require/enforce the system image to have a valid properties file.
- // The actual directory names are irrelevant.
- // If we find multiple occurrences of the same platform/abi, the first one read wins.
-
- File[] firstLevelFiles = new File(sdkOsPath, SdkConstants.FD_SYSTEM_IMAGES).listFiles();
- if (firstLevelFiles != null) {
- for (File firstLevel : firstLevelFiles) {
- File[] secondLevelFiles = firstLevel.listFiles();
- if (secondLevelFiles != null) {
- for (File secondLevel : secondLevelFiles) {
- try {
- File propFile = new File(secondLevel, SdkConstants.FN_SOURCE_PROP);
- Properties props = new Properties();
- FileInputStream fis = null;
- try {
- fis = new FileInputStream(propFile);
- props.load(fis);
- } finally {
- if (fis != null) {
- fis.close();
- }
- }
-
- AndroidVersion propsVersion = new AndroidVersion(props);
- if (!propsVersion.equals(version)) {
- continue;
- }
-
- String abi = props.getProperty(PkgProps.SYS_IMG_ABI);
- if (abi != null && !abiFound.contains(abi)) {
- found.add(new SystemImage(
- secondLevel,
- LocationType.IN_SYSTEM_IMAGE,
- abi));
- abiFound.add(abi);
- }
- } catch (Exception ignore) {}
- }
- }
- }
- }
-
- // Then look in either the platform/images/abi or the legacy folder
- root = new File(root, SdkConstants.OS_IMAGES_FOLDER);
- File[] files = root.listFiles();
- boolean useLegacy = true;
- boolean hasImgFiles = false;
-
- if (files != null) {
- // Look for sub-directories
- for (File file : files) {
- if (file.isDirectory()) {
- useLegacy = false;
- String abi = file.getName();
- if (!abiFound.contains(abi)) {
- found.add(new SystemImage(
- file,
- LocationType.IN_PLATFORM_SUBFOLDER,
- abi));
- abiFound.add(abi);
- }
- } else if (!hasImgFiles && file.isFile()) {
- if (file.getName().endsWith(".img")) { //$NON-NLS-1$
- hasImgFiles = true;
- }
- }
- }
- }
-
- if (useLegacy && hasImgFiles && root.isDirectory() &&
- !abiFound.contains(SdkConstants.ABI_ARMEABI)) {
- // We found no sub-folder system images but it looks like the top directory
- // has some img files in it. It must be a legacy ARM EABI system image folder.
- found.add(new SystemImage(
- root,
- LocationType.IN_PLATFORM_LEGACY,
- SdkConstants.ABI_ARMEABI));
- }
-
- return found.toArray(new ISystemImage[found.size()]);
- }
-
- /**
- * Loads the Add-on from the SDK.
- * Creates the "add-ons" folder if necessary.
- *
- * @param osSdkPath Location of the SDK
- * @param targets the list to fill with the add-ons.
- * @param dirInfos a map to keep information on directories to see if they change later.
- * @param log the ILogger object receiving warning/error from the parsing.
- * @throws RuntimeException when the "add-ons" folder is missing and cannot be created.
- */
- private static void loadAddOns(
- @NonNull String osSdkPath,
- @NonNull ArrayList<IAndroidTarget> targets,
- @NonNull Map<File, DirInfo> dirInfos,
- @NonNull ILogger log) {
- File addonFolder = new File(osSdkPath, SdkConstants.FD_ADDONS);
-
- if (addonFolder.isDirectory()) {
- File[] addons = addonFolder.listFiles();
-
- IAndroidTarget[] targetList = targets.toArray(new IAndroidTarget[targets.size()]);
-
- if (addons != null) {
- for (File addon : addons) {
- // Add-ons have to be folders. Ignore files and no need to warn about them.
- AddOnTarget target;
- if (addon.isDirectory()) {
- target = loadAddon(addon, targetList, log);
- if (target != null) {
- targets.add(target);
- }
- // Remember we visited this file/directory,
- // even if we failed to load anything from it.
- dirInfos.put(addon, new DirInfo(addon));
- }
- }
- }
-
- return;
- }
-
- // Try to create it or complain if something else is in the way.
- if (!addonFolder.exists()) {
- if (!addonFolder.mkdir()) {
- throw new RuntimeException(
- String.format("Failed to create %1$s.",
- addonFolder.getAbsolutePath()));
- }
- } else {
- throw new RuntimeException(
- String.format("%1$s is not a folder.",
- addonFolder.getAbsolutePath()));
- }
- }
-
- /**
- * Loads a specific Add-on at a given location.
- * @param addonDir the location of the add-on directory.
- * @param targetList The list of Android target that were already loaded from the SDK.
- * @param log the ILogger object receiving warning/error from the parsing.
- */
- @Nullable
- private static AddOnTarget loadAddon(
- @NonNull File addonDir,
- @NonNull IAndroidTarget[] targetList,
- @NonNull ILogger log) {
- // Parse the addon properties to ensure we can load it.
- Pair<Map<String, String>, String> infos = parseAddonProperties(addonDir, targetList, log);
-
- Map<String, String> propertyMap = infos.getFirst();
- String error = infos.getSecond();
-
- if (error != null) {
- log.warning("Ignoring add-on '%1$s': %2$s", addonDir.getName(), error);
- return null;
- }
-
- // Since error==null we're not supposed to encounter any issues loading this add-on.
- try {
- assert propertyMap != null;
-
- String api = propertyMap.get(ADDON_API);
- String name = propertyMap.get(ADDON_NAME);
- String vendor = propertyMap.get(ADDON_VENDOR);
-
- assert api != null;
- assert name != null;
- assert vendor != null;
-
- PlatformTarget baseTarget = null;
-
- // Look for a platform that has a matching api level or codename.
- for (IAndroidTarget target : targetList) {
- if (target.isPlatform() && target.getVersion().equals(api)) {
- baseTarget = (PlatformTarget)target;
- break;
- }
- }
-
- assert baseTarget != null;
-
- // get the optional description
- String description = propertyMap.get(ADDON_DESCRIPTION);
-
- // get the add-on revision
- int revisionValue = 1;
- String revision = propertyMap.get(ADDON_REVISION);
- if (revision == null) {
- revision = propertyMap.get(ADDON_REVISION_OLD);
- }
- if (revision != null) {
- revisionValue = Integer.parseInt(revision);
- }
-
- // get the optional libraries
- String librariesValue = propertyMap.get(ADDON_LIBRARIES);
- Map<String, String[]> libMap = null;
-
- if (librariesValue != null) {
- librariesValue = librariesValue.trim();
- if (librariesValue.length() > 0) {
- // split in the string into the libraries name
- String[] libraries = librariesValue.split(";"); //$NON-NLS-1$
- if (libraries.length > 0) {
- libMap = new HashMap<String, String[]>();
- for (String libName : libraries) {
- libName = libName.trim();
-
- // get the library data from the properties
- String libData = propertyMap.get(libName);
-
- if (libData != null) {
- // split the jar file from the description
- Matcher m = PATTERN_LIB_DATA.matcher(libData);
- if (m.matches()) {
- libMap.put(libName, new String[] {
- m.group(1), m.group(2) });
- } else {
- log.warning(
- "Ignoring library '%1$s', property value has wrong format\n\t%2$s",
- libName, libData);
- }
- } else {
- log.warning(
- "Ignoring library '%1$s', missing property value",
- libName, libData);
- }
- }
- }
- }
- }
-
- // get the abi list.
- ISystemImage[] systemImages = getAddonSystemImages(addonDir);
-
- // check whether the add-on provides its own rendering info/library.
- boolean hasRenderingLibrary = false;
- boolean hasRenderingResources = false;
-
- File dataFolder = new File(addonDir, SdkConstants.FD_DATA);
- if (dataFolder.isDirectory()) {
- hasRenderingLibrary = new File(dataFolder, SdkConstants.FN_LAYOUTLIB_JAR).isFile();
- hasRenderingResources = new File(dataFolder, SdkConstants.FD_RES).isDirectory() &&
- new File(dataFolder, SdkConstants.FD_FONTS).isDirectory();
- }
-
- AddOnTarget target = new AddOnTarget(addonDir.getAbsolutePath(), name, vendor,
- revisionValue, description, systemImages, libMap,
- hasRenderingLibrary, hasRenderingResources,baseTarget);
-
- // need to parse the skins.
- String[] skins = parseSkinFolder(target.getPath(IAndroidTarget.SKINS));
-
- // get the default skin, or take it from the base platform if needed.
- String defaultSkin = propertyMap.get(ADDON_DEFAULT_SKIN);
- if (defaultSkin == null) {
- if (skins.length == 1) {
- defaultSkin = skins[0];
- } else {
- defaultSkin = baseTarget.getDefaultSkin();
- }
- }
-
- // get the USB ID (if available)
- int usbVendorId = convertId(propertyMap.get(ADDON_USB_VENDOR));
- if (usbVendorId != IAndroidTarget.NO_USB_ID) {
- target.setUsbVendorId(usbVendorId);
- }
-
- target.setSkins(skins, defaultSkin);
-
- return target;
-
- } catch (Exception e) {
- log.warning("Ignoring add-on '%1$s': error %2$s.",
- addonDir.getName(), e.toString());
- }
-
- return null;
- }
-
- /**
- * Parses the add-on properties and decodes any error that occurs when loading an addon.
- *
- * @param addonDir the location of the addon directory.
- * @param targetList The list of Android target that were already loaded from the SDK.
- * @param log the ILogger object receiving warning/error from the parsing.
- * @return A pair with the property map and an error string. Both can be null but not at the
- * same time. If a non-null error is present then the property map must be ignored. The error
- * should be translatable as it might show up in the SdkManager UI.
- */
- @NonNull
- public static Pair<Map<String, String>, String> parseAddonProperties(
- @NonNull File addonDir,
- @NonNull IAndroidTarget[] targetList,
- @NonNull ILogger log) {
- Map<String, String> propertyMap = null;
- String error = null;
-
- FileWrapper addOnManifest = new FileWrapper(addonDir, SdkConstants.FN_MANIFEST_INI);
-
- do {
- if (!addOnManifest.isFile()) {
- error = String.format("File not found: %1$s", SdkConstants.FN_MANIFEST_INI);
- break;
- }
-
- propertyMap = ProjectProperties.parsePropertyFile(addOnManifest, log);
- if (propertyMap == null) {
- error = String.format("Failed to parse properties from %1$s",
- SdkConstants.FN_MANIFEST_INI);
- break;
- }
-
- // look for some specific values in the map.
- // we require name, vendor, and api
- String name = propertyMap.get(ADDON_NAME);
- if (name == null) {
- error = addonManifestWarning(ADDON_NAME);
- break;
- }
-
- String vendor = propertyMap.get(ADDON_VENDOR);
- if (vendor == null) {
- error = addonManifestWarning(ADDON_VENDOR);
- break;
- }
-
- String api = propertyMap.get(ADDON_API);
- PlatformTarget baseTarget = null;
- if (api == null) {
- error = addonManifestWarning(ADDON_API);
- break;
- }
-
- // Look for a platform that has a matching api level or codename.
- for (IAndroidTarget target : targetList) {
- if (target.isPlatform() && target.getVersion().equals(api)) {
- baseTarget = (PlatformTarget)target;
- break;
- }
- }
-
- if (baseTarget == null) {
- error = String.format("Unable to find base platform with API level '%1$s'", api);
- break;
- }
-
- // get the add-on revision
- String revision = propertyMap.get(ADDON_REVISION);
- if (revision == null) {
- revision = propertyMap.get(ADDON_REVISION_OLD);
- }
- if (revision != null) {
- try {
- Integer.parseInt(revision);
- } catch (NumberFormatException e) {
- // looks like revision does not parse to a number.
- error = String.format("%1$s is not a valid number in %2$s.",
- ADDON_REVISION, SdkConstants.FN_BUILD_PROP);
- break;
- }
- }
-
- } while(false);
-
- return Pair.of(propertyMap, error);
- }
-
- /**
- * Converts a string representation of an hexadecimal ID into an int.
- * @param value the string to convert.
- * @return the int value, or {@link IAndroidTarget#NO_USB_ID} if the conversion failed.
- */
- private static int convertId(@Nullable String value) {
- if (value != null && value.length() > 0) {
- if (PATTERN_USB_IDS.matcher(value).matches()) {
- String v = value.substring(2);
- try {
- return Integer.parseInt(v, 16);
- } catch (NumberFormatException e) {
- // this shouldn't happen since we check the pattern above, but this is safer.
- // the method will return 0 below.
- }
- }
- }
-
- return IAndroidTarget.NO_USB_ID;
- }
-
- /**
- * Prepares a warning about the addon being ignored due to a missing manifest value.
- * This string will show up in the SdkManager UI.
- *
- * @param valueName The missing manifest value, for display.
- */
- @NonNull
- private static String addonManifestWarning(@NonNull String valueName) {
- return String.format("'%1$s' is missing from %2$s.",
- valueName, SdkConstants.FN_MANIFEST_INI);
- }
-
- /**
- * Checks the given platform has all the required files, and returns true if they are all
- * present.
- * <p/>This checks the presence of the following files: android.jar, framework.aidl, aapt(.exe),
- * aidl(.exe), dx(.bat), and dx.jar
- *
- * @param platform The folder containing the platform.
- * @param log Logger.
- */
- private static boolean checkPlatformContent(
- @NonNull File platform,
- @NonNull ILogger log) {
- for (String relativePath : sPlatformContentList) {
- File f = new File(platform, relativePath);
- if (!f.exists()) {
- log.warning(
- "Ignoring platform '%1$s': %2$s is missing.", //$NON-NLS-1$
- platform.getName(), relativePath);
- return false;
- }
- }
- return true;
- }
-
-
-
- /**
- * Parses the skin folder and builds the skin list.
- * @param osPath The path of the skin root folder.
- */
- @NonNull
- private static String[] parseSkinFolder(@NonNull String osPath) {
- File skinRootFolder = new File(osPath);
-
- if (skinRootFolder.isDirectory()) {
- ArrayList<String> skinList = new ArrayList<String>();
-
- File[] files = skinRootFolder.listFiles();
-
- for (File skinFolder : files) {
- if (skinFolder.isDirectory()) {
- // check for layout file
- File layout = new File(skinFolder, SdkConstants.FN_SKIN_LAYOUT);
-
- if (layout.isFile()) {
- // for now we don't parse the content of the layout and
- // simply add the directory to the list.
- skinList.add(skinFolder.getName());
- }
- }
- }
-
- return skinList.toArray(new String[skinList.size()]);
- }
-
- return new String[0];
- }
-
- /**
- * Initialize the sample folders for all known targets (platforms and addons).
- * <p/>
- * Samples used to be located at SDK/Target/samples. We then changed this to
- * have a separate SDK/samples/samples-API directory. This parses either directories
- * and sets the targets' sample path accordingly.
- *
- * @param log Logger.
- */
- private void initializeSamplePaths(@NonNull ILogger log) {
- File sampleFolder = new File(mOsSdkPath, SdkConstants.FD_SAMPLES);
- if (sampleFolder.isDirectory()) {
- File[] platforms = sampleFolder.listFiles();
-
- for (File platform : platforms) {
- if (platform.isDirectory()) {
- // load the source.properties file and get an AndroidVersion object from it.
- AndroidVersion version = getSamplesVersion(platform, log);
-
- if (version != null) {
- // locate the platform matching this version
- for (IAndroidTarget target : mTargets) {
- if (target.isPlatform() && target.getVersion().equals(version)) {
- ((PlatformTarget)target).setSamplesPath(platform.getAbsolutePath());
- break;
- }
- }
- }
- }
- }
- }
- }
-
- /**
- * Returns the {@link AndroidVersion} of the sample in the given folder.
- *
- * @param folder The sample's folder.
- * @param log Logger for errors.
- * @return An {@link AndroidVersion} or null on error.
- */
- @Nullable
- private AndroidVersion getSamplesVersion(
- @NonNull File folder,
- @NonNull ILogger log) {
- File sourceProp = new File(folder, SdkConstants.FN_SOURCE_PROP);
- try {
- Properties p = new Properties();
- FileInputStream fis = null;
- try {
- fis = new FileInputStream(sourceProp);
- p.load(fis);
- } finally {
- if (fis != null) {
- fis.close();
- }
- }
-
- return new AndroidVersion(p);
- } catch (FileNotFoundException e) {
- log.warning("Ignoring sample '%1$s': does not contain %2$s.", //$NON-NLS-1$
- folder.getName(), SdkConstants.FN_SOURCE_PROP);
- } catch (IOException e) {
- log.warning("Ignoring sample '%1$s': failed reading %2$s.", //$NON-NLS-1$
- folder.getName(), SdkConstants.FN_SOURCE_PROP);
- } catch (AndroidVersionException e) {
- log.warning("Ignoring sample '%1$s': no android version found in %2$s.", //$NON-NLS-1$
- folder.getName(), SdkConstants.FN_SOURCE_PROP);
- }
-
- return null;
- }
-
- /**
- * Loads the build-tools from the SDK.
- * Creates the "build-tools" folder if necessary.
- *
- * @param sdkOsPath Location of the SDK
- * @param infos the map to fill with the build-tools.
- * @param dirInfos a map to keep information on directories to see if they change later.
- * @param log the ILogger object receiving warning/error from the parsing.
- * @throws RuntimeException when the "platforms" folder is missing and cannot be created.
- */
- private static void loadBuildTools(
- @NonNull String sdkOsPath,
- @NonNull Map<FullRevision, BuildToolInfo> infos,
- @NonNull Map<File, DirInfo> dirInfos,
- @NonNull ILogger log) {
- File buildToolsFolder = new File(sdkOsPath, SdkConstants.FD_BUILD_TOOLS);
-
- if (buildToolsFolder.isDirectory()) {
- File[] folders = buildToolsFolder.listFiles();
- if (folders != null) {
- for (File subFolder : folders) {
- if (subFolder.isDirectory()) {
- BuildToolInfo info = loadBuildTool(sdkOsPath, subFolder, log);
- if (info != null) {
- infos.put(info.getRevision(), info);
- }
- // Remember we visited this file/directory,
- // even if we failed to load anything from it.
- dirInfos.put(subFolder, new DirInfo(subFolder));
- } else {
- log.warning("Ignoring build-tool '%1$s', not a folder.",
- subFolder.getName());
- }
- }
- }
-
- return;
- }
-
- // Try to create it or complain if something else is in the way.
- if (!buildToolsFolder.exists()) {
- if (!buildToolsFolder.mkdir()) {
- throw new RuntimeException(
- String.format("Failed to create %1$s.",
- buildToolsFolder.getAbsolutePath()));
- }
- } else {
- throw new RuntimeException(
- String.format("%1$s is not a folder.",
- buildToolsFolder.getAbsolutePath()));
- }
- }
-
- /**
- * Loads a specific Platform at a given location.
- * @param sdkOsPath Location of the SDK
- * @param folder the root folder of the platform.
- * @param log the ILogger object receiving warning/error from the parsing.
- */
- @Nullable
- private static BuildToolInfo loadBuildTool(
- @NonNull String sdkOsPath,
- @NonNull File folder,
- @NonNull ILogger log) {
- FileOp f = new FileOp();
-
- File sourcePropFile = new File(folder, SdkConstants.FN_SOURCE_PROP);
- if (!f.isFile(sourcePropFile)) {
- log.warning("Ignoring build-tool '%1$s': missing file %2$s",
- folder.getName(), SdkConstants.FN_SOURCE_PROP);
- } else {
- Properties props = f.loadProperties(sourcePropFile);
- String revStr = props.getProperty(PkgProps.PKG_REVISION);
-
- try {
- FullRevision rev =
- FullRevision.parseRevision(props.getProperty(PkgProps.PKG_REVISION));
-
- BuildToolInfo info = new BuildToolInfo(rev, folder);
- return info.isValid(log) ? info : null;
-
- } catch (NumberFormatException e) {
- log.warning("Ignoring build-tool '%1$s': invalid revision '%2$s'",
- folder.getName(), revStr);
- }
-
- }
-
- return null;
- }
-
// -------------
public static class LayoutlibVersion implements Comparable<LayoutlibVersion> {
@@ -1473,114 +405,4 @@
return lhsValue - rhsValue;
}
}
-
- // -------------
-
- private static class DirInfo {
- @NonNull
- private final File mDir;
- private final long mDirModifiedTS;
- private final long mPropsModifiedTS;
- private final long mPropsChecksum;
-
- /**
- * Creates a new immutable {@link DirInfo}.
- *
- * @param dir The platform/addon directory of the target. It should be a directory.
- */
- public DirInfo(@NonNull File dir) {
- mDir = dir;
- mDirModifiedTS = dir.lastModified();
-
- // Capture some info about the source.properties file if it exists.
- // We use propsModifiedTS == 0 to mean there is no props file.
- long propsChecksum = 0;
- long propsModifiedTS = 0;
- File props = new File(dir, SdkConstants.FN_SOURCE_PROP);
- if (props.isFile()) {
- propsModifiedTS = props.lastModified();
- propsChecksum = getFileChecksum(props);
- }
- mPropsModifiedTS = propsModifiedTS;
- mPropsChecksum = propsChecksum;
- }
-
- /**
- * Checks whether the directory/source.properties attributes have changed.
- *
- * @return True if the directory modified timestamp or
- * its source.property files have changed.
- */
- public boolean hasChanged() {
- // Does platform directory still exist?
- if (!mDir.isDirectory()) {
- return true;
- }
- // Has platform directory modified-timestamp changed?
- if (mDirModifiedTS != mDir.lastModified()) {
- return true;
- }
-
- File props = new File(mDir, SdkConstants.FN_SOURCE_PROP);
-
- // The directory did not have a props file if target was null or
- // if mPropsModifiedTS is 0.
- boolean hadProps = mPropsModifiedTS != 0;
-
- // Was there a props file and it vanished, or there wasn't and there's one now?
- if (hadProps != props.isFile()) {
- return true;
- }
-
- if (hadProps) {
- // Has source.props file modified-timestamp changed?
- if (mPropsModifiedTS != props.lastModified()) {
- return true;
- }
- // Had the content of source.props changed?
- if (mPropsChecksum != getFileChecksum(props)) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Computes an adler32 checksum (source.props are small files, so this
- * should be OK with an acceptable collision rate.)
- */
- private static long getFileChecksum(@NonNull File file) {
- FileInputStream fis = null;
- try {
- fis = new FileInputStream(file);
- Adler32 a = new Adler32();
- byte[] buf = new byte[1024];
- int n;
- while ((n = fis.read(buf)) > 0) {
- a.update(buf, 0, n);
- }
- return a.getValue();
- } catch (Exception ignore) {
- } finally {
- try {
- if (fis != null) {
- fis.close();
- }
- } catch(Exception ignore) {}
- }
- return 0;
- }
-
- /** Returns a visual representation of this object for debugging. */
- @Override
- public String toString() {
- String s = String.format("<DirInfo %1$s TS=%2$d", mDir, mDirModifiedTS); //$NON-NLS-1$
- if (mPropsModifiedTS != 0) {
- s += String.format(" | Props TS=%1$d, Chksum=%2$s", //$NON-NLS-1$
- mPropsModifiedTS, mPropsChecksum);
- }
- return s + ">"; //$NON-NLS-1$
- }
- }
}
diff --git a/sdklib/src/main/java/com/android/sdklib/SystemImage.java b/sdklib/src/main/java/com/android/sdklib/SystemImage.java
index afc11c7..fadb44e 100755
--- a/sdklib/src/main/java/com/android/sdklib/SystemImage.java
+++ b/sdklib/src/main/java/com/android/sdklib/SystemImage.java
@@ -17,6 +17,7 @@
package com.android.sdklib;
import com.android.SdkConstants;
+import com.android.sdklib.internal.androidTarget.PlatformTarget;
import com.android.sdklib.io.FileOp;
import java.io.File;
diff --git a/sdklib/src/main/java/com/android/sdklib/build/ApkBuilder.java b/sdklib/src/main/java/com/android/sdklib/build/ApkBuilder.java
index 685cbc5..4a69e2e 100644
--- a/sdklib/src/main/java/com/android/sdklib/build/ApkBuilder.java
+++ b/sdklib/src/main/java/com/android/sdklib/build/ApkBuilder.java
@@ -106,7 +106,7 @@
// jar file, but we need to exclude some other folder (like /META-INF) so
// we check anyway.
for (int i = 0 ; i < segments.length - 1; i++) {
- if (checkFolderForPackaging(segments[i]) == false) {
+ if (!checkFolderForPackaging(segments[i])) {
return false;
}
}
@@ -618,8 +618,10 @@
try {
// file is a directory, process its content.
File[] files = sourceFolder.listFiles();
- for (File file : files) {
- processFileForResource(builder, file, null);
+ if (files != null) {
+ for (File file : files) {
+ processFileForResource(builder, file, null);
+ }
}
} catch (DuplicateFileException e) {
throw e;
@@ -657,7 +659,7 @@
throw new SealedApkException("APK is already sealed");
}
- if (nativeFolder.isDirectory() == false) {
+ if (!nativeFolder.isDirectory()) {
// not a directory? check if it's a file or doesn't exist
if (nativeFolder.exists()) {
throw new ApkCreationException("%s is not a folder", nativeFolder);
@@ -732,7 +734,7 @@
public static List<FileEntry> getNativeFiles(File nativeFolder, boolean debugMode)
throws ApkCreationException {
- if (nativeFolder.isDirectory() == false) {
+ if (!nativeFolder.isDirectory()) {
// not a directory? check if it's a file or doesn't exist
if (nativeFolder.exists()) {
throw new ApkCreationException("%s is not a folder", nativeFolder);
@@ -850,8 +852,10 @@
// and process its content.
File[] files = file.listFiles();
- for (File contentFile : files) {
- processFileForResource(builder, contentFile, path);
+ if (files != null) {
+ for (File contentFile : files) {
+ processFileForResource(builder, contentFile, path);
+ }
}
}
} else {
@@ -896,12 +900,12 @@
}
if (file.exists()) { // will be a file in this case.
- if (file.canWrite() == false) {
+ if (!file.canWrite()) {
throw new ApkCreationException("Cannot write %s", file);
}
} else {
try {
- if (file.createNewFile() == false) {
+ if (!file.createNewFile()) {
throw new ApkCreationException("Failed to create %s", file);
}
} catch (IOException e) {
@@ -927,7 +931,7 @@
}
if (file.exists()) {
- if (file.canRead() == false) {
+ if (!file.canRead()) {
throw new ApkCreationException("Cannot read %s", file);
}
} else {
@@ -949,11 +953,11 @@
* @param folderName the name of the folder.
*/
public static boolean checkFolderForPackaging(String folderName) {
- return folderName.equalsIgnoreCase("CVS") == false &&
- folderName.equalsIgnoreCase(".svn") == false &&
- folderName.equalsIgnoreCase("SCCS") == false &&
- folderName.equalsIgnoreCase("META-INF") == false &&
- folderName.startsWith("_") == false;
+ return !folderName.equalsIgnoreCase("CVS") &&
+ !folderName.equalsIgnoreCase(".svn") &&
+ !folderName.equalsIgnoreCase("SCCS") &&
+ !folderName.equalsIgnoreCase("META-INF") &&
+ !folderName.startsWith("_");
}
/**
@@ -983,18 +987,19 @@
return false;
}
- return "aidl".equalsIgnoreCase(extension) == false && // Aidl files
- "rs".equalsIgnoreCase(extension) == false && // RenderScript files
- "rsh".equalsIgnoreCase(extension) == false && // RenderScript header files
- "d".equalsIgnoreCase(extension) == false && // Dependency files
- "java".equalsIgnoreCase(extension) == false && // Java files
- "scala".equalsIgnoreCase(extension) == false && // Scala files
- "class".equalsIgnoreCase(extension) == false && // Java class files
- "scc".equalsIgnoreCase(extension) == false && // VisualSourceSafe
- "swp".equalsIgnoreCase(extension) == false && // vi swap file
- "thumbs.db".equalsIgnoreCase(fileName) == false && // image index file
- "picasa.ini".equalsIgnoreCase(fileName) == false && // image index file
- "package.html".equalsIgnoreCase(fileName) == false && // Javadoc
- "overview.html".equalsIgnoreCase(fileName) == false; // Javadoc
+ return !"aidl".equalsIgnoreCase(extension) && // Aidl files
+ !"rs".equalsIgnoreCase(extension) && // RenderScript files
+ !"fs".equalsIgnoreCase(extension) && // FilterScript files
+ !"rsh".equalsIgnoreCase(extension) && // RenderScript header files
+ !"d".equalsIgnoreCase(extension) && // Dependency files
+ !"java".equalsIgnoreCase(extension) && // Java files
+ !"scala".equalsIgnoreCase(extension) && // Scala files
+ !"class".equalsIgnoreCase(extension) && // Java class files
+ !"scc".equalsIgnoreCase(extension) && // VisualSourceSafe
+ !"swp".equalsIgnoreCase(extension) && // vi swap file
+ !"thumbs.db".equalsIgnoreCase(fileName) && // image index file
+ !"picasa.ini".equalsIgnoreCase(fileName) && // image index file
+ !"package.html".equalsIgnoreCase(fileName) && // Javadoc
+ !"overview.html".equalsIgnoreCase(fileName); // Javadoc
}
}
diff --git a/sdklib/src/main/java/com/android/sdklib/build/DependencyFile.java b/sdklib/src/main/java/com/android/sdklib/build/DependencyFile.java
new file mode 100644
index 0000000..bddd612
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/build/DependencyFile.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.build;
+
+import com.android.annotations.NonNull;
+import com.google.common.base.Charsets;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Parse a dependency file.
+ */
+public class DependencyFile {
+
+ @NonNull
+ private final File mDependencyFile;
+
+ @NonNull
+ private final List<File> mSourceFolders;
+
+ private boolean mIsParsed = false;
+
+ private List<File> mOutputFiles;
+ private List<File> mInputFiles;
+ private List<File> mSdkInputFiles;
+
+ public DependencyFile(@NonNull File dependencyFile, @NonNull List<File> sourceFolders) {
+ mDependencyFile = dependencyFile;
+ mSourceFolders = sourceFolders;
+ }
+
+ @NonNull
+ public File getFile() {
+ return mDependencyFile;
+ }
+
+ @NonNull
+ public List<File> getInputFiles() {
+ if (!mIsParsed) {
+ throw new IllegalStateException("Parsing was not done");
+ }
+ return mInputFiles;
+ }
+
+ @NonNull
+ public List<File> getSdkInputFiles() {
+ if (!mIsParsed) {
+ throw new IllegalStateException("Parsing was not done");
+ }
+ return mSdkInputFiles;
+ }
+
+ @NonNull
+ public List<File> getOutputFiles() {
+ if (!mIsParsed) {
+ throw new IllegalStateException("Parsing was not done");
+ }
+ return mOutputFiles;
+ }
+
+ /**
+ * Shortcut access to the first output file. This is useful for generator that only output
+ * one file.
+ */
+ public File getFirstOutput() {
+ if (!mIsParsed) {
+ throw new IllegalStateException("Parsing was not done");
+ }
+
+ if (!mOutputFiles.isEmpty()) {
+ return mOutputFiles.get(0);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns whether the given file is a dependency for this source file.
+ * <p/>Note that the source file itself is not tested against. Therefore if
+ * {@code file.equals(getSourceFile()} returns {@code true}, this method will return
+ * {@code false}.
+ * @param file the file to check against
+ * @return true if the given file is a dependency for this source file.
+ */
+ public boolean hasInput(@NonNull File file) {
+ if (!mIsParsed) {
+ throw new IllegalStateException("Parsing was not done");
+ }
+
+ return mInputFiles.contains(file);
+ }
+
+ /**
+ * Returns whether the given file is an ouput of this source file.
+ * @param file the file to test.
+ * @return true if the file is an output file.
+ */
+ public boolean hasOutput(@NonNull File file) {
+ if (!mIsParsed) {
+ throw new IllegalStateException("Parsing was not done");
+ }
+ return mOutputFiles.contains(file);
+ }
+
+ /**
+ * Parses the dependency file(s)
+ *
+ */
+ public void parse() throws IOException {
+ if (!mDependencyFile.isFile()) {
+ mInputFiles = Collections.emptyList();
+ mOutputFiles = Collections.emptyList();
+ mIsParsed = true;
+ return;
+ }
+
+ //contents = file.getContents();
+ List<String> lines = Files.readLines(mDependencyFile, Charsets.UTF_8);
+
+ // we're going to be pretty brutal here.
+ // The format is something like:
+ // output1 output2 [...]: source dep1 dep2 [...]
+ // expect it's likely split on several lines. So let's move it back on a single line
+ // first
+ StringBuilder sb = new StringBuilder();
+ for (String line : lines) {
+ line = line.trim();
+ if (line.endsWith("\\")) {
+ line = line.substring(0, line.length() - 1);
+ }
+
+ sb.append(line);
+ }
+
+ // split the left and right part
+ String[] files = sb.toString().split(":");
+
+ // get the output files:
+ String[] outputs = files[0].trim().split(" ");
+ mOutputFiles = getList(outputs);
+
+ // and the dependency files:
+ String[] inputs = files[1].trim().split(" ");
+
+
+ if (inputs.length == 0) {
+ mInputFiles = Collections.emptyList();
+ mSdkInputFiles = Collections.emptyList();
+ }
+
+ mInputFiles = Lists.newArrayListWithExpectedSize(inputs.length);
+ mSdkInputFiles = Lists.newArrayListWithExpectedSize(inputs.length);
+
+ for (String path : inputs) {
+ File f = new File(path);
+ if (checkParentFile(f, mSourceFolders)) {
+ mInputFiles.add(f);
+ } else {
+ mSdkInputFiles.add(f);
+ }
+ }
+
+ mIsParsed = true;
+ }
+
+ /**
+ * Checks whether a need for compilation is needed.
+ *
+ * THIS ONLY CHECK TIMESTAMP AND IS NOT A VALID WAY OF DOING THIS CHECK
+ *
+ * @return true if file timestamp detect a need for compilation
+ *
+ * @deprecated Use Gradle instead!
+ *
+ */
+ @Deprecated
+ public boolean needCompilation() {
+ if (!mIsParsed) {
+ throw new IllegalStateException("Parsing was not done");
+ }
+
+ // compares the earliest output time with the latest input time.
+ // This is very basic, but temporary until we get better control in Gradle.
+
+ long inputTime = 0;
+
+ for (File file : mInputFiles) {
+ long time = file.lastModified();
+ if (time > inputTime) {
+ inputTime = time;
+ }
+ }
+
+ long outputTime = Long.MAX_VALUE;
+ for (File file : mOutputFiles) {
+ long time = file.lastModified();
+ if (time < outputTime) {
+ outputTime = time;
+ }
+ }
+
+ return outputTime < inputTime;
+ }
+
+ private List<File> getList(@NonNull String[] paths) {
+ if (paths.length == 0) {
+ return Collections.emptyList();
+ }
+
+ List<File> list = Lists.newArrayListWithCapacity(paths.length);
+
+ for (String path : paths) {
+ list.add(new File(path));
+ }
+
+ return list;
+ }
+
+ @Override
+ public String toString() {
+ return "DependencyFile{" +
+ "mDependencyFile=" + mDependencyFile +
+ ", mIsParsed=" + mIsParsed +
+ ", mOutputFiles=" + mOutputFiles +
+ ", mInputFiles=" + mInputFiles +
+ '}';
+ }
+
+ private static boolean checkParentFile(@NonNull File child, @NonNull List<File> parents) {
+ for (File parent : parents) {
+ if (parent.equals(child)) {
+ return true;
+ }
+ }
+
+ File childParent = child.getParentFile();
+ if (childParent == null) {
+ return false;
+ }
+
+ return checkParentFile(childParent, parents);
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/build/FileGatherer.java b/sdklib/src/main/java/com/android/sdklib/build/FileGatherer.java
new file mode 100644
index 0000000..7a513bd
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/build/FileGatherer.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.build;
+
+import com.android.annotations.NonNull;
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Source Searcher processor, gathering a list of all the files found by the SourceSearcher.
+ */
+public class FileGatherer implements SourceSearcher.SourceFileProcessor {
+ @NonNull
+ private final List<File> mFiles = Lists.newArrayList();
+
+ @Override
+ public void processFile(@NonNull File sourceFile, @NonNull String extension) throws IOException {
+ mFiles.add(sourceFile);
+ }
+
+ @NonNull
+ public List<File> getFiles() {
+ return mFiles;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/build/ManualRenderScriptChecker.java b/sdklib/src/main/java/com/android/sdklib/build/ManualRenderScriptChecker.java
new file mode 100644
index 0000000..ee61030
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/build/ManualRenderScriptChecker.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.build;
+
+import static com.android.SdkConstants.EXT_FS;
+import static com.android.SdkConstants.EXT_RS;
+import static com.android.SdkConstants.EXT_RSH;
+
+import com.android.annotations.NonNull;
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Checks whether Renderscript compilation is needed. This is entirely based
+ * on using dependency files and manually looking up the list of current inputs, and old
+ * outputs timestamp.
+ *
+ * TODO: add checks on input/output checksum to detect true changes.
+ * TODO: (better) delete Ant and use Gradle.
+ *
+ * This should be only needed in Ant.
+ */
+public class ManualRenderScriptChecker extends RenderScriptChecker {
+
+ @NonNull
+ private final List<File> mInputFiles = Lists.newArrayList();
+
+ public ManualRenderScriptChecker(
+ @NonNull List<File> sourceFolders,
+ @NonNull File binFolder) {
+ super(sourceFolders, binFolder);
+ }
+
+ public boolean mustCompile() throws IOException {
+ mInputFiles.clear();
+
+ loadDependencies();
+
+ if (mDependencyFiles.isEmpty()) {
+ mInputFiles.addAll(findInputFiles());
+ return !mInputFiles.isEmpty();
+ }
+
+ // get the current files to compile, while checking then against the old inputs
+ // to detect new inputs
+ SourceSearcher searcher = new SourceSearcher(mSourceFolders, EXT_RS, EXT_FS, EXT_RSH);
+ InputProcessor inputProcessor = new InputProcessor(mOldInputs);
+ searcher.search(inputProcessor);
+
+ // at this point we have gathered the input files, so we can record them in case we have to
+ // compile later.
+ mInputFiles.addAll(inputProcessor.sourceFiles);
+
+ if (inputProcessor.mustCompile) {
+ return true;
+ }
+
+ // no new files? check if we have less input files.
+ if (mOldInputs.size() !=
+ inputProcessor.sourceFiles.size() + inputProcessor.headerFiles.size()) {
+ return true;
+ }
+
+ // since there's no change in the input, look for change in the output.
+ for (File file : mOldOutputs) {
+ if (!file.isFile()) {
+ // deleted output file?
+ return true;
+ }
+ }
+
+ // finally look at file changes.
+ for (DependencyFile depFile : mDependencyFiles) {
+ if (depFile.needCompilation()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @NonNull
+ public List<File> getInputFiles() {
+ return mInputFiles;
+ }
+
+ private static class InputProcessor implements SourceSearcher.SourceFileProcessor {
+
+ @NonNull
+ private final Set<File> mOldInputs;
+
+ List<File> sourceFiles = Lists.newArrayList();
+ List<File> headerFiles = Lists.newArrayList();
+ boolean mustCompile = false;
+
+ InputProcessor(@NonNull Set<File> oldInputs) {
+ mOldInputs = oldInputs;
+ }
+
+ @Override
+ public void processFile(@NonNull File sourceFile, @NonNull String extension)
+ throws IOException {
+ if (EXT_RSH.equals(extension)) {
+ headerFiles.add(sourceFile);
+ } else {
+ sourceFiles.add(sourceFile);
+ }
+
+ // detect new inputs.
+ if (!mOldInputs.contains(sourceFile)) {
+ mustCompile = true;
+ }
+ }
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/build/RenderScriptChecker.java b/sdklib/src/main/java/com/android/sdklib/build/RenderScriptChecker.java
new file mode 100644
index 0000000..0304cf6
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/build/RenderScriptChecker.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.build;
+
+import static com.android.SdkConstants.DOT_DEP;
+import static com.android.SdkConstants.EXT_FS;
+import static com.android.SdkConstants.EXT_RS;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Loads dependencies for Renderscript.
+ */
+public class RenderScriptChecker {
+
+ @NonNull
+ protected final List<File> mSourceFolders;
+ @NonNull
+ private final File mBinFolder;
+
+ protected Set<File> mOldOutputs;
+ protected Set<File> mOldInputs;
+ protected List<DependencyFile> mDependencyFiles;
+
+ public RenderScriptChecker(
+ @NonNull List<File> sourceFolders,
+ @NonNull File binFolder) {
+ mSourceFolders = sourceFolders;
+ mBinFolder = binFolder;
+ }
+
+ public void loadDependencies() throws IOException {
+ // get the dependency data from all files under bin/rsDeps/
+ File renderscriptDeps = new File(mBinFolder, RenderScriptProcessor.RS_DEPS);
+
+ File[] depsFiles = null;
+
+ if (renderscriptDeps.isDirectory()) {
+ depsFiles = renderscriptDeps.listFiles(new FilenameFilter() {
+ @Override
+ public boolean accept(File file, String s) {
+ return s.endsWith(DOT_DEP);
+ }
+ });
+ }
+
+ int count = depsFiles != null ? depsFiles.length : 0;
+ mDependencyFiles = Lists.newArrayListWithCapacity(0);
+ mOldOutputs = Sets.newHashSet();
+ mOldInputs = Sets.newHashSet();
+ if (count > 0) {
+ for (File file : depsFiles) {
+ DependencyFile depFile = new DependencyFile(file, mSourceFolders);
+ depFile.parse();
+ mDependencyFiles.add(depFile);
+ // record old inputs
+ mOldOutputs.addAll(depFile.getOutputFiles());
+ // record old inputs
+ mOldInputs.addAll(depFile.getInputFiles());
+ }
+ }
+ }
+
+ @NonNull
+ public List<File> findInputFiles() throws IOException {
+ // gather source files.
+ SourceSearcher searcher = new SourceSearcher(mSourceFolders, EXT_RS, EXT_FS);
+ FileGatherer fileGatherer = new FileGatherer();
+ searcher.search(fileGatherer);
+ return fileGatherer.getFiles();
+ }
+
+ @Nullable
+ public Set<File> getOldOutputs() {
+ return mOldOutputs;
+ }
+
+ @Nullable
+ public Set<File> getOldInputs() {
+ return mOldInputs;
+ }
+
+ public void cleanDependencies() {
+ if (mDependencyFiles != null) {
+ for (DependencyFile depFile : mDependencyFiles) {
+ depFile.getFile().delete();
+ }
+ }
+ }
+
+ @NonNull
+ public List<File> getSourceFolders() {
+ return mSourceFolders;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/build/RenderScriptProcessor.java b/sdklib/src/main/java/com/android/sdklib/build/RenderScriptProcessor.java
new file mode 100644
index 0000000..b5219b9
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/build/RenderScriptProcessor.java
@@ -0,0 +1,399 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.sdklib.build;
+
+import static com.android.SdkConstants.EXT_BC;
+import static com.android.SdkConstants.FN_RENDERSCRIPT_V8_JAR;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.BuildToolInfo;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Compiles Renderscript files.
+ */
+public class RenderScriptProcessor {
+
+ // ABI list, as pairs of (android-ABI, toolchain-ABI)
+ private static final class Abi {
+
+ @NonNull
+ private final String mDevice;
+ @NonNull
+ private final String mToolchain;
+ @NonNull
+ private final BuildToolInfo.PathId mLinker;
+ @NonNull
+ private final String[] mLinkerArgs;
+
+ Abi(@NonNull String device,
+ @NonNull String toolchain,
+ @NonNull BuildToolInfo.PathId linker,
+ @NonNull String... linkerArgs) {
+
+ mDevice = device;
+ mToolchain = toolchain;
+ mLinker = linker;
+ mLinkerArgs = linkerArgs;
+ }
+ }
+
+ private static final Abi[] ABIS = {
+ new Abi("armeabi-v7a", "armv7-none-linux-gnueabi", BuildToolInfo.PathId.LD_ARM,
+ "-dynamic-linker", "/system/bin/linker", "-X", "-m", "armelf_linux_eabi"),
+ new Abi("mips", "mipsel-unknown-linux", BuildToolInfo.PathId.LD_MIPS, "-EL"),
+ new Abi("x86", "i686-unknown-linux", BuildToolInfo.PathId.LD_X86, "-m", "elf_i386") };
+
+ public static final String RS_DEPS = "rsDeps";
+
+ @NonNull
+ private final List<File> mInputs;
+
+ @NonNull
+ private final List<File> mImportFolders;
+
+ @NonNull
+ private final File mBuildFolder;
+
+ @NonNull
+ private final File mSourceOutputDir;
+
+ @NonNull
+ private final File mResOutputDir;
+
+ @NonNull
+ private final File mObjOutputDir;
+
+ @NonNull
+ private final File mLibOutputDir;
+
+ @NonNull
+ private final BuildToolInfo mBuildToolInfo;
+
+ private final int mTargetApi;
+
+ private final boolean mDebugBuild;
+
+ private final int mOptimLevel;
+
+ private final boolean mSupportMode;
+
+ private final File mRsLib;
+ private final File mLibClCore;
+
+ public interface CommandLineLauncher {
+ void launch(
+ @NonNull File executable,
+ @NonNull List<String> arguments,
+ @NonNull Map<String, String> envVariableMap)
+ throws IOException, InterruptedException;
+ }
+
+ public RenderScriptProcessor(
+ @NonNull List<File> inputs,
+ @NonNull List<File> importFolders,
+ @NonNull File buildFolder,
+ @NonNull File sourceOutputDir,
+ @NonNull File resOutputDir,
+ @NonNull File objOutputDir,
+ @NonNull File libOutputDir,
+ @NonNull BuildToolInfo buildToolInfo,
+ int targetApi,
+ boolean debugBuild,
+ int optimLevel,
+ boolean supportMode) {
+ mInputs = inputs;
+ mImportFolders = importFolders;
+ mBuildFolder = buildFolder;
+ mSourceOutputDir = sourceOutputDir;
+ mResOutputDir = resOutputDir;
+ mObjOutputDir = objOutputDir;
+ mLibOutputDir = libOutputDir;
+ mBuildToolInfo = buildToolInfo;
+ mTargetApi = targetApi;
+ mDebugBuild = debugBuild;
+ mOptimLevel = optimLevel;
+ mSupportMode = supportMode;
+
+ if (supportMode) {
+ File rs = new File(mBuildToolInfo.getLocation(), "renderscript");
+ mRsLib = new File(rs, "lib");
+ mLibClCore = new File(mRsLib, "libclcore.bc");
+ } else {
+ mLibClCore = null;
+ mRsLib = null;
+ }
+ }
+
+ public void cleanOldOutput(@Nullable Collection<File> oldOutputs) {
+ if (oldOutputs != null) {
+ // the old output collections contains the bc and .java files that could be
+ // in a folder shared with other output files, so it's useful to delete
+ // those only.
+
+ for (File file : oldOutputs) {
+ file.delete();
+ }
+ }
+
+ // however .o and .so from support mode are in their own folder so we delete the
+ // content of those folders directly.
+ deleteFolder(mObjOutputDir);
+ deleteFolder(mLibOutputDir);
+ }
+
+ public static File getSupportJar(String buildToolsFolder) {
+ return new File(buildToolsFolder, "renderscript/lib/" + FN_RENDERSCRIPT_V8_JAR);
+ }
+
+ public static File getSupportNativeLibFolder(String buildToolsFolder) {
+ File rs = new File(buildToolsFolder, "renderscript");
+ File lib = new File(rs, "lib");
+ return new File(lib, "packaged");
+ }
+
+ public void build(@NonNull CommandLineLauncher launcher)
+ throws IOException, InterruptedException {
+
+ // get the env var
+ Map<String, String> env = Maps.newHashMap();
+ if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) {
+ env.put("DYLD_LIBRARY_PATH", mBuildToolInfo.getLocation().getAbsolutePath());
+ } else if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_LINUX) {
+ env.put("LD_LIBRARY_PATH", mBuildToolInfo.getLocation().getAbsolutePath());
+ }
+
+ doMainCompilation(launcher, env);
+
+ if (mSupportMode) {
+ createSupportFiles(launcher, env);
+ }
+ }
+
+ private void doMainCompilation(@NonNull CommandLineLauncher launcher,
+ @NonNull Map<String, String> env)
+ throws IOException, InterruptedException {
+ if (mInputs.isEmpty()) {
+ return;
+ }
+
+ String renderscript = mBuildToolInfo.getPath(BuildToolInfo.PathId.LLVM_RS_CC);
+ if (renderscript == null || !new File(renderscript).isFile()) {
+ throw new IllegalStateException(BuildToolInfo.PathId.LLVM_RS_CC + " is missing");
+ }
+
+ String rsPath = mBuildToolInfo.getPath(BuildToolInfo.PathId.ANDROID_RS);
+ String rsClangPath = mBuildToolInfo.getPath(BuildToolInfo.PathId.ANDROID_RS_CLANG);
+
+ // the renderscript compiler doesn't expect the top res folder,
+ // but the raw folder directly.
+ File rawFolder = new File(mResOutputDir, SdkConstants.FD_RES_RAW);
+
+ // compile all the files in a single pass
+ ArrayList<String> command = Lists.newArrayListWithExpectedSize(25);
+
+ // Due to a device side bug, let's not enable this at this time.
+// if (mDebugBuild) {
+// command.add("-g");
+// }
+
+ command.add("-O");
+ command.add(Integer.toString(mOptimLevel));
+
+ // add all import paths
+ command.add("-I");
+ command.add(rsPath);
+ command.add("-I");
+ command.add(rsClangPath);
+
+ for (File importPath : mImportFolders) {
+ if (importPath.isDirectory()) {
+ command.add("-I");
+ command.add(importPath.getAbsolutePath());
+ }
+ }
+
+ command.add("-d");
+ command.add(new File(mBuildFolder, RS_DEPS).getAbsolutePath());
+ command.add("-MD");
+
+ if (mSupportMode) {
+ command.add("-rs-package-name=android.support.v8.renderscript");
+ }
+
+ // source output
+ command.add("-p");
+ command.add(mSourceOutputDir.getAbsolutePath());
+
+ // res output
+ command.add("-o");
+ command.add(rawFolder.getAbsolutePath());
+
+ command.add("-target-api");
+ int targetApi = mTargetApi < 11 ? 11 : mTargetApi;
+ targetApi = (mSupportMode && targetApi < 18) ? 18 : targetApi;
+ command.add(Integer.toString(targetApi));
+
+ // input files
+ for (File sourceFile : mInputs) {
+ command.add(sourceFile.getAbsolutePath());
+ }
+
+ launcher.launch(new File(renderscript), command, env);
+ }
+
+ private void createSupportFiles(@NonNull CommandLineLauncher launcher,
+ @NonNull Map<String, String> env) throws IOException, InterruptedException {
+ // get the generated BC files.
+ File rawFolder = new File(mResOutputDir, SdkConstants.FD_RES_RAW);
+
+ SourceSearcher searcher = new SourceSearcher(Collections.singletonList(rawFolder), EXT_BC);
+ FileGatherer fileGatherer = new FileGatherer();
+ searcher.search(fileGatherer);
+
+ for (File bcFile : fileGatherer.getFiles()) {
+ String name = bcFile.getName();
+ String objName = name.replaceAll("\\.bc", ".o");
+ String soName = "librs." + name.replaceAll("\\.bc", ".so");
+
+ for (Abi abi : ABIS) {
+ File objFile = createSupportObjFile(bcFile, abi, objName, launcher, env);
+ createSupportLibFile(objFile, abi, soName, launcher, env);
+ }
+ }
+ }
+
+ private File createSupportObjFile(
+ @NonNull File bcFile,
+ @NonNull Abi abi,
+ @NonNull String objName,
+ @NonNull CommandLineLauncher launcher,
+ @NonNull Map<String, String> env) throws IOException, InterruptedException {
+
+
+ // make sure the dest folder exist
+ File abiFolder = new File(mObjOutputDir, abi.mDevice);
+ if (!abiFolder.isDirectory() && !abiFolder.mkdirs()) {
+ throw new IOException("Unable to create dir " + abiFolder.getAbsolutePath());
+ }
+
+ File exe = new File(mBuildToolInfo.getPath(BuildToolInfo.PathId.BCC_COMPAT));
+
+ List<String> args = Lists.newArrayListWithExpectedSize(10);
+
+ args.add("-O" + Integer.toString(mOptimLevel));
+
+ File outFile = new File(abiFolder, objName);
+ args.add("-o");
+ args.add(outFile.getAbsolutePath());
+
+ args.add("-fPIC");
+ args.add("-shared");
+
+ args.add("-rt-path");
+ args.add(mLibClCore.getAbsolutePath());
+
+ args.add("-mtriple");
+ args.add(abi.mToolchain);
+
+ args.add(bcFile.getAbsolutePath());
+
+ launcher.launch(exe, args, env);
+
+ return outFile;
+ }
+
+ private void createSupportLibFile(
+ @NonNull File objFile,
+ @NonNull Abi abi,
+ @NonNull String soName,
+ @NonNull CommandLineLauncher launcher,
+ @NonNull Map<String, String> env) throws IOException, InterruptedException {
+
+ // make sure the dest folder exist
+ File abiFolder = new File(mLibOutputDir, abi.mDevice);
+ if (!abiFolder.isDirectory() && !abiFolder.mkdirs()) {
+ throw new IOException("Unable to create dir " + abiFolder.getAbsolutePath());
+ }
+
+ File intermediatesFolder = new File(mRsLib, "intermediates");
+ File intermediatesAbiFolder = new File(intermediatesFolder, abi.mDevice);
+ File packagedFolder = new File(mRsLib, "packaged");
+ File packagedAbiFolder = new File(packagedFolder, abi.mDevice);
+
+ List<String> args = Lists.newArrayListWithExpectedSize(25);
+
+ args.add("--eh-frame-hdr");
+ Collections.addAll(args, abi.mLinkerArgs);
+ args.add("-shared");
+ args.add("-Bsymbolic");
+ args.add("-z");
+ args.add("noexecstack");
+ args.add("-z");
+ args.add("relro");
+ args.add("-z");
+ args.add("now");
+
+ File outFile = new File(abiFolder, soName);
+ args.add("-o");
+ args.add(outFile.getAbsolutePath());
+
+ args.add("-L" + intermediatesAbiFolder.getAbsolutePath());
+ args.add("-L" + packagedAbiFolder.getAbsolutePath());
+
+ args.add("-soname");
+ args.add(soName);
+
+ args.add(objFile.getAbsolutePath());
+ args.add(new File(intermediatesAbiFolder, "libcompiler_rt.a").getAbsolutePath());
+
+ args.add("-lRSSupport");
+ args.add("-lm");
+ args.add("-lc");
+
+ File exe = new File(mBuildToolInfo.getPath(abi.mLinker));
+
+ launcher.launch(exe, args, env);
+ }
+
+ protected static void deleteFolder(File folder) {
+ File[] files = folder.listFiles();
+ if (files != null && files.length > 0) {
+ for (File file : files) {
+ if (file.isDirectory()) {
+ deleteFolder(file);
+ } else {
+ file.delete();
+ }
+ }
+ }
+
+ folder.delete();
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/build/SourceSearcher.java b/sdklib/src/main/java/com/android/sdklib/build/SourceSearcher.java
new file mode 100644
index 0000000..e8356a6
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/build/SourceSearcher.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.build;
+
+import com.android.annotations.NonNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Class to search for source files (by extension) in a set of source folders.
+ */
+public class SourceSearcher {
+
+ @NonNull
+ private final List<File> mSourceFolders;
+
+ @NonNull
+ private final String[] mExtensions;
+
+ public interface SourceFileProcessor {
+ void processFile(@NonNull File sourceFile, @NonNull String extension) throws IOException;
+ }
+
+ public SourceSearcher(@NonNull List<File> sourceFolders, @NonNull String... extensions) {
+ mSourceFolders = sourceFolders;
+ mExtensions = extensions;
+ }
+
+ public void search(@NonNull SourceFileProcessor processor)
+ throws IOException {
+ for (File file : mSourceFolders) {
+ processFile(file, processor);
+ }
+ }
+
+ private void processFile(@NonNull final File file, @NonNull final SourceFileProcessor processor)
+ throws IOException {
+ if (file.isFile()) {
+ // get the extension of the file.
+ String ext = checkExtension(file);
+ if (ext != null) {
+ processor.processFile(file, ext);
+ }
+ } else if (file.isDirectory()) {
+ File[] children = file.listFiles();
+ if (children != null) {
+ for (File child : children) {
+ processFile(child, processor);
+ }
+ }
+ }
+ }
+
+ /**
+ * Return null if the extension don't match or the extension if there is a match.
+ *
+ * if there's no extension to look for, then returns an empty string.
+ *
+ * @param file the file to check
+ * @return a string if match, null otherwise
+ */
+ private String checkExtension(@NonNull File file) {
+ if (mExtensions.length == 0) {
+ return "";
+ }
+
+ String filename = file.getName();
+ int pos = filename.indexOf('.');
+ if (pos != -1) {
+ String extension = filename.substring(pos + 1);
+ for (String ext : mExtensions) {
+ if (ext.equalsIgnoreCase(extension)) {
+ return ext;
+ }
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/devices/Device.java b/sdklib/src/main/java/com/android/sdklib/devices/Device.java
index 36f9480..8078d20 100644
--- a/sdklib/src/main/java/com/android/sdklib/devices/Device.java
+++ b/sdklib/src/main/java/com/android/sdklib/devices/Device.java
@@ -38,6 +38,10 @@
@NonNull
private final String mName;
+ /** ID of the device */
+ @NonNull
+ private final String mId;
+
/** Manufacturer of the device */
@NonNull
private final String mManufacturer;
@@ -59,16 +63,42 @@
private final State mDefaultState;
/**
- * Returns the name of the {@link Device}.
+ * Returns the name of the {@link Device}. This is intended to be displayed by the user and
+ * can vary over time. For a stable internal name of the device, use {@link #getId} instead.
+ *
+ * @deprecated Use {@link #getId()} or {@link #getDisplayName()} instead based on whether
+ * a stable identifier or a user visible name is needed
+ * @return The name of the {@link Device}.
+ */
+ @NonNull
+ @Deprecated
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Returns the user visible name of the {@link Device}. This is intended to be displayed by the
+ * user and can vary over time. For a stable internal name of the device, use {@link #getId}
+ * instead.
*
* @return The name of the {@link Device}.
*/
@NonNull
- public String getName() {
+ public String getDisplayName() {
return mName;
}
/**
+ * Returns the id of the {@link Device}.
+ *
+ * @return The id of the {@link Device}.
+ */
+ @NonNull
+ public String getId() {
+ return mId;
+ }
+
+ /**
* Returns the manufacturer of the {@link Device}.
*
* @return The name of the manufacturer of the {@link Device}.
@@ -206,6 +236,7 @@
public static class Builder {
private String mName;
+ private String mId;
private String mManufacturer;
private final List<Software> mSoftware = new ArrayList<Software>();
private final List<State> mState = new ArrayList<State>();
@@ -215,7 +246,8 @@
public Builder() { }
public Builder(Device d) {
- mName = d.getName();
+ mName = d.getDisplayName();
+ mId = d.getId();
mManufacturer = d.getManufacturer();
for (Software s : d.getAllSoftware()) {
mSoftware.add(s.deepCopy());
@@ -233,6 +265,10 @@
mName = name;
}
+ public void setId(@NonNull String id) {
+ mId = id;
+ }
+
public void setManufacturer(@NonNull String manufacturer) {
mManufacturer = manufacturer;
}
@@ -283,6 +319,10 @@
throw generateBuildException("Device states not configured");
}
+ if (mId == null) {
+ mId = mName;
+ }
+
if (mMeta == null) {
mMeta = new Meta();
}
@@ -315,6 +355,7 @@
private Device(Builder b) {
mName = b.mName;
+ mId = b.mId;
mManufacturer = b.mManufacturer;
mSoftware = Collections.unmodifiableList(b.mSoftware);
mState = Collections.unmodifiableList(b.mState);
@@ -331,7 +372,7 @@
return false;
}
Device d = (Device) o;
- return mName.equals(d.getName())
+ return mName.equals(d.getDisplayName())
&& mManufacturer.equals(d.getManufacturer())
&& mSoftware.equals(d.getAllSoftware())
&& mState.equals(d.getAllStates())
diff --git a/sdklib/src/main/java/com/android/sdklib/devices/DeviceManager.java b/sdklib/src/main/java/com/android/sdklib/devices/DeviceManager.java
index 8c36b87..61fa17a 100644
--- a/sdklib/src/main/java/com/android/sdklib/devices/DeviceManager.java
+++ b/sdklib/src/main/java/com/android/sdklib/devices/DeviceManager.java
@@ -37,6 +37,7 @@
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
+import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -163,14 +164,14 @@
}
@Nullable
- public Device getDevice(@NonNull String name, @NonNull String manufacturer) {
+ public Device getDevice(@NonNull String id, @NonNull String manufacturer) {
initDevicesLists();
for (List<?> devices :
new List<?>[] { mUserDevices, mDefaultDevices, mVendorDevices } ) {
if (devices != null) {
@SuppressWarnings("unchecked") List<Device> devicesList = (List<Device>) devices;
for (Device d : devicesList) {
- if (d.getName().equals(name) && d.getManufacturer().equals(manufacturer)) {
+ if (d.getId().equals(id) && d.getManufacturer().equals(manufacturer)) {
return d;
}
}
@@ -245,16 +246,15 @@
if (mVendorDevices == null) {
mVendorDevices = new ArrayList<Device>();
- if (mOsSdkPath != null) {
- // Load devices from tools folder
- File toolsDevices = new File(mOsSdkPath,
- SdkConstants.OS_SDK_TOOLS_LIB_FOLDER +
- File.separator +
- SdkConstants.FN_DEVICES_XML);
- if (toolsDevices.isFile()) {
- mVendorDevices.addAll(loadDevices(toolsDevices));
- }
+ // Load builtin devices
+ try {
+ InputStream stream = DeviceManager.class.getResourceAsStream("nexus.xml");
+ mVendorDevices.addAll(DeviceParser.parse(stream));
+ } catch (Exception e) {
+ mLog.error(e, null, "Could not load devices");
+ }
+ if (mOsSdkPath != null) {
// Load devices from vendor extras
File extrasFolder = new File(mOsSdkPath, SdkConstants.FD_EXTRAS);
List<File> deviceDirs = getExtraDirs(extrasFolder);
@@ -345,7 +345,7 @@
Iterator<Device> it = mUserDevices.iterator();
while (it.hasNext()) {
Device userDevice = it.next();
- if (userDevice.getName().equals(d.getName())
+ if (userDevice.getId().equals(d.getId())
&& userDevice.getManufacturer().equals(d.getManufacturer())) {
it.remove();
notifyListeners();
@@ -459,7 +459,7 @@
}
}
props.put(AvdManager.AVD_INI_DEVICE_HASH, Integer.toString(d.hashCode()));
- props.put(AvdManager.AVD_INI_DEVICE_NAME, d.getName());
+ props.put(AvdManager.AVD_INI_DEVICE_NAME, d.getId());
props.put(AvdManager.AVD_INI_DEVICE_MANUFACTURER, d.getManufacturer());
return props;
}
diff --git a/sdklib/src/main/java/com/android/sdklib/devices/DeviceParser.java b/sdklib/src/main/java/com/android/sdklib/devices/DeviceParser.java
index 511fed6..7c39a97 100644
--- a/sdklib/src/main/java/com/android/sdklib/devices/DeviceParser.java
+++ b/sdklib/src/main/java/com/android/sdklib/devices/DeviceParser.java
@@ -123,6 +123,8 @@
mDevices.add(mBuilder.build());
} else if (DeviceSchema.NODE_NAME.equals(localName)) {
mBuilder.setName(getString(mStringAccumulator));
+ } else if (DeviceSchema.NODE_ID.equals(localName)) {
+ mBuilder.setId(getString(mStringAccumulator));
} else if (DeviceSchema.NODE_MANUFACTURER.equals(localName)) {
mBuilder.setManufacturer(getString(mStringAccumulator));
} else if (DeviceSchema.NODE_META.equals(localName)) {
diff --git a/sdklib/src/main/java/com/android/sdklib/devices/DeviceWriter.java b/sdklib/src/main/java/com/android/sdklib/devices/DeviceWriter.java
index f475ba3..df70c9c 100644
--- a/sdklib/src/main/java/com/android/sdklib/devices/DeviceWriter.java
+++ b/sdklib/src/main/java/com/android/sdklib/devices/DeviceWriter.java
@@ -75,9 +75,17 @@
root.appendChild(deviceNode);
Element name = doc.createElement(PREFIX + DeviceSchema.NODE_NAME);
- name.appendChild(doc.createTextNode(device.getName()));
+ String displayName = device.getDisplayName();
+ name.appendChild(doc.createTextNode(displayName));
deviceNode.appendChild(name);
+ String deviceId = device.getId();
+ if (!deviceId.equals(displayName)) {
+ Element id = doc.createElement(PREFIX + DeviceSchema.NODE_ID);
+ id.appendChild(doc.createTextNode(deviceId));
+ deviceNode.appendChild(id);
+ }
+
Element manufacturer = doc.createElement(PREFIX + DeviceSchema.NODE_MANUFACTURER);
manufacturer.appendChild(doc.createTextNode(device.getManufacturer()));
deviceNode.appendChild(manufacturer);
diff --git a/sdklib/src/main/java/com/android/sdklib/devices/devices.xml b/sdklib/src/main/java/com/android/sdklib/devices/devices.xml
index 9eacec2..f2f75c7 100644
--- a/sdklib/src/main/java/com/android/sdklib/devices/devices.xml
+++ b/sdklib/src/main/java/com/android/sdklib/devices/devices.xml
@@ -4,9 +4,8 @@
xmlns:d="http://schemas.android.com/sdk/devices/1">
<d:device>
- <d:name>
- 2.7in QVGA
- </d:name>
+ <d:name>2.7" QVGA</d:name>
+ <d:id>2.7in QVGA</d:id>
<d:manufacturer>
Generic
</d:manufacturer>
@@ -93,9 +92,8 @@
</d:device>
<d:device>
- <d:name>
- 2.7in QVGA slider
- </d:name>
+ <d:name>2.7" QVGA slider</d:name>
+ <d:id>2.7in QVGA slider</d:id>
<d:manufacturer>
Generic
</d:manufacturer>
@@ -188,9 +186,8 @@
</d:device>
<d:device>
- <d:name>
- 3.2in HVGA slider (ADP1)
- </d:name>
+ <d:name>3.2" HVGA slider (ADP1)</d:name>
+ <d:id>3.2in HVGA slider (ADP1)</d:id>
<d:manufacturer>
Generic
</d:manufacturer>
@@ -283,9 +280,8 @@
</d:device>
<d:device>
- <d:name>
- 3.2in QVGA (ADP2)
- </d:name>
+ <d:name>3.2" QVGA (ADP2)</d:name>
+ <d:id>3.2in QVGA (ADP2)</d:id>
<d:manufacturer>
Generic
</d:manufacturer>
@@ -372,9 +368,8 @@
</d:device>
<d:device>
- <d:name>
- 3.3in WQVGA
- </d:name>
+ <d:name>3.3" WQVGA</d:name>
+ <d:id>3.3in WQVGA</d:id>
<d:manufacturer>
Generic
</d:manufacturer>
@@ -460,9 +455,8 @@
</d:state>
</d:device>
<d:device>
- <d:name>
- 3.4in WQVGA
- </d:name>
+ <d:name>3.4in WQVGA</d:name>
+ <d:id>3.4" WQVGA</d:id>
<d:manufacturer>
Generic
</d:manufacturer>
@@ -549,9 +543,8 @@
</d:device>
<d:device>
- <d:name>
- 3.7in WVGA (Nexus One)
- </d:name>
+ <d:name>3.7" WVGA (Nexus One)</d:name>
+ <d:id>3.7in WVGA (Nexus One)</d:id>
<d:manufacturer>
Generic
</d:manufacturer>
@@ -638,9 +631,8 @@
</d:device>
<d:device>
- <d:name>
- 3.7 FWVGA slider
- </d:name>
+ <d:name>3.7" FWVGA slider</d:name>
+ <d:id>3.7 FWVGA slider</d:id>
<d:manufacturer>
Generic
</d:manufacturer>
@@ -733,9 +725,8 @@
</d:device>
<d:device>
- <d:name>
- 4in WVGA (Nexus S)
- </d:name>
+ <d:name>4" WVGA (Nexus S)</d:name>
+ <d:id>4in WVGA (Nexus S)</d:id>
<d:manufacturer>
Generic
</d:manufacturer>
@@ -822,9 +813,8 @@
</d:device>
<d:device>
- <d:name>
- 4.65in 720p (Galaxy Nexus)
- </d:name>
+ <d:name>4.65" 720p (Galaxy Nexus)</d:name>
+ <d:id>4.65in 720p (Galaxy Nexus)</d:id>
<d:manufacturer>
Generic
</d:manufacturer>
@@ -960,9 +950,8 @@
</d:device>
<d:device>
- <d:name>
- 4.7in WXGA
- </d:name>
+ <d:name>4.7" WXGA</d:name>
+ <d:id>4.7in WXGA</d:id>
<d:manufacturer>
Generic
</d:manufacturer>
@@ -1049,9 +1038,8 @@
</d:device>
<d:device>
- <d:name>
- 5.1in WVGA
- </d:name>
+ <d:name>5.1" WVGA</d:name>
+ <d:id>5.1in WVGA</d:id>
<d:manufacturer>
Generic
</d:manufacturer>
@@ -1138,9 +1126,8 @@
</d:device>
<d:device>
- <d:name>
- 5.4in FWVGA
- </d:name>
+ <d:name>5.4" FWVGA</d:name>
+ <d:id>5.4in FWVGA</d:id>
<d:manufacturer>
Generic
</d:manufacturer>
@@ -1227,9 +1214,8 @@
</d:device>
<d:device>
- <d:name>
- 7in WSVGA (Tablet)
- </d:name>
+ <d:name>7" WSVGA (Tablet)</d:name>
+ <d:id>7in WSVGA (Tablet)</d:id>
<d:manufacturer>
Generic
</d:manufacturer>
@@ -1317,9 +1303,8 @@
<d:device>
- <d:name>
- 10.1in WXGA (Tablet)
- </d:name>
+ <d:name>10.1" WXGA (Tablet)</d:name>
+ <d:id>10.1in WXGA (Tablet)</d:id>
<d:manufacturer>
Generic
</d:manufacturer>
diff --git a/sdklib/src/main/java/com/android/sdklib/devices/nexus.xml b/sdklib/src/main/java/com/android/sdklib/devices/nexus.xml
new file mode 100644
index 0000000..24fbbf4
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/devices/nexus.xml
@@ -0,0 +1,796 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<d:devices xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:d="http://schemas.android.com/sdk/devices/1">
+
+ <d:device>
+ <d:name>Nexus One</d:name>
+ <d:manufacturer>Google</d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>normal</d:screen-size>
+ <d:diagonal-length>3.7</d:diagonal-length>
+ <d:pixel-density>hdpi</d:pixel-density>
+ <d:screen-ratio>long</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>480</d:x-dimension>
+ <d:y-dimension>800</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>254</d:xdpi>
+ <d:ydpi>254</d:ydpi>
+ <d:touch>
+ <d:multitouch>basic</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Wifi
+ Bluetooth
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Compass
+ GPS
+ LightSensor
+ ProximitySensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>back</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>true</d:flash>
+ </d:camera>
+ <d:keyboard>nokeys</d:keyboard>
+ <d:nav>trackball</d:nav>
+ <d:ram unit="MiB">512</d:ram>
+ <d:buttons>hard</d:buttons>
+ <d:internal-storage unit="MiB">503</d:internal-storage>
+ <d:removable-storage unit="MiB">0</d:removable-storage>
+ <d:cpu>Qualcomm Scorpion</d:cpu>
+ <d:gpu>Qualcomm Adreno 200</d:gpu>
+ <d:abi>
+ armeabi-v7a
+ armeabi
+ </d:abi>
+ <d:dock> </d:dock>
+ <d:power-type>plugged-in</d:power-type>
+ </d:hardware>
+ <d:software>
+ <d:api-level>7-10</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles> </d:bluetooth-profiles>
+ <d:gl-version>2.0</d:gl-version>
+ <d:gl-extensions>
+ </d:gl-extensions>
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+ <d:state name="Portrait" default="true">
+ <d:description>The phone in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ <d:state name="Landscape">
+ <d:description>The phone in landscape view</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ </d:device>
+ <d:device>
+ <d:name>Nexus S</d:name>
+ <d:manufacturer>Google</d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>normal</d:screen-size>
+ <d:diagonal-length>4</d:diagonal-length>
+ <d:pixel-density>hdpi</d:pixel-density>
+ <d:screen-ratio>long</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>480</d:x-dimension>
+ <d:y-dimension>800</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>235</d:xdpi>
+ <d:ydpi>235</d:ydpi>
+ <d:touch>
+ <d:multitouch>jazz-hands</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Wifi
+ Bluetooth
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Compass
+ GPS
+ Gyroscope
+ LightSensor
+ ProximitySensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>back</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>true</d:flash>
+ </d:camera>
+ <d:camera>
+ <d:location>front</d:location>
+ <d:autofocus>false</d:autofocus>
+ <d:flash>false</d:flash>
+ </d:camera>
+ <d:keyboard>nokeys</d:keyboard>
+ <d:nav>nonav</d:nav>
+ <d:ram unit="KiB">351428</d:ram>
+ <d:buttons>hard</d:buttons>
+ <d:internal-storage unit="MiB">503</d:internal-storage>
+ <d:removable-storage unit="MiB">0</d:removable-storage>
+ <d:cpu>Samsung Exynos 3110</d:cpu>
+ <d:gpu>PowerVR SGX 540</d:gpu>
+ <d:abi>
+ armeabi-v7a
+ armeabi
+ </d:abi>
+ <d:dock> </d:dock>
+ <d:power-type>plugged-in</d:power-type>
+ </d:hardware>
+ <d:software>
+ <d:api-level>9-16</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles> </d:bluetooth-profiles>
+ <d:gl-version>2.0</d:gl-version>
+ <d:gl-extensions>
+ GL_EXT_debug_marker
+ GL_OES_rgb8_rgba8
+ GL_OES_depth24
+ GL_OES_vertex_half_float
+ GL_OES_texture_float
+ GL_OES_texture_half_float
+ GL_OES_element_index_uint
+ GL_OES_mapbuffer
+ GL_OES_fragment_precision_high
+ GL_OES_compressed_ETC1_RGB8_texture
+ GL_OES_EGL_image
+ GL_OES_EGL_image_external
+ GL_OES_required_internalformat
+ GL_OES_depth_texture
+ GL_OES_get_program_binary
+ GL_OES_packed_depth_stencil
+ GL_OES_standard_derivatives
+ GL_OES_vertex_array_object
+ GL_OES_egl_sync
+ GL_EXT_multi_draw_arrays
+ GL_EXT_texture_format_BGRA8888
+ GL_EXT_discard_framebuffer
+ GL_EXT_shader_texture_lod
+ GL_IMG_shader_binary
+ GL_IMG_texture_compression_pvrtc
+ GL_IMG_texture_npot
+ GL_IMG_texture_format_BGRA8888
+ GL_IMG_read_format
+ GL_IMG_program_binary
+ GL_IMG_multisampled_render_to_texture
+ </d:gl-extensions>
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+ <d:state name="Portrait" default="true">
+ <d:description>The phone in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ <d:state name="Landscape">
+ <d:description>The phone in landscape view</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ </d:device>
+
+ <d:device>
+ <d:name>Galaxy Nexus</d:name>
+ <d:manufacturer>Google</d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>normal</d:screen-size>
+ <d:diagonal-length>4.65</d:diagonal-length> <!-- In inches -->
+ <d:pixel-density>xhdpi</d:pixel-density>
+ <d:screen-ratio>long</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>720</d:x-dimension>
+ <d:y-dimension>1280</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>316</d:xdpi>
+ <d:ydpi>316</d:ydpi>
+ <d:touch>
+ <d:multitouch>jazz-hands</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Bluetooth
+ Wifi
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Barometer
+ Gyroscope
+ Compass
+ GPS
+ ProximitySensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>front</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>false</d:flash>
+ </d:camera>
+ <d:camera>
+ <d:location>back</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>true</d:flash>
+ </d:camera>
+ <d:keyboard>nokeys</d:keyboard>
+ <d:nav>nonav</d:nav>
+ <d:ram unit="GiB">1</d:ram>
+ <d:buttons>soft</d:buttons>
+ <d:internal-storage unit="GiB">16</d:internal-storage>
+ <d:removable-storage unit="KiB"></d:removable-storage>
+ <d:cpu>OMAP 4460</d:cpu> <!-- cpu type (Tegra3) freeform -->
+ <d:gpu>PowerVR SGX540</d:gpu>
+ <d:abi>
+ armeabi
+ armeabi-v7a
+ </d:abi>
+ <!--dock (car, desk, tv, none)-->
+ <d:dock>
+ </d:dock>
+ <!-- plugged in (never, charge, always) -->
+ <d:power-type>battery</d:power-type>
+ </d:hardware>
+ <d:software>
+ <d:api-level>14-</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles>
+ HSP
+ HFP
+ SPP
+ A2DP
+ AVRCP
+ OPP
+ PBAP
+ GAVDP
+ AVDTP
+ HID
+ HDP
+ PAN
+ </d:bluetooth-profiles>
+ <d:gl-version>2.0</d:gl-version>
+ <!--
+ These can be gotten via
+ javax.microedition.khronos.opengles.GL10.glGetString(GL10.GL_EXTENSIONS);
+ -->
+ <d:gl-extensions>
+ GL_EXT_discard_framebuffer
+ GL_EXT_multi_draw_arrays
+ GL_EXT_shader_texture_lod
+ GL_EXT_texture_format_BGRA8888
+ GL_IMG_multisampled_render_to_texture
+ GL_IMG_program_binary
+ GL_IMG_read_format
+ GL_IMG_shader_binary
+ GL_IMG_texture_compression_pvrtc
+ GL_IMG_texture_format_BGRA8888
+ GL_IMG_texture_npot
+ GL_OES_compressed_ETC1_RGB8_texture
+ GL_OES_depth_texture
+ GL_OES_depth24
+ GL_OES_EGL_image
+ GL_OES_EGL_image_external
+ GL_OES_egl_sync
+ GL_OES_element_index_uint
+ GL_OES_fragment_precision_high
+ GL_OES_get_program_binary
+ GL_OES_mapbuffer
+ GL_OES_packed_depth_stencil
+ GL_OES_required_internalformat
+ GL_OES_rgb8_rgba8
+ GL_OES_standard_derivatives
+ GL_OES_texture_float
+ GL_OES_texture_half_float
+ GL_OES_vertex_array_object
+ GL_OES_vertex_half_float
+ </d:gl-extensions>
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+ <d:state name="Portrait" default="true">
+ <d:description>The phone in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ <d:state name="Landscape">
+ <d:description>The phone in landscape view</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ </d:device>
+
+ <d:device>
+ <d:name>Nexus 7 (2012)</d:name>
+ <d:id>Nexus 7</d:id>
+ <d:manufacturer>Google</d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>large</d:screen-size>
+ <d:diagonal-length>7.0</d:diagonal-length>
+ <d:pixel-density>tvdpi</d:pixel-density>
+ <d:screen-ratio>notlong</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>800</d:x-dimension>
+ <d:y-dimension>1280</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>195</d:xdpi>
+ <d:ydpi>200</d:ydpi>
+ <d:touch>
+ <d:multitouch>jazz-hands</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Wifi
+ Bluetooth
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Compass
+ GPS
+ Gyroscope
+ LightSensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>front</d:location>
+ <d:autofocus>false</d:autofocus>
+ <d:flash>false</d:flash>
+ </d:camera>
+ <d:keyboard>nokeys</d:keyboard>
+ <d:nav>nonav</d:nav>
+ <d:ram unit="GiB">1</d:ram>
+ <d:buttons>soft</d:buttons>
+ <d:internal-storage unit="GiB">8</d:internal-storage>
+ <d:removable-storage unit="MiB"> </d:removable-storage>
+ <d:cpu> Tegra3 </d:cpu>
+ <d:gpu> Tegra3 </d:gpu>
+ <d:abi>
+ armeabi-v7a
+ armeabi
+ </d:abi>
+ <d:dock> </d:dock>
+ <d:power-type>battery</d:power-type>
+ </d:hardware>
+
+ <d:software>
+ <d:api-level>16</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles> </d:bluetooth-profiles>
+ <d:gl-version>2.0</d:gl-version>
+ <d:gl-extensions> </d:gl-extensions>
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+
+ <d:state name="Portrait" default="true">
+ <d:description>The phone in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ <d:state name="Landscape">
+ <d:description>The phone in landscape view</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ </d:device>
+
+ <d:device>
+ <d:name>Nexus 4</d:name>
+ <d:manufacturer>Google</d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>normal</d:screen-size>
+ <d:diagonal-length>4.7</d:diagonal-length>
+ <d:pixel-density>xhdpi</d:pixel-density>
+ <d:screen-ratio>notlong</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>768</d:x-dimension>
+ <d:y-dimension>1280</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>320</d:xdpi>
+ <d:ydpi>320</d:ydpi>
+ <d:touch>
+ <d:multitouch>jazz-hands</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Wifi
+ Bluetooth
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Barometer
+ Compass
+ GPS
+ Gyroscope
+ LightSensor
+ ProximitySensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>back</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>true</d:flash>
+ </d:camera>
+ <d:camera>
+ <d:location>front</d:location>
+ <d:autofocus>false</d:autofocus>
+ <d:flash>false</d:flash>
+ </d:camera>
+ <d:keyboard>nokeys</d:keyboard>
+ <d:nav>nonav</d:nav>
+ <d:ram unit="KiB">1953125</d:ram>
+ <d:buttons>soft</d:buttons>
+ <d:internal-storage unit="KiB">7811891</d:internal-storage>
+ <d:removable-storage unit="MiB"></d:removable-storage>
+ <d:cpu>Qualcomm Snapdragon S4 Pro</d:cpu>
+ <d:gpu>Adreno 320</d:gpu>
+ <d:abi>
+ armeabi-v7a
+ armeabi
+ </d:abi>
+ <d:dock></d:dock>
+ <d:power-type>battery</d:power-type>
+ </d:hardware>
+ <d:software>
+ <d:api-level>16</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles></d:bluetooth-profiles>
+ <d:gl-version>2.0</d:gl-version>
+ <d:gl-extensions>GL_EXT_debug_marker GL_AMD_compressed_ATC_texture
+ GL_AMD_performance_monitor GL_AMD_program_binary_Z400 GL_EXT_robustness
+ GL_EXT_texture_format_BGRA8888 GL_EXT_texture_type_2_10_10_10_REV GL_NV_fence
+ GL_OES_compressed_ETC1_RGB8_texture GL_OES_depth_texture GL_OES_depth24
+ GL_OES_EGL_image GL_OES_EGL_image_external GL_OES_element_index_uint
+ GL_OES_fbo_render_mipmap GL_OES_fragment_precision_high GL_OES_get_program_binary
+ GL_OES_packed_depth_stencil GL_OES_rgb8_rgba8 GL_OES_standard_derivatives
+ GL_OES_texture_3D GL_OES_texture_float GL_OES_texture_half_float
+ GL_OES_texture_half_float_linear GL_OES_texture_npot GL_OES_vertex_half_float
+ GL_OES_vertex_type_10_10_10_2 GL_OES_vertex_array_object GL_QCOM_alpha_test
+ GL_QCOM_binning_control GL_QCOM_driver_control GL_QCOM_perfmon_global_mode
+ GL_QCOM_extended_get GL_QCOM_extended_get2 GL_QCOM_tiled_rendering
+ GL_QCOM_writeonly_rendering GL_EXT_sRGB
+ </d:gl-extensions>
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+ <d:state name="Portrait" default="true">
+ <d:description>The phone in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ <d:state name="Landscape">
+ <d:description>The phone in landscape view</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ </d:device>
+
+ <d:device>
+ <d:name>Nexus 10</d:name>
+ <d:manufacturer>Google</d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>xlarge</d:screen-size>
+ <d:diagonal-length>10.055</d:diagonal-length>
+ <d:pixel-density>xhdpi</d:pixel-density>
+ <d:screen-ratio>notlong</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>2560</d:x-dimension>
+ <d:y-dimension>1600</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>300</d:xdpi>
+ <d:ydpi>300</d:ydpi>
+ <d:touch>
+ <d:multitouch>jazz-hands</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Wifi
+ Bluetooth
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Barometer
+ Compass
+ GPS
+ Gyroscope
+ LightSensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>back</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>true</d:flash>
+ </d:camera>
+ <d:camera>
+ <d:location>front</d:location>
+ <d:autofocus>false</d:autofocus>
+ <d:flash>false</d:flash>
+ </d:camera>
+ <d:keyboard>nokeys</d:keyboard>
+ <d:nav>nonav</d:nav>
+ <d:ram unit="KiB">1953125</d:ram>
+ <d:buttons>soft</d:buttons>
+ <d:internal-storage unit="KiB">15623782</d:internal-storage>
+ <d:removable-storage unit="MiB"></d:removable-storage>
+ <d:cpu>Dual-core A15</d:cpu>
+ <d:gpu>Quad-core Mali T604</d:gpu>
+ <d:abi>
+ armeabi-v7a
+ armeabi
+ </d:abi>
+ <d:dock></d:dock>
+ <d:power-type>battery</d:power-type>
+ </d:hardware>
+ <d:software>
+ <d:api-level>16</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles></d:bluetooth-profiles>
+ <d:gl-version>2.0</d:gl-version>
+ <d:gl-extensions>GL_EXT_debug_marker GL_ARM_rgba8 GL_ARM_mali_shader_binary
+ GL_OES_depth24 GL_OES_depth_texture GL_OES_depth_texture_cube_map
+ GL_OES_packed_depth_stencil GL_OES_rgb8_rgba8 GL_EXT_read_format_bgra
+ GL_OES_compressed_paletted_texture GL_OES_compressed_ETC1_RGB8_texture
+ GL_OES_standard_derivatives GL_OES_EGL_image GL_OES_EGL_image_external
+ GL_OES_EGL_sync GL_OES_texture_npot GL_OES_vertex_half_float
+ GL_OES_required_internalformat GL_OES_vertex_array_object GL_OES_mapbuffer
+ GL_EXT_texture_format_BGRA8888 GL_EXT_texture_rg GL_EXT_texture_type_2_10_10_10_REV
+ GL_OES_fbo_render_mipmap GL_OES_element_index_uint GL_EXT_shadow_samplers
+ GL_EXT_occlusion_query_boolean GL_EXT_blend_minmax GL_EXT_discard_framebuffer
+ GL_OES_get_program_binary GL_OES_texture_3D GL_EXT_texture_storage
+ GL_EXT_multisampled_render_to_texture GL_OES_surfaceless_context
+ GL_ARM_mali_program_binary
+ </d:gl-extensions>
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+ <d:state name="Portrait">
+ <d:description>The phone in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ <d:state name="Landscape" default="true">
+ <d:description>The phone in landscape view</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ </d:device>
+
+ <d:device>
+ <d:name>Nexus 7</d:name>
+ <d:id>Nexus 7 2013</d:id>
+ <d:manufacturer>Google</d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>large</d:screen-size>
+ <d:diagonal-length>7.02</d:diagonal-length>
+ <d:pixel-density>xhdpi</d:pixel-density>
+ <d:screen-ratio>notlong</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>1200</d:x-dimension>
+ <d:y-dimension>1920</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>323</d:xdpi>
+ <d:ydpi>323</d:ydpi>
+ <d:touch>
+ <d:multitouch>jazz-hands</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Wifi
+ Bluetooth
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Compass
+ GPS
+ Gyroscope
+ LightSensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>back</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>false</d:flash>
+ </d:camera>
+ <d:camera>
+ <d:location>front</d:location>
+ <d:autofocus>false</d:autofocus>
+ <d:flash>false</d:flash>
+ </d:camera>
+ <d:keyboard>nokeys</d:keyboard>
+ <d:nav>nonav</d:nav>
+ <d:ram unit="GiB">2</d:ram>
+ <d:buttons>soft</d:buttons>
+ <d:internal-storage unit="GiB">32</d:internal-storage>
+ <d:removable-storage unit="MiB"> </d:removable-storage>
+ <d:cpu> Qualcomm Snapdragon S4 Pro, 1.5GHz </d:cpu>
+ <d:gpu> Adreno 320, 400MHz </d:gpu>
+ <d:abi>
+ armeabi-v7a
+ armeabi
+ </d:abi>
+ <d:dock> </d:dock>
+ <d:power-type>battery</d:power-type>
+ </d:hardware>
+
+ <d:software>
+ <d:api-level>18</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles> </d:bluetooth-profiles>
+ <d:gl-version>3.0</d:gl-version>
+ <d:gl-extensions>
+ GL_AMD_compressed_ATC_texture GL_AMD_performance_monitor
+ GL_AMD_program_binary_Z400 GL_EXT_debug_labelGL_EXT_debug_markerGL_EXT_robustness
+ GL_EXT_texture_format_BGRA8888 GL_EXT_texture_type_2_10_10_10_REV GL_NV_fence
+ GL_OES_compressed_ETC1_RGB8_texture GL_OES_depth_texture GL_OES_depth24
+ GL_OES_EGL_image GL_OES_EGL_image_external GL_OES_element_index_uint
+ GL_OES_fbo_render_mipmap GL_OES_fragment_precision_high GL_OES_get_program_binary
+ GL_OES_packed_depth_stencil GL_OES_depth_texture_cube_map GL_OES_rgb8_rgba8
+ GL_OES_standard_derivatives GL_OES_texture_3D GL_OES_texture_float
+ GL_OES_texture_half_float GL_OES_texture_half_float_linear GL_OES_texture_npot
+ GL_OES_vertex_half_float GL_OES_vertex_type_10_10_10_2 GL_OES_vertex_array_object
+ GL_QCOM_alpha_test GL_QCOM_binning_control GL_QCOM_driver_control
+ GL_QCOM_perfmon_global_mode GL_QCOM_extended_get GL_QCOM_extended_get2
+ GL_QCOM_tiled_rendering GL_QCOM_writeonly_rendering GL_EXT_sRGB
+ GL_EXT_texture_filter_anisotropic GL_EXT_color_buffer_float
+ GL_EXT_color_buffer_half_float
+ </d:gl-extensions>
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+
+ <d:state name="Portrait" default="true">
+ <d:description>The phone in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ <d:state name="Landscape">
+ <d:description>The phone in landscape view</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ </d:device>
+
+ <d:device>
+ <d:name>Nexus 5</d:name>
+ <d:id>Nexus 5</d:id>
+ <d:manufacturer>Google</d:manufacturer>
+ <d:hardware>
+ <d:screen>
+ <d:screen-size>normal</d:screen-size>
+ <d:diagonal-length>4.95</d:diagonal-length>
+ <d:pixel-density>xxhdpi</d:pixel-density>
+ <d:screen-ratio>notlong</d:screen-ratio>
+ <d:dimensions>
+ <d:x-dimension>1080</d:x-dimension>
+ <d:y-dimension>1920</d:y-dimension>
+ </d:dimensions>
+ <d:xdpi>445</d:xdpi>
+ <d:ydpi>445</d:ydpi>
+ <d:touch>
+ <d:multitouch>jazz-hands</d:multitouch>
+ <d:mechanism>finger</d:mechanism>
+ <d:screen-type>capacitive</d:screen-type>
+ </d:touch>
+ </d:screen>
+ <d:networking>
+ Wifi
+ Bluetooth
+ NFC
+ </d:networking>
+ <d:sensors>
+ Accelerometer
+ Barometer
+ Compass
+ GPS
+ Gyroscope
+ LightSensor
+ ProximitySensor
+ </d:sensors>
+ <d:mic>true</d:mic>
+ <d:camera>
+ <d:location>back</d:location>
+ <d:autofocus>true</d:autofocus>
+ <d:flash>true</d:flash>
+ </d:camera>
+ <d:camera>
+ <d:location>front</d:location>
+ <d:autofocus>false</d:autofocus>
+ <d:flash>false</d:flash>
+ </d:camera>
+ <d:keyboard>nokeys</d:keyboard>
+ <d:nav>nonav</d:nav>
+ <d:ram unit="GiB">2</d:ram>
+ <d:buttons>soft</d:buttons>
+ <d:internal-storage unit="GiB">16</d:internal-storage>
+ <d:removable-storage unit="MiB"></d:removable-storage>
+ <d:cpu>Snapdragon 800 (MSM8974)</d:cpu>
+ <d:gpu>Adreno 330</d:gpu>
+ <d:abi>
+ armeabi-v7a
+ armeabi
+ </d:abi>
+ <d:dock></d:dock>
+ <d:power-type>battery</d:power-type>
+ </d:hardware>
+ <d:software>
+ <d:api-level>19</d:api-level>
+ <d:live-wallpaper-support>true</d:live-wallpaper-support>
+ <d:bluetooth-profiles></d:bluetooth-profiles>
+ <d:gl-version>3.0</d:gl-version>
+ <d:gl-extensions>
+ GL_AMD_compressed_ATC_texture GL_AMD_performance_monitor GL_AMD_program_binary_Z400
+ GL_EXT_debug_label GL_EXT_debug_marker GL_EXT_discard_framebuffer
+ GL_EXT_robustness GL_EXT_texture_format_BGRA8888 GL_EXT_texture_type_2_10_10_10_REV
+ GL_NV_fence GL_OES_compressed_ETC1_RGB8_texture GL_OES_depth_texture GL_OES_depth24
+ GL_OES_EGL_image GL_OES_EGL_image_external GL_OES_element_index_uint
+ GL_OES_fbo_render_mipmap GL_OES_fragment_precision_high GL_OES_get_program_binary
+ GL_OES_packed_depth_stencil GL_OES_depth_texture_cube_map GL_OES_rgb8_rgba8
+ GL_OES_standard_derivatives GL_OES_texture_3D GL_OES_texture_float
+ GL_OES_texture_half_float GL_OES_texture_half_float_linear GL_OES_texture_npot
+ GL_OES_vertex_half_float GL_OES_vertex_type_10_10_10_2 GL_OES_vertex_array_object
+ GL_QCOM_alpha_test GL_QCOM_binning_control GL_QCOM_driver_control
+ GL_QCOM_perfmon_global_mode GL_QCOM_extended_get GL_QCOM_extended_get2
+ GL_QCOM_tiled_rendering GL_QCOM_writeonly_rendering GL_EXT_sRGB
+ GL_EXT_texture_filter_anisotropic GL_EXT_color_buffer_float
+ GL_EXT_color_buffer_half_float GL_EXT_disjoint_timer_query
+ </d:gl-extensions>
+ <d:status-bar>true</d:status-bar>
+ </d:software>
+ <d:state name="Portrait" default="true">
+ <d:description>The phone in portrait view</d:description>
+ <d:screen-orientation>port</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ <d:state name="Landscape">
+ <d:description>The phone in landscape view</d:description>
+ <d:screen-orientation>land</d:screen-orientation>
+ <d:keyboard-state>keyssoft</d:keyboard-state>
+ <d:nav-state>nonav</d:nav-state>
+ </d:state>
+ </d:device>
+
+</d:devices>
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/androidTarget/AddOnTarget.java b/sdklib/src/main/java/com/android/sdklib/internal/androidTarget/AddOnTarget.java
new file mode 100644
index 0000000..83c3555
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/internal/androidTarget/AddOnTarget.java
@@ -0,0 +1,475 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.internal.androidTarget;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.sdklib.AndroidTargetHash;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.ISystemImage;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Represents an add-on target in the SDK.
+ * An add-on extends a standard {@link PlatformTarget}.
+ */
+public final class AddOnTarget implements IAndroidTarget {
+
+ private static final class OptionalLibrary implements IOptionalLibrary {
+ private final String mJarName;
+ private final String mJarPath;
+ private final String mName;
+ private final String mDescription;
+
+ OptionalLibrary(String jarName, String jarPath, String name, String description) {
+ mJarName = jarName;
+ mJarPath = jarPath;
+ mName = name;
+ mDescription = description;
+ }
+
+ @Override
+ public String getJarName() {
+ return mJarName;
+ }
+
+ @Override
+ public String getJarPath() {
+ return mJarPath;
+ }
+
+ @Override
+ public String getName() {
+ return mName;
+ }
+
+ @Override
+ public String getDescription() {
+ return mDescription;
+ }
+ }
+
+ private final String mLocation;
+ private final PlatformTarget mBasePlatform;
+ private final String mName;
+ private final ISystemImage[] mSystemImages;
+ private final String mVendor;
+ private final int mRevision;
+ private final String mDescription;
+ private final boolean mHasRenderingLibrary;
+ private final boolean mHasRenderingResources;
+
+ private String[] mSkins;
+ private String mDefaultSkin;
+ private IOptionalLibrary[] mLibraries;
+ private int mVendorId = NO_USB_ID;
+
+ /**
+ * Creates a new add-on
+ * @param location the OS path location of the add-on
+ * @param name the name of the add-on
+ * @param vendor the vendor name of the add-on
+ * @param revision the revision of the add-on
+ * @param description the add-on description
+ * @param systemImages list of supported system images. Can be null or empty.
+ * @param libMap A map containing the optional libraries. The map key is the fully-qualified
+ * library name. The value is a 2 string array with the .jar filename, and the description.
+ * @param hasRenderingLibrary whether the addon has a custom layoutlib.jar
+ * @param hasRenderingResources whether the add has custom framework resources.
+ * @param basePlatform the platform the add-on is extending.
+ */
+ public AddOnTarget(
+ String location,
+ String name,
+ String vendor,
+ int revision,
+ String description,
+ ISystemImage[] systemImages,
+ Map<String, String[]> libMap,
+ boolean hasRenderingLibrary,
+ boolean hasRenderingResources,
+ PlatformTarget basePlatform) {
+ if (location.endsWith(File.separator) == false) {
+ location = location + File.separator;
+ }
+
+ mLocation = location;
+ mName = name;
+ mVendor = vendor;
+ mRevision = revision;
+ mDescription = description;
+ mHasRenderingLibrary = hasRenderingLibrary;
+ mHasRenderingResources = hasRenderingResources;
+ mBasePlatform = basePlatform;
+
+ // If the add-on does not have any system-image of its own, the list here
+ // is empty and it's up to the callers to query the parent platform.
+ mSystemImages = systemImages == null ? new ISystemImage[0] : systemImages;
+ Arrays.sort(mSystemImages);
+
+ // handle the optional libraries.
+ if (libMap != null) {
+ mLibraries = new IOptionalLibrary[libMap.size()];
+ int index = 0;
+ for (Entry<String, String[]> entry : libMap.entrySet()) {
+ String jarFile = entry.getValue()[0];
+ String desc = entry.getValue()[1];
+ mLibraries[index++] = new OptionalLibrary(jarFile,
+ mLocation + SdkConstants.OS_ADDON_LIBS_FOLDER + jarFile,
+ entry.getKey(), desc);
+ }
+ }
+ }
+
+ @Override
+ public String getLocation() {
+ return mLocation;
+ }
+
+ @Override
+ public String getName() {
+ return mName;
+ }
+
+ @Override
+ public ISystemImage getSystemImage(String abiType) {
+ for (ISystemImage sysImg : mSystemImages) {
+ if (sysImg.getAbiType().equals(abiType)) {
+ return sysImg;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public ISystemImage[] getSystemImages() {
+ return mSystemImages;
+ }
+
+ @Override
+ public String getVendor() {
+ return mVendor;
+ }
+
+ @Override
+ public String getFullName() {
+ return String.format("%1$s (%2$s)", mName, mVendor);
+ }
+
+ @Override
+ public String getClasspathName() {
+ return String.format("%1$s [%2$s]", mName, mBasePlatform.getClasspathName());
+ }
+
+ @Override
+ public String getShortClasspathName() {
+ return String.format("%1$s [%2$s]", mName, mBasePlatform.getVersionName());
+ }
+
+ @Override
+ public String getDescription() {
+ return mDescription;
+ }
+
+ @Override
+ public AndroidVersion getVersion() {
+ // this is always defined by the base platform
+ return mBasePlatform.getVersion();
+ }
+
+ @Override
+ public String getVersionName() {
+ return mBasePlatform.getVersionName();
+ }
+
+ @Override
+ public int getRevision() {
+ return mRevision;
+ }
+
+ @Override
+ public boolean isPlatform() {
+ return false;
+ }
+
+ @Override
+ public IAndroidTarget getParent() {
+ return mBasePlatform;
+ }
+
+ @Override
+ public String getPath(int pathId) {
+ switch (pathId) {
+ case SKINS:
+ return mLocation + SdkConstants.OS_SKINS_FOLDER;
+ case DOCS:
+ return mLocation + SdkConstants.FD_DOCS + File.separator
+ + SdkConstants.FD_DOCS_REFERENCE;
+
+ case LAYOUT_LIB:
+ if (mHasRenderingLibrary) {
+ return mLocation + SdkConstants.FD_DATA + File.separator
+ + SdkConstants.FN_LAYOUTLIB_JAR;
+ }
+ return mBasePlatform.getPath(pathId);
+
+ case RESOURCES:
+ if (mHasRenderingResources) {
+ return mLocation + SdkConstants.FD_DATA + File.separator
+ + SdkConstants.FD_RES;
+ }
+ return mBasePlatform.getPath(pathId);
+
+ case FONTS:
+ if (mHasRenderingResources) {
+ return mLocation + SdkConstants.FD_DATA + File.separator
+ + SdkConstants.FD_FONTS;
+ }
+ return mBasePlatform.getPath(pathId);
+
+ case SAMPLES:
+ // only return the add-on samples folder if there is actually a sample (or more)
+ File sampleLoc = new File(mLocation, SdkConstants.FD_SAMPLES);
+ if (sampleLoc.isDirectory()) {
+ File[] files = sampleLoc.listFiles(new FileFilter() {
+ @Override
+ public boolean accept(File pathname) {
+ return pathname.isDirectory();
+ }
+
+ });
+ if (files != null && files.length > 0) {
+ return sampleLoc.getAbsolutePath();
+ }
+ }
+ //$FALL-THROUGH$
+ default :
+ return mBasePlatform.getPath(pathId);
+ }
+ }
+
+ @Override
+ public BuildToolInfo getBuildToolInfo() {
+ return mBasePlatform.getBuildToolInfo();
+ }
+
+ @Override @NonNull
+ public List<String> getBootClasspath() {
+ return Collections.singletonList(getPath(IAndroidTarget.ANDROID_JAR));
+ }
+
+ @Override
+ public boolean hasRenderingLibrary() {
+ return mHasRenderingLibrary || mHasRenderingResources;
+ }
+
+ @Override
+ public String[] getSkins() {
+ return mSkins;
+ }
+
+ @Override
+ public String getDefaultSkin() {
+ return mDefaultSkin;
+ }
+
+ @Override
+ public IOptionalLibrary[] getOptionalLibraries() {
+ return mLibraries;
+ }
+
+ /**
+ * Returns the list of libraries of the underlying platform.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public String[] getPlatformLibraries() {
+ return mBasePlatform.getPlatformLibraries();
+ }
+
+ @Override
+ public String getProperty(String name) {
+ return mBasePlatform.getProperty(name);
+ }
+
+ @Override
+ public Integer getProperty(String name, Integer defaultValue) {
+ return mBasePlatform.getProperty(name, defaultValue);
+ }
+
+ @Override
+ public Boolean getProperty(String name, Boolean defaultValue) {
+ return mBasePlatform.getProperty(name, defaultValue);
+ }
+
+ @Override
+ public Map<String, String> getProperties() {
+ return mBasePlatform.getProperties();
+ }
+
+ @Override
+ public int getUsbVendorId() {
+ return mVendorId;
+ }
+
+ @Override
+ public boolean canRunOn(IAndroidTarget target) {
+ // basic test
+ if (target == this) {
+ return true;
+ }
+
+ /*
+ * The method javadoc indicates:
+ * Returns whether the given target is compatible with the receiver.
+ * <p/>A target is considered compatible if applications developed for the receiver can
+ * run on the given target.
+ */
+
+ // The receiver is an add-on. There are 2 big use cases: The add-on has libraries
+ // or the add-on doesn't (in which case we consider it a platform).
+ if (mLibraries == null || mLibraries.length == 0) {
+ return mBasePlatform.canRunOn(target);
+ } else {
+ // the only targets that can run the receiver are the same add-on in the same or later
+ // versions.
+ // first check: vendor/name
+ if (mVendor.equals(target.getVendor()) == false ||
+ mName.equals(target.getName()) == false) {
+ return false;
+ }
+
+ // now check the version. At this point since we checked the add-on part,
+ // we can revert to the basic check on version/codename which are done by the
+ // base platform already.
+ return mBasePlatform.canRunOn(target);
+ }
+
+ }
+
+ @Override
+ public String hashString() {
+ return String.format(AndroidTargetHash.ADD_ON_FORMAT, mVendor, mName,
+ mBasePlatform.getVersion().getApiString());
+ }
+
+ @Override
+ public int hashCode() {
+ return hashString().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof AddOnTarget) {
+ AddOnTarget addon = (AddOnTarget)obj;
+
+ return mVendor.equals(addon.mVendor) && mName.equals(addon.mName) &&
+ mBasePlatform.getVersion().equals(addon.mBasePlatform.getVersion());
+ }
+
+ return false;
+ }
+
+ /*
+ * Order by API level (preview/n count as between n and n+1).
+ * At the same API level, order as: Platform first, then add-on ordered by vendor and then name
+ * (non-Javadoc)
+ * @see java.lang.Comparable#compareTo(java.lang.Object)
+ */
+ @Override
+ public int compareTo(IAndroidTarget target) {
+ // quick check.
+ if (this == target) {
+ return 0;
+ }
+
+ int versionDiff = getVersion().compareTo(target.getVersion());
+
+ // only if the version are the same do we care about platform/add-ons.
+ if (versionDiff == 0) {
+ // platforms go before add-ons.
+ if (target.isPlatform()) {
+ return +1;
+ } else {
+ AddOnTarget targetAddOn = (AddOnTarget)target;
+
+ // both are add-ons of the same version. Compare per vendor then by name
+ int vendorDiff = mVendor.compareTo(targetAddOn.mVendor);
+ if (vendorDiff == 0) {
+ return mName.compareTo(targetAddOn.mName);
+ } else {
+ return vendorDiff;
+ }
+ }
+
+ }
+
+ return versionDiff;
+ }
+
+ /**
+ * Returns a string representation suitable for debugging.
+ * The representation is not intended for display to the user.
+ *
+ * The representation is also purposely compact. It does not describe _all_ the properties
+ * of the target, only a few key ones.
+ *
+ * @see #getDescription()
+ */
+ @Override
+ public String toString() {
+ return String.format("AddonTarget %1$s rev %2$d (based on %3$s)", //$NON-NLS-1$
+ getVersion(),
+ getRevision(),
+ getParent().toString());
+ }
+
+ // ---- local methods.
+
+ public void setSkins(String[] skins, String defaultSkin) {
+ mDefaultSkin = defaultSkin;
+
+ // we mix the add-on and base platform skins
+ HashSet<String> skinSet = new HashSet<String>();
+ skinSet.addAll(Arrays.asList(skins));
+ skinSet.addAll(Arrays.asList(mBasePlatform.getSkins()));
+
+ mSkins = skinSet.toArray(new String[skinSet.size()]);
+ }
+
+ /**
+ * Sets the USB vendor id in the add-on.
+ */
+ public void setUsbVendorId(int vendorId) {
+ if (vendorId == 0) {
+ throw new IllegalArgumentException( "VendorId must be > 0");
+ }
+
+ mVendorId = vendorId;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/androidTarget/PlatformTarget.java b/sdklib/src/main/java/com/android/sdklib/internal/androidTarget/PlatformTarget.java
new file mode 100644
index 0000000..0fc2227
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/internal/androidTarget/PlatformTarget.java
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.internal.androidTarget;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.sdklib.AndroidTargetHash;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.ISystemImage;
+import com.android.sdklib.SdkManager.LayoutlibVersion;
+import com.android.utils.SparseArray;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Represents a platform target in the SDK.
+ */
+public final class PlatformTarget implements IAndroidTarget {
+
+ private static final String PLATFORM_VENDOR = "Android Open Source Project";
+
+ private static final String PLATFORM_NAME = "Android %s";
+ private static final String PLATFORM_NAME_PREVIEW = "Android %s (Preview)";
+
+ /** the OS path to the root folder of the platform component. */
+ private final String mRootFolderOsPath;
+ private final String mName;
+ private final AndroidVersion mVersion;
+ private final String mVersionName;
+ private final int mRevision;
+ private final Map<String, String> mProperties;
+ private final SparseArray<String> mPaths = new SparseArray<String>();
+ private String[] mSkins;
+ private final ISystemImage[] mSystemImages;
+ private final LayoutlibVersion mLayoutlibVersion;
+ private final BuildToolInfo mBuildToolInfo;
+
+ /**
+ * Creates a Platform target.
+ *
+ * @param sdkOsPath the root folder of the SDK
+ * @param platformOSPath the root folder of the platform component
+ * @param apiVersion the API Level + codename.
+ * @param versionName the version name of the platform.
+ * @param revision the revision of the platform component.
+ * @param layoutlibVersion The {@link LayoutlibVersion}. May be null.
+ * @param systemImages list of supported system images
+ * @param properties the platform properties
+ */
+ @SuppressWarnings("deprecation")
+ public PlatformTarget(
+ String sdkOsPath,
+ String platformOSPath,
+ AndroidVersion apiVersion,
+ String versionName,
+ int revision,
+ LayoutlibVersion layoutlibVersion,
+ ISystemImage[] systemImages,
+ Map<String, String> properties,
+ @NonNull BuildToolInfo buildToolInfo) {
+ if (!platformOSPath.endsWith(File.separator)) {
+ platformOSPath = platformOSPath + File.separator;
+ }
+ mRootFolderOsPath = platformOSPath;
+ mProperties = Collections.unmodifiableMap(properties);
+ mVersion = apiVersion;
+ mVersionName = versionName;
+ mRevision = revision;
+ mLayoutlibVersion = layoutlibVersion;
+ mBuildToolInfo = buildToolInfo;
+ mSystemImages = systemImages == null ? new ISystemImage[0] : systemImages;
+ Arrays.sort(mSystemImages);
+
+ if (mVersion.isPreview()) {
+ mName = String.format(PLATFORM_NAME_PREVIEW, mVersionName);
+ } else {
+ mName = String.format(PLATFORM_NAME, mVersionName);
+ }
+
+ // pre-build the path to the platform components
+ mPaths.put(ANDROID_JAR, mRootFolderOsPath + SdkConstants.FN_FRAMEWORK_LIBRARY);
+ mPaths.put(UI_AUTOMATOR_JAR, mRootFolderOsPath + SdkConstants.FN_UI_AUTOMATOR_LIBRARY);
+ mPaths.put(SOURCES, mRootFolderOsPath + SdkConstants.FD_ANDROID_SOURCES);
+ mPaths.put(ANDROID_AIDL, mRootFolderOsPath + SdkConstants.FN_FRAMEWORK_AIDL);
+ mPaths.put(SAMPLES, mRootFolderOsPath + SdkConstants.OS_PLATFORM_SAMPLES_FOLDER);
+ mPaths.put(SKINS, mRootFolderOsPath + SdkConstants.OS_SKINS_FOLDER);
+ mPaths.put(TEMPLATES, mRootFolderOsPath + SdkConstants.OS_PLATFORM_TEMPLATES_FOLDER);
+ mPaths.put(DATA, mRootFolderOsPath + SdkConstants.OS_PLATFORM_DATA_FOLDER);
+ mPaths.put(ATTRIBUTES, mRootFolderOsPath + SdkConstants.OS_PLATFORM_ATTRS_XML);
+ mPaths.put(MANIFEST_ATTRIBUTES,
+ mRootFolderOsPath + SdkConstants.OS_PLATFORM_ATTRS_MANIFEST_XML);
+ mPaths.put(RESOURCES, mRootFolderOsPath + SdkConstants.OS_PLATFORM_RESOURCES_FOLDER);
+ mPaths.put(FONTS, mRootFolderOsPath + SdkConstants.OS_PLATFORM_FONTS_FOLDER);
+ mPaths.put(LAYOUT_LIB, mRootFolderOsPath + SdkConstants.OS_PLATFORM_DATA_FOLDER +
+ SdkConstants.FN_LAYOUTLIB_JAR);
+ mPaths.put(WIDGETS, mRootFolderOsPath + SdkConstants.OS_PLATFORM_DATA_FOLDER +
+ SdkConstants.FN_WIDGETS);
+ mPaths.put(ACTIONS_ACTIVITY, mRootFolderOsPath + SdkConstants.OS_PLATFORM_DATA_FOLDER +
+ SdkConstants.FN_INTENT_ACTIONS_ACTIVITY);
+ mPaths.put(ACTIONS_BROADCAST, mRootFolderOsPath + SdkConstants.OS_PLATFORM_DATA_FOLDER +
+ SdkConstants.FN_INTENT_ACTIONS_BROADCAST);
+ mPaths.put(ACTIONS_SERVICE, mRootFolderOsPath + SdkConstants.OS_PLATFORM_DATA_FOLDER +
+ SdkConstants.FN_INTENT_ACTIONS_SERVICE);
+ mPaths.put(CATEGORIES, mRootFolderOsPath + SdkConstants.OS_PLATFORM_DATA_FOLDER +
+ SdkConstants.FN_INTENT_CATEGORIES);
+ mPaths.put(ANT, mRootFolderOsPath + SdkConstants.OS_PLATFORM_ANT_FOLDER);
+ }
+
+ /**
+ * Returns the {@link LayoutlibVersion}. May be null.
+ */
+ public LayoutlibVersion getLayoutlibVersion() {
+ return mLayoutlibVersion;
+ }
+
+ @Override
+ public ISystemImage getSystemImage(String abiType) {
+ for (ISystemImage sysImg : mSystemImages) {
+ if (sysImg.getAbiType().equals(abiType)) {
+ return sysImg;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public ISystemImage[] getSystemImages() {
+ return mSystemImages;
+ }
+
+ @Override
+ public String getLocation() {
+ return mRootFolderOsPath;
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p/>
+ * For Platform, the vendor name is always "Android".
+ *
+ * @see com.android.sdklib.IAndroidTarget#getVendor()
+ */
+ @Override
+ public String getVendor() {
+ return PLATFORM_VENDOR;
+ }
+
+ @Override
+ public String getName() {
+ return mName;
+ }
+
+ @Override
+ public String getFullName() {
+ return mName;
+ }
+
+ @Override
+ public String getClasspathName() {
+ return mName;
+ }
+
+ @Override
+ public String getShortClasspathName() {
+ return mName;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * Description for the Android platform is dynamically generated.
+ *
+ * @see com.android.sdklib.IAndroidTarget#getDescription()
+ */
+ @Override
+ public String getDescription() {
+ return String.format("Standard Android platform %s", mVersionName);
+ }
+
+ @Override
+ public AndroidVersion getVersion() {
+ return mVersion;
+ }
+
+ @Override
+ public String getVersionName() {
+ return mVersionName;
+ }
+
+ @Override
+ public int getRevision() {
+ return mRevision;
+ }
+
+ @Override
+ public boolean isPlatform() {
+ return true;
+ }
+
+ @Override
+ public IAndroidTarget getParent() {
+ return null;
+ }
+
+ @Override
+ public String getPath(int pathId) {
+ return mPaths.get(pathId);
+ }
+
+ @Override
+ public BuildToolInfo getBuildToolInfo() {
+ return mBuildToolInfo;
+ }
+
+ @Override @NonNull
+ public List<String> getBootClasspath() {
+ return Collections.singletonList(getPath(IAndroidTarget.ANDROID_JAR));
+ }
+
+ /**
+ * Returns whether the target is able to render layouts. This is always true for platforms.
+ */
+ @Override
+ public boolean hasRenderingLibrary() {
+ return true;
+ }
+
+
+ @Override
+ public String[] getSkins() {
+ return mSkins;
+ }
+
+ @Override
+ public String getDefaultSkin() {
+ // only one skin? easy.
+ if (mSkins.length == 1) {
+ return mSkins[0];
+ }
+
+ // look for the skin name in the platform props
+ String skinName = mProperties.get(SdkConstants.PROP_SDK_DEFAULT_SKIN);
+ if (skinName != null) {
+ return skinName;
+ }
+
+ // otherwise try to find a good default.
+ if (mVersion.getApiLevel() >= 4) {
+ // at this time, this is the default skin for all older platforms that had 2+ skins.
+ return "WVGA800";
+ }
+
+ return "HVGA"; // this is for 1.5 and earlier.
+ }
+
+ /**
+ * Always returns null, as a standard platform ha no optional libraries.
+ *
+ * {@inheritDoc}
+ * @see com.android.sdklib.IAndroidTarget#getOptionalLibraries()
+ */
+ @Override
+ public IOptionalLibrary[] getOptionalLibraries() {
+ return null;
+ }
+
+ /**
+ * Currently always return a fixed list with "android.test.runner" in it.
+ * <p/>
+ * TODO change the fixed library list to be build-dependent later.
+ * {@inheritDoc}
+ */
+ @Override
+ public String[] getPlatformLibraries() {
+ return new String[] { SdkConstants.ANDROID_TEST_RUNNER_LIB };
+ }
+
+ /**
+ * The platform has no USB Vendor Id: always return {@link IAndroidTarget#NO_USB_ID}.
+ * {@inheritDoc}
+ */
+ @Override
+ public int getUsbVendorId() {
+ return NO_USB_ID;
+ }
+
+ @Override
+ public boolean canRunOn(IAndroidTarget target) {
+ // basic test
+ if (target == this) {
+ return true;
+ }
+
+ // if the platform has a codename (ie it's a preview of an upcoming platform), then
+ // both platforms must be exactly identical.
+ if (mVersion.getCodename() != null) {
+ return mVersion.equals(target.getVersion());
+ }
+
+ // target is compatible wit the receiver as long as its api version number is greater or
+ // equal.
+ return target.getVersion().getApiLevel() >= mVersion.getApiLevel();
+ }
+
+ @Override
+ public String hashString() {
+ return AndroidTargetHash.getPlatformHashString(mVersion);
+ }
+
+ @Override
+ public int hashCode() {
+ return hashString().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof PlatformTarget) {
+ PlatformTarget platform = (PlatformTarget)obj;
+
+ return mVersion.equals(platform.getVersion());
+ }
+
+ return false;
+ }
+
+ /*
+ * Order by API level (preview/n count as between n and n+1).
+ * At the same API level, order as: Platform first, then add-on ordered by vendor and then name
+ * (non-Javadoc)
+ * @see java.lang.Comparable#compareTo(java.lang.Object)
+ */
+ @Override
+ public int compareTo(IAndroidTarget target) {
+ // quick check.
+ if (this == target) {
+ return 0;
+ }
+
+ int versionDiff = mVersion.compareTo(target.getVersion());
+
+ // only if the version are the same do we care about add-ons.
+ if (versionDiff == 0) {
+ // platforms go before add-ons.
+ if (target.isPlatform() == false) {
+ return -1;
+ }
+ }
+
+ return versionDiff;
+ }
+
+ /**
+ * Returns a string representation suitable for debugging.
+ * The representation is not intended for display to the user.
+ *
+ * The representation is also purposely compact. It does not describe _all_ the properties
+ * of the target, only a few key ones.
+ *
+ * @see #getDescription()
+ */
+ @Override
+ public String toString() {
+ return String.format("PlatformTarget %1$s rev %2$d", //$NON-NLS-1$
+ getVersion(),
+ getRevision());
+ }
+
+ @Override
+ public String getProperty(String name) {
+ return mProperties.get(name);
+ }
+
+ @Override
+ public Integer getProperty(String name, Integer defaultValue) {
+ try {
+ String value = getProperty(name);
+ if (value != null) {
+ return Integer.decode(value);
+ }
+ } catch (NumberFormatException e) {
+ // ignore, return default value;
+ }
+
+ return defaultValue;
+ }
+
+ @Override
+ public Boolean getProperty(String name, Boolean defaultValue) {
+ String value = getProperty(name);
+ if (value != null) {
+ return Boolean.valueOf(value);
+ }
+
+ return defaultValue;
+ }
+
+ @Override
+ public Map<String, String> getProperties() {
+ return mProperties; // mProperties is unmodifiable.
+ }
+
+ // ---- platform only methods.
+
+ public void setSkins(String[] skins) {
+ mSkins = skins;
+ }
+
+ public void setSamplesPath(String osLocation) {
+ mPaths.put(SAMPLES, osLocation);
+ }
+
+ public void setSourcesPath(String osLocation) {
+ mPaths.put(SOURCES, osLocation);
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/build/SignedJarBuilder.java b/sdklib/src/main/java/com/android/sdklib/internal/build/SignedJarBuilder.java
old mode 100755
new mode 100644
index 6c3239d..2dd0a26
--- a/sdklib/src/main/java/com/android/sdklib/internal/build/SignedJarBuilder.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/build/SignedJarBuilder.java
@@ -16,6 +16,16 @@
package com.android.sdklib.internal.build;
+import com.android.sdklib.internal.build.SignedJarBuilder.IZipEntryFilter.ZipAbortException;
+
+import sun.misc.BASE64Encoder;
+import sun.security.pkcs.ContentInfo;
+import sun.security.pkcs.PKCS7;
+import sun.security.pkcs.SignerInfo;
+import sun.security.x509.AlgorithmId;
+import sun.security.x509.X500Name;
+
+import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
@@ -30,9 +40,8 @@
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
-import java.security.cert.CertificateEncodingException;
+import java.security.SignatureException;
import java.security.cert.X509Certificate;
-import java.util.ArrayList;
import java.util.Map;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
@@ -42,23 +51,6 @@
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.DEROutputStream;
-import org.bouncycastle.cert.jcajce.JcaCertStore;
-import org.bouncycastle.cms.CMSException;
-import org.bouncycastle.cms.CMSProcessableByteArray;
-import org.bouncycastle.cms.CMSSignedData;
-import org.bouncycastle.cms.CMSSignedDataGenerator;
-import org.bouncycastle.cms.CMSTypedData;
-import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
-import org.bouncycastle.operator.ContentSigner;
-import org.bouncycastle.operator.OperatorCreationException;
-import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
-import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
-import org.bouncycastle.util.encoders.Base64;
-
-import com.android.sdklib.internal.build.SignedJarBuilder.IZipEntryFilter.ZipAbortException;
-
/**
* A Jar file builder with signature support.
*
@@ -70,25 +62,34 @@
private static final String DIGEST_ATTR = "SHA1-Digest";
private static final String DIGEST_MANIFEST_ATTR = "SHA1-Digest-Manifest";
- /** Write to another stream and track how many bytes have been
- * written.
- */
- private static class CountOutputStream extends FilterOutputStream {
+ /** Write to another stream and also feed it to the Signature object. */
+ private static class SignatureOutputStream extends FilterOutputStream {
+ private Signature mSignature;
private int mCount = 0;
- public CountOutputStream(OutputStream out) {
+ public SignatureOutputStream(OutputStream out, Signature sig) {
super(out);
- mCount = 0;
+ mSignature = sig;
}
@Override
public void write(int b) throws IOException {
+ try {
+ mSignature.update((byte) b);
+ } catch (SignatureException e) {
+ throw new IOException("SignatureException: " + e);
+ }
super.write(b);
mCount++;
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
+ try {
+ mSignature.update(b, off, len);
+ } catch (SignatureException e) {
+ throw new IOException("SignatureException: " + e);
+ }
super.write(b, off, len);
mCount += len;
}
@@ -102,7 +103,7 @@
private PrivateKey mKey;
private X509Certificate mCertificate;
private Manifest mManifest;
- private Base64 mBase64;
+ private BASE64Encoder mBase64Encoder;
private MessageDigest mMessageDigest;
private byte[] mBuffer = new byte[4096];
@@ -161,7 +162,7 @@
*/
public SignedJarBuilder(OutputStream out, PrivateKey key, X509Certificate certificate)
throws IOException, NoSuchAlgorithmException {
- mOutputJar = new JarOutputStream(out);
+ mOutputJar = new JarOutputStream(new BufferedOutputStream(out));
mOutputJar.setLevel(9);
mKey = key;
mCertificate = certificate;
@@ -172,7 +173,7 @@
main.putValue("Manifest-Version", "1.0");
main.putValue("Created-By", "1.0 (Android)");
- mBase64 = new Base64();
+ mBase64Encoder = new BASE64Encoder();
mMessageDigest = MessageDigest.getInstance(DIGEST_ALGORITHM);
}
}
@@ -253,7 +254,7 @@
* @throws IOException
* @throws GeneralSecurityException
*/
- public void close() throws IOException, GeneralSecurityException, Exception {
+ public void close() throws IOException, GeneralSecurityException {
if (mManifest != null) {
// write the manifest to the jar file
mOutputJar.putNextEntry(new JarEntry(JarFile.MANIFEST_NAME));
@@ -263,15 +264,17 @@
Signature signature = Signature.getInstance("SHA1with" + mKey.getAlgorithm());
signature.initSign(mKey);
mOutputJar.putNextEntry(new JarEntry("META-INF/CERT.SF"));
-
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- writeSignatureFile(baos);
- byte[] signedData = baos.toByteArray();
- mOutputJar.write(signedData);
+ SignatureOutputStream out = new SignatureOutputStream(mOutputJar, signature);
+ writeSignatureFile(out);
// CERT.*
mOutputJar.putNextEntry(new JarEntry("META-INF/CERT." + mKey.getAlgorithm()));
- writeSignatureBlock(new CMSProcessableByteArray(signedData), mCertificate, mKey);
+ writeSignatureBlock(signature, mCertificate, mKey);
+
+ // close out at the end because it can also close mOutputJar.
+ // (there's some timing issue here I think, because it's worked before with out
+ // being closed after writing CERT.SF).
+ out.close();
}
mOutputJar.close();
@@ -323,20 +326,19 @@
attr = new Attributes();
mManifest.getEntries().put(entry.getName(), attr);
}
- attr.putValue(DIGEST_ATTR,
- new String(mBase64.encode(mMessageDigest.digest()), "ASCII"));
+ attr.putValue(DIGEST_ATTR, mBase64Encoder.encode(mMessageDigest.digest()));
}
}
/** Writes a .SF file with a digest to the manifest. */
- private void writeSignatureFile(OutputStream out)
+ private void writeSignatureFile(SignatureOutputStream out)
throws IOException, GeneralSecurityException {
Manifest sf = new Manifest();
Attributes main = sf.getMainAttributes();
main.putValue("Signature-Version", "1.0");
main.putValue("Created-By", "1.0 (Android)");
- Base64 base64 = new Base64();
+ BASE64Encoder base64 = new BASE64Encoder();
MessageDigest md = MessageDigest.getInstance(DIGEST_ALGORITHM);
PrintStream print = new PrintStream(
new DigestOutputStream(new ByteArrayOutputStream(), md),
@@ -345,7 +347,7 @@
// Digest of the entire manifest
mManifest.write(print);
print.flush();
- main.putValue(DIGEST_MANIFEST_ATTR, new String(base64.encode(md.digest()), "ASCII"));
+ main.putValue(DIGEST_MANIFEST_ATTR, base64.encode(md.digest()));
Map<String, Attributes> entries = mManifest.getEntries();
for (Map.Entry<String, Attributes> entry : entries.entrySet()) {
@@ -358,52 +360,39 @@
print.flush();
Attributes sfAttr = new Attributes();
- sfAttr.putValue(DIGEST_ATTR, new String(base64.encode(md.digest()), "ASCII"));
+ sfAttr.putValue(DIGEST_ATTR, base64.encode(md.digest()));
sf.getEntries().put(entry.getKey(), sfAttr);
}
- CountOutputStream cout = new CountOutputStream(out);
- sf.write(cout);
+
+ sf.write(out);
// A bug in the java.util.jar implementation of Android platforms
// up to version 1.6 will cause a spurious IOException to be thrown
// if the length of the signature file is a multiple of 1024 bytes.
// As a workaround, add an extra CRLF in this case.
- if ((cout.size() % 1024) == 0) {
- cout.write('\r');
- cout.write('\n');
+ if ((out.size() % 1024) == 0) {
+ out.write('\r');
+ out.write('\n');
}
}
/** Write the certificate file with a digital signature. */
- private void writeSignatureBlock(CMSTypedData data, X509Certificate publicKey,
+ private void writeSignatureBlock(Signature signature, X509Certificate publicKey,
PrivateKey privateKey)
- throws IOException,
- CertificateEncodingException,
- OperatorCreationException,
- CMSException {
+ throws IOException, GeneralSecurityException {
+ SignerInfo signerInfo = new SignerInfo(
+ new X500Name(publicKey.getIssuerX500Principal().getName()),
+ publicKey.getSerialNumber(),
+ AlgorithmId.get(DIGEST_ALGORITHM),
+ AlgorithmId.get(privateKey.getAlgorithm()),
+ signature.sign());
- ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>();
- certList.add(publicKey);
- JcaCertStore certs = new JcaCertStore(certList);
+ PKCS7 pkcs7 = new PKCS7(
+ new AlgorithmId[] { AlgorithmId.get(DIGEST_ALGORITHM) },
+ new ContentInfo(ContentInfo.DATA_OID, null),
+ new X509Certificate[] { publicKey },
+ new SignerInfo[] { signerInfo });
- CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
- ContentSigner sha1Signer = new JcaContentSignerBuilder(
- "SHA1with" + privateKey.getAlgorithm())
- .build(privateKey);
- gen.addSignerInfoGenerator(
- new JcaSignerInfoGeneratorBuilder(
- new JcaDigestCalculatorProviderBuilder()
- .build())
- .setDirectSignature(true)
- .build(sha1Signer, publicKey));
- gen.addCertificates(certs);
- CMSSignedData sigData = gen.generate(data, false);
-
- ASN1InputStream asn1 = new ASN1InputStream(sigData.getEncoded());
- DEROutputStream dos = new DEROutputStream(mOutputJar);
- dos.writeObject(asn1.readObject());
- dos.flush();
- dos.close();
- asn1.close();
+ pkcs7.encodeSignedData(mOutputJar);
}
}
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/project/ProjectProperties.java b/sdklib/src/main/java/com/android/sdklib/internal/project/ProjectProperties.java
index 6a31c27..e578747 100644
--- a/sdklib/src/main/java/com/android/sdklib/internal/project/ProjectProperties.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/project/ProjectProperties.java
@@ -29,6 +29,7 @@
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Collections;
@@ -62,6 +63,8 @@
public static final String PROPERTY_TARGET = "target";
/** The property name for the renderscript build target */
public static final String PROPERTY_RS_TARGET = "renderscript.target";
+ /** The property name for the renderscript support mode */
+ public static final String PROPERTY_RS_SUPPORT = "renderscript.support.mode";
/** The version of the build tools to use to compile */
public static final String PROPERTY_BUILD_TOOLS = "sdk.buildtools";
@@ -73,6 +76,7 @@
public static final String PROPERTY_RULES_PATH = "layoutrules.jars";
public static final String PROPERTY_SDK = "sdk.dir";
+ public static final String PROPERTY_NDK = "ndk.dir";
// LEGACY - Kept so that we can actually remove it from local.properties.
private static final String PROPERTY_SDK_LEGACY = "sdk-location";
@@ -451,10 +455,50 @@
public static Map<String, String> parsePropertyFile(
@NonNull IAbstractFile propFile,
@Nullable ILogger log) {
+ try {
+ return parsePropertyStream(propFile.getContents(),
+ propFile.getOsLocation(),
+ log);
+ } catch (StreamException e) {
+ if (log != null) {
+ log.warning("Error parsing '%1$s': %2$s.",
+ propFile.getOsLocation(),
+ e.getMessage());
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Parses a property file (using UTF-8 encoding) and returns a map of the content.
+ * <p/>
+ * Always closes the given input stream on exit.
+ * <p/>
+ * IMPORTANT: This method is now unfortunately used in multiple places to parse random
+ * property files. This is NOT a safe practice since there is no corresponding method
+ * to write property files unless you use {@link ProjectPropertiesWorkingCopy#save()}.
+ * Code that writes INI or properties without at least using {@link #escape(String)} will
+ * certainly not load back correct data. <br/>
+ * Unless there's a strong legacy need to support existing files, new callers should
+ * probably just use Java's {@link Properties} which has well defined semantics.
+ * It's also a mistake to write/read property files using this code and expect it to
+ * work with Java's {@link Properties} or external tools (e.g. ant) since there can be
+ * differences in escaping and in character encoding.
+ *
+ * @param propStream the input stream of the property file to parse.
+ * @param propPath the file path, for display purposed in case of error.
+ * @param log the ILogger object receiving warning/error from the parsing.
+ * @return the map of (key,value) pairs, or null if the parsing failed.
+ */
+ public static Map<String, String> parsePropertyStream(
+ @NonNull InputStream propStream,
+ @NonNull String propPath,
+ @Nullable ILogger log) {
BufferedReader reader = null;
try {
- reader = new BufferedReader(new InputStreamReader(propFile.getContents(),
- SdkConstants.INI_CHARSET));
+ reader = new BufferedReader(
+ new InputStreamReader(propStream, SdkConstants.INI_CHARSET));
String line = null;
Map<String, String> map = new HashMap<String, String>();
@@ -468,7 +512,7 @@
} else {
if (log != null) {
log.warning("Error parsing '%1$s': \"%2$s\" is not a valid syntax",
- propFile.getOsLocation(),
+ propPath,
line);
}
return null;
@@ -484,17 +528,12 @@
} catch (IOException e) {
if (log != null) {
log.warning("Error parsing '%1$s': %2$s.",
- propFile.getOsLocation(),
- e.getMessage());
- }
- } catch (StreamException e) {
- if (log != null) {
- log.warning("Error parsing '%1$s': %2$s.",
- propFile.getOsLocation(),
+ propPath,
e.getMessage());
}
} finally {
Closeables.closeQuietly(reader);
+ Closeables.closeQuietly(propStream);
}
return null;
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/IListDescription.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/IListDescription.java
new file mode 100755
index 0000000..fdcd1a3
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/IListDescription.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.internal.repository;
+
+/**
+ * Interface for elements that can provide a description of themselves.
+ */
+public interface IListDescription {
+
+ /**
+ * Returns a description of this package that is suitable for a list display.
+ * Should not be empty. Must never be null.
+ * <p/>
+ * Note that this is the "base" name for the package
+ * with no specific revision nor API mentioned.
+ * In contrast, {@link IDescription#getShortDescription()} should be used if you
+ * want more details such as the package revision number or the API, if applicable.
+ */
+ public abstract String getListDescription();
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/LocalSdkParser.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/LocalSdkParser.java
index 4b63aa9..ff87bd7 100755
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/LocalSdkParser.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/LocalSdkParser.java
@@ -18,10 +18,13 @@
import com.android.SdkConstants;
import com.android.annotations.NonNull;
+import com.android.io.FileWrapper;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.ISystemImage;
import com.android.sdklib.ISystemImage.LocationType;
import com.android.sdklib.SdkManager;
+import com.android.sdklib.internal.androidTarget.PlatformTarget;
+import com.android.sdklib.internal.project.ProjectProperties;
import com.android.sdklib.internal.repository.archives.Archive.Arch;
import com.android.sdklib.internal.repository.archives.Archive.Os;
import com.android.sdklib.internal.repository.packages.AddonPackage;
@@ -35,6 +38,8 @@
import com.android.sdklib.internal.repository.packages.SourcePackage;
import com.android.sdklib.internal.repository.packages.SystemImagePackage;
import com.android.sdklib.internal.repository.packages.ToolPackage;
+import com.android.sdklib.local.LocalAddonPkgInfo;
+import com.android.sdklib.local.LocalSdk;
import com.android.utils.ILogger;
import com.android.utils.Pair;
@@ -55,33 +60,33 @@
private Package[] mPackages;
/** Parse all SDK folders. */
- public static final int PARSE_ALL = 0xFFFF;
+ public static final int PARSE_ALL = LocalSdk.PKG_ALL;
/** Parse the SDK/tools folder. */
- public static final int PARSE_TOOLS = 0x0001;
+ public static final int PARSE_TOOLS = LocalSdk.PKG_TOOLS;
/** Parse the SDK/platform-tools folder */
- public static final int PARSE_PLATFORM_TOOLS = 0x0002;
+ public static final int PARSE_PLATFORM_TOOLS = LocalSdk.PKG_PLATFORM_TOOLS;
/** Parse the SDK/docs folder. */
- public static final int PARSE_DOCS = 0x0004;
+ public static final int PARSE_DOCS = LocalSdk.PKG_DOCS;
/**
* Equivalent to parsing the SDK/platforms folder but does so
* by using the <em>valid</em> targets loaded by the {@link SdkManager}.
* Parsing the platforms also parses the SDK/system-images folder.
*/
- public static final int PARSE_PLATFORMS = 0x0010;
+ public static final int PARSE_PLATFORMS = LocalSdk.PKG_PLATFORMS;
/**
* Equivalent to parsing the SDK/addons folder but does so
* by using the <em>valid</em> targets loaded by the {@link SdkManager}.
*/
- public static final int PARSE_ADDONS = 0x0020;
+ public static final int PARSE_ADDONS = LocalSdk.PKG_ADDONS;
/** Parse the SDK/samples folder.
* Note: this will not detect samples located in the SDK/extras packages. */
- public static final int PARSE_SAMPLES = 0x0100;
+ public static final int PARSE_SAMPLES = LocalSdk.PKG_SAMPLES;
/** Parse the SDK/sources folder. */
- public static final int PARSE_SOURCES = 0x0200;
+ public static final int PARSE_SOURCES = LocalSdk.PKG_SOURCES;
/** Parse the SDK/extras folder. */
- public static final int PARSE_EXTRAS = 0x0400;
+ public static final int PARSE_EXTRAS = LocalSdk.PKG_EXTRAS;
/** Parse the SDK/build-tools folder. */
- public static final int PARSE_BUILD_TOOLS = 0x0800;
+ public static final int PARSE_BUILD_TOOLS = LocalSdk.PKG_BUILD_TOOLS;
public LocalSdkParser() {
// pass
@@ -405,7 +410,7 @@
for (File dir : files) {
if (dir.isDirectory() && !visited.contains(dir)) {
Pair<Map<String, String>, String> infos =
- SdkManager.parseAddonProperties(dir, sdkManager.getTargets(), log);
+ parseAddonProperties(dir, sdkManager.getTargets(), log);
Properties sourceProps =
parseProperties(new File(dir, SdkConstants.FN_SOURCE_PROP));
@@ -426,6 +431,112 @@
}
/**
+ * Parses the add-on properties and decodes any error that occurs when
+ * loading an addon.
+ *
+ * @param addonDir the location of the addon directory.
+ * @param targetList The list of Android target that were already loaded
+ * from the SDK.
+ * @param log the ILogger object receiving warning/error from the parsing.
+ * @return A pair with the property map and an error string. Both can be
+ * null but not at the same time. If a non-null error is present
+ * then the property map must be ignored. The error should be
+ * translatable as it might show up in the SdkManager UI.
+ */
+ @Deprecated // Copied from SdkManager.java, dup of LocalAddonPkgInfo.parseAddonProperties.
+ @NonNull
+ public static Pair<Map<String, String>, String> parseAddonProperties(
+ @NonNull File addonDir, @NonNull IAndroidTarget[] targetList,
+ @NonNull ILogger log) {
+ Map<String, String> propertyMap = null;
+ String error = null;
+
+ FileWrapper addOnManifest = new FileWrapper(addonDir,
+ SdkConstants.FN_MANIFEST_INI);
+
+ do {
+ if (!addOnManifest.isFile()) {
+ error = String.format("File not found: %1$s",
+ SdkConstants.FN_MANIFEST_INI);
+ break;
+ }
+
+ propertyMap = ProjectProperties.parsePropertyFile(addOnManifest,
+ log);
+ if (propertyMap == null) {
+ error = String.format("Failed to parse properties from %1$s",
+ SdkConstants.FN_MANIFEST_INI);
+ break;
+ }
+
+ // look for some specific values in the map.
+ // we require name, vendor, and api
+ String name = propertyMap.get(LocalAddonPkgInfo.ADDON_NAME);
+ if (name == null) {
+ error = String.format("'%1$s' is missing from %2$s.",
+ LocalAddonPkgInfo.ADDON_NAME,
+ SdkConstants.FN_MANIFEST_INI);
+ break;
+ }
+
+ String vendor = propertyMap.get(LocalAddonPkgInfo.ADDON_VENDOR);
+ if (vendor == null) {
+ error = String.format("'%1$s' is missing from %2$s.",
+ LocalAddonPkgInfo.ADDON_VENDOR,
+ SdkConstants.FN_MANIFEST_INI);
+ break;
+ }
+
+ String api = propertyMap.get(LocalAddonPkgInfo.ADDON_API);
+ PlatformTarget plat = null;
+ if (api == null) {
+ error = String.format("'%1$s' is missing from %2$s.",
+ LocalAddonPkgInfo.ADDON_API,
+ SdkConstants.FN_MANIFEST_INI);
+ break;
+ }
+
+ // Look for a platform that has a matching api level or codename.
+ PlatformTarget baseTarget = null;
+ for (IAndroidTarget target : targetList) {
+ if (target.isPlatform() && target.getVersion().equals(api)) {
+ baseTarget = (PlatformTarget) target;
+ break;
+ }
+ }
+
+ if (baseTarget == null) {
+ error = String.format(
+ "Unable to find base platform with API level '%1$s'",
+ api);
+ break;
+ }
+
+ // get the add-on revision
+ String revision = propertyMap.get(LocalAddonPkgInfo.ADDON_REVISION);
+ if (revision == null) {
+ revision = propertyMap.get(LocalAddonPkgInfo.ADDON_REVISION_OLD);
+ }
+ if (revision != null) {
+ try {
+ Integer.parseInt(revision);
+ } catch (NumberFormatException e) {
+ // looks like revision does not parse to a number.
+ error = String.format(
+ "%1$s is not a valid number in %2$s.",
+ LocalAddonPkgInfo.ADDON_REVISION,
+ SdkConstants.FN_BUILD_PROP);
+ break;
+ }
+ }
+
+ } while (false);
+
+ return Pair.of(propertyMap, error);
+ }
+
+
+ /**
* The sdk manager only lists valid system image via its addons or platform targets.
* However here we also want to find "broken" system images, that is system images
* that are located in the sdk/system-images folder but somehow not loaded properly.
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/AddonPackage.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/AddonPackage.java
index d85dd85..89c0181 100755
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/AddonPackage.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/AddonPackage.java
@@ -28,6 +28,7 @@
import com.android.sdklib.internal.repository.archives.Archive.Arch;
import com.android.sdklib.internal.repository.archives.Archive.Os;
import com.android.sdklib.internal.repository.sources.SdkSource;
+import com.android.sdklib.local.LocalAddonPkgInfo;
import com.android.sdklib.repository.PkgProps;
import com.android.sdklib.repository.SdkAddonConstants;
import com.android.sdklib.repository.SdkRepoConstants;
@@ -317,14 +318,14 @@
PkgProps.ADDON_NAME_DISPLAY,
getProperty(sourceProps,
PkgProps.ADDON_NAME,
- addonProps.get(SdkManager.ADDON_NAME)));
+ addonProps.get(LocalAddonPkgInfo.ADDON_NAME)));
String vendor = getProperty(sourceProps,
PkgProps.ADDON_VENDOR_DISPLAY,
getProperty(sourceProps,
PkgProps.ADDON_VENDOR,
- addonProps.get(SdkManager.ADDON_VENDOR)));
- String api = addonProps.get(SdkManager.ADDON_API);
- String revision = addonProps.get(SdkManager.ADDON_REVISION);
+ addonProps.get(LocalAddonPkgInfo.ADDON_VENDOR)));
+ String api = addonProps.get(LocalAddonPkgInfo.ADDON_API);
+ String revision = addonProps.get(LocalAddonPkgInfo.ADDON_REVISION);
String shortDesc = String.format("%1$s by %2$s, Android API %3$s, revision %4$s [*]",
name,
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/ExtraPackage.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/ExtraPackage.java
index 78a2450..71bef30 100755
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/ExtraPackage.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/ExtraPackage.java
@@ -28,6 +28,7 @@
import com.android.sdklib.internal.repository.archives.Archive.Arch;
import com.android.sdklib.internal.repository.archives.Archive.Os;
import com.android.sdklib.internal.repository.sources.SdkSource;
+import com.android.sdklib.repository.FullRevision;
import com.android.sdklib.repository.PkgProps;
import com.android.sdklib.repository.RepoConstants;
import com.android.utils.NullLogger;
@@ -44,8 +45,11 @@
/**
* Represents a extra XML node in an SDK repository.
*/
-public class ExtraPackage extends MinToolsPackage
- implements IMinApiLevelDependency {
+public class ExtraPackage extends NoPreviewRevisionPackage
+ implements IMinApiLevelDependency, IMinToolsDependency {
+
+ /** Mixin handling the min-tools dependency. */
+ private final MinToolsMixin mMinToolsMixin;
/**
* The extra display name. Used in the UI to represent the package. It can be anything.
@@ -102,6 +106,8 @@
Map<String,String> licenses) {
super(source, packageNode, nsUri, licenses);
+ mMinToolsMixin = new MinToolsMixin(packageNode);
+
mPath = PackageParserUtils.getXmlString(packageNode, RepoConstants.NODE_PATH);
// Read name-display, vendor-display and vendor-id, introduced in addon-4.xsd.
@@ -224,6 +230,17 @@
archiveArch,
archiveOsPath);
+ mMinToolsMixin = new MinToolsMixin(
+ source,
+ props,
+ revision,
+ license,
+ description,
+ descUrl,
+ archiveOs,
+ archiveArch,
+ archiveOsPath);
+
// The path argument comes before whatever could be in the properties
mPath = path != null ? path : getProperty(props, PkgProps.EXTRA_PATH, path);
@@ -232,23 +249,23 @@
String vid = vendorId != null ? vendorId :
getProperty(props, PkgProps.EXTRA_VENDOR_ID, ""); //$NON-NLS-1$
- if (vid.length() == 0) {
+ if (vid == null || vid.length() == 0) {
// If vid is missing, use the old <vendor> attribute.
// <vendor> did not exist prior to schema repo-v3 and tools r8.
String vendor = getProperty(props, PkgProps.EXTRA_VENDOR, ""); //$NON-NLS-1$
vid = sanitizeLegacyVendor(vendor);
- if (vname.length() == 0) {
+ if (vname == null || vname.length() == 0) {
vname = vendor;
}
}
- if (vname.length() == 0) {
+ if (vname == null || vname.length() == 0) {
// The vendor-display name can be empty, in which case we use the vendor-id.
vname = vid;
}
mVendorDisplay = vname.trim();
mVendorId = vid.trim();
- if (name.length() == 0) {
+ if (name == null || name.length() == 0) {
// If name is missing, use the <path> attribute as done in an addon-3 schema.
name = getPrettyName();
}
@@ -279,6 +296,7 @@
@Override
public void saveProperties(Properties props) {
super.saveProperties(props);
+ mMinToolsMixin.saveProperties(props);
props.setProperty(PkgProps.EXTRA_PATH, mPath);
props.setProperty(PkgProps.EXTRA_NAME_DISPLAY, mDisplayName);
@@ -306,6 +324,15 @@
}
/**
+ * The minimal revision of the tools package required by this extra package, if > 0,
+ * or {@link #MIN_TOOLS_REV_NOT_SPECIFIED} if there is no such requirement.
+ */
+ @Override
+ public FullRevision getMinToolsRevision() {
+ return mMinToolsMixin.getMinToolsRevision();
+ }
+
+ /**
* Returns the minimal API level required by this extra package, if > 0,
* or {@link #MIN_API_LEVEL_NOT_SPECIFIED} if there is no such requirement.
*/
@@ -706,7 +733,7 @@
@Override
public int hashCode() {
final int prime = 31;
- int result = super.hashCode();
+ int result = mMinToolsMixin.hashCode(super.hashCode());
result = prime * result + mMinApiLevel;
result = prime * result + ((mPath == null) ? 0 : mPath.hashCode());
result = prime * result + Arrays.hashCode(mProjectFiles);
@@ -746,6 +773,6 @@
} else if (!mVendorDisplay.equals(other.mVendorDisplay)) {
return false;
}
- return true;
+ return mMinToolsMixin.equals(obj);
}
}
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/FullRevisionPackage.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/FullRevisionPackage.java
index bf63752..305d9fa 100755
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/FullRevisionPackage.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/FullRevisionPackage.java
@@ -80,18 +80,10 @@
super(source, props, revision, license, description, descUrl,
archiveOs, archiveArch, archiveOsPath);
- String revStr = getProperty(props, PkgProps.PKG_REVISION, null);
-
- FullRevision rev = null;
- if (revStr != null) {
- try {
- rev = FullRevision.parseRevision(revStr);
- } catch (NumberFormatException ignore) {}
- }
+ FullRevision rev = PackageParserUtils.getPropertyFullRevision(props);
if (rev == null) {
rev = new FullRevision(revision);
}
-
mPreviewVersion = rev;
}
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/MajorRevisionPackage.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/MajorRevisionPackage.java
index 45a018c..d3c56c8 100755
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/MajorRevisionPackage.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/MajorRevisionPackage.java
@@ -111,6 +111,36 @@
}
@Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + ((mRevision == null) ? 0 : mRevision.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof MajorRevisionPackage)) {
+ return false;
+ }
+ MajorRevisionPackage other = (MajorRevisionPackage) obj;
+ if (mRevision == null) {
+ if (other.mRevision != null) {
+ return false;
+ }
+ } else if (!mRevision.equals(other.mRevision)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
public UpdateInfo canBeUpdatedBy(Package replacementPackage) {
if (replacementPackage == null) {
return UpdateInfo.INCOMPATIBLE;
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/MinToolsMixin.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/MinToolsMixin.java
new file mode 100755
index 0000000..497741f
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/MinToolsMixin.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.internal.repository.packages;
+
+import com.android.sdklib.internal.repository.archives.Archive.Arch;
+import com.android.sdklib.internal.repository.archives.Archive.Os;
+import com.android.sdklib.internal.repository.sources.SdkSource;
+import com.android.sdklib.repository.FullRevision;
+import com.android.sdklib.repository.PkgProps;
+import com.android.sdklib.repository.SdkRepoConstants;
+
+import org.w3c.dom.Node;
+
+import java.util.Properties;
+
+/**
+ * Represents an XML node in an SDK repository that has a min-tools-rev requirement.
+ */
+class MinToolsMixin implements IMinToolsDependency {
+
+ /**
+ * The minimal revision of the tools package required by this extra package, if > 0,
+ * or {@link #MIN_TOOLS_REV_NOT_SPECIFIED} if there is no such requirement.
+ */
+ private final FullRevision mMinToolsRevision;
+
+ /**
+ * Creates a new mixin from the attributes and elements of the given XML node.
+ * This constructor should throw an exception if the package cannot be created.
+ *
+ * @param packageNode The XML element being parsed.
+ */
+ MinToolsMixin(Node packageNode) {
+
+ mMinToolsRevision = PackageParserUtils.parseFullRevisionElement(
+ PackageParserUtils.findChildElement(packageNode, SdkRepoConstants.NODE_MIN_TOOLS_REV));
+ }
+
+ /**
+ * Manually create a new mixin with one archive and the given attributes.
+ * This is used to create packages from local directories in which case there must be
+ * one archive which URL is the actual target location.
+ * <p/>
+ * Properties from props are used first when possible, e.g. if props is non null.
+ * <p/>
+ * By design, this creates a package with one and only one archive.
+ */
+ public MinToolsMixin(
+ SdkSource source,
+ Properties props,
+ int revision,
+ String license,
+ String description,
+ String descUrl,
+ Os archiveOs,
+ Arch archiveArch,
+ String archiveOsPath) {
+
+ String revStr = Package.getProperty(props, PkgProps.MIN_TOOLS_REV, null);
+
+ FullRevision rev = MIN_TOOLS_REV_NOT_SPECIFIED;
+ if (revStr != null) {
+ try {
+ rev = FullRevision.parseRevision(revStr);
+ } catch (NumberFormatException ignore) {}
+ }
+
+ mMinToolsRevision = rev;
+ }
+
+ /**
+ * The minimal revision of the tools package required by this extra package, if > 0,
+ * or {@link #MIN_TOOLS_REV_NOT_SPECIFIED} if there is no such requirement.
+ */
+ @Override
+ public FullRevision getMinToolsRevision() {
+ return mMinToolsRevision;
+ }
+
+ public void saveProperties(Properties props) {
+ if (!getMinToolsRevision().equals(MIN_TOOLS_REV_NOT_SPECIFIED)) {
+ props.setProperty(PkgProps.MIN_TOOLS_REV, getMinToolsRevision().toShortString());
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode(super.hashCode());
+ }
+
+ int hashCode(int superHashCode) {
+ final int prime = 31;
+ int result = superHashCode;
+ result = prime * result + ((mMinToolsRevision == null) ? 0 : mMinToolsRevision.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof IMinToolsDependency)) {
+ return false;
+ }
+ IMinToolsDependency other = (IMinToolsDependency) obj;
+ if (mMinToolsRevision == null) {
+ if (other.getMinToolsRevision() != null) {
+ return false;
+ }
+ } else if (!mMinToolsRevision.equals(other.getMinToolsRevision())) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/MinToolsPackage.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/MinToolsPackage.java
index 049137e..a9069e9 100755
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/MinToolsPackage.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/MinToolsPackage.java
@@ -20,8 +20,6 @@
import com.android.sdklib.internal.repository.archives.Archive.Os;
import com.android.sdklib.internal.repository.sources.SdkSource;
import com.android.sdklib.repository.FullRevision;
-import com.android.sdklib.repository.PkgProps;
-import com.android.sdklib.repository.SdkRepoConstants;
import org.w3c.dom.Node;
@@ -33,11 +31,7 @@
*/
public abstract class MinToolsPackage extends MajorRevisionPackage implements IMinToolsDependency {
- /**
- * The minimal revision of the tools package required by this extra package, if > 0,
- * or {@link #MIN_TOOLS_REV_NOT_SPECIFIED} if there is no such requirement.
- */
- private final FullRevision mMinToolsRevision;
+ private final MinToolsMixin mMinToolsMixin;
/**
* Creates a new package from the attributes and elements of the given XML node.
@@ -52,8 +46,7 @@
MinToolsPackage(SdkSource source, Node packageNode, String nsUri, Map<String,String> licenses) {
super(source, packageNode, nsUri, licenses);
- mMinToolsRevision = PackageParserUtils.parseFullRevisionElement(
- PackageParserUtils.findChildElement(packageNode, SdkRepoConstants.NODE_MIN_TOOLS_REV));
+ mMinToolsMixin = new MinToolsMixin(packageNode);
}
/**
@@ -78,16 +71,16 @@
super(source, props, revision, license, description, descUrl,
archiveOs, archiveArch, archiveOsPath);
- String revStr = getProperty(props, PkgProps.MIN_TOOLS_REV, null);
-
- FullRevision rev = MIN_TOOLS_REV_NOT_SPECIFIED;
- if (revStr != null) {
- try {
- rev = FullRevision.parseRevision(revStr);
- } catch (NumberFormatException ignore) {}
- }
-
- mMinToolsRevision = rev;
+ mMinToolsMixin = new MinToolsMixin(
+ source,
+ props,
+ revision,
+ license,
+ description,
+ descUrl,
+ archiveOs,
+ archiveArch,
+ archiveOsPath);
}
/**
@@ -96,24 +89,18 @@
*/
@Override
public FullRevision getMinToolsRevision() {
- return mMinToolsRevision;
+ return mMinToolsMixin.getMinToolsRevision();
}
@Override
public void saveProperties(Properties props) {
super.saveProperties(props);
-
- if (!getMinToolsRevision().equals(MIN_TOOLS_REV_NOT_SPECIFIED)) {
- props.setProperty(PkgProps.MIN_TOOLS_REV, getMinToolsRevision().toShortString());
- }
+ mMinToolsMixin.saveProperties(props);
}
@Override
public int hashCode() {
- final int prime = 31;
- int result = super.hashCode();
- result = prime * result + ((mMinToolsRevision == null) ? 0 : mMinToolsRevision.hashCode());
- return result;
+ return mMinToolsMixin.hashCode(super.hashCode());
}
@Override
@@ -127,14 +114,6 @@
if (!(obj instanceof MinToolsPackage)) {
return false;
}
- MinToolsPackage other = (MinToolsPackage) obj;
- if (mMinToolsRevision == null) {
- if (other.mMinToolsRevision != null) {
- return false;
- }
- } else if (!mMinToolsRevision.equals(other.mMinToolsRevision)) {
- return false;
- }
- return true;
+ return mMinToolsMixin.equals(obj);
}
}
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/NoPreviewRevisionPackage.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/NoPreviewRevisionPackage.java
new file mode 100755
index 0000000..33274e7
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/NoPreviewRevisionPackage.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.internal.repository.packages;
+
+import com.android.sdklib.internal.repository.archives.Archive.Arch;
+import com.android.sdklib.internal.repository.archives.Archive.Os;
+import com.android.sdklib.internal.repository.sources.SdkSource;
+import com.android.sdklib.repository.NoPreviewRevision;
+import com.android.sdklib.repository.PkgProps;
+import com.android.sdklib.repository.SdkRepoConstants;
+
+import org.w3c.dom.Node;
+
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Represents a package in an SDK repository that has a {@link NoPreviewRevision},
+ * which is a single major.minor.micro revision number and no preview.
+ */
+public abstract class NoPreviewRevisionPackage extends Package {
+
+ private final NoPreviewRevision mRevision;
+
+ /**
+ * Creates a new package from the attributes and elements of the given XML node.
+ * This constructor should throw an exception if the package cannot be created.
+ *
+ * @param source The {@link SdkSource} where this is loaded from.
+ * @param packageNode The XML element being parsed.
+ * @param nsUri The namespace URI of the originating XML document, to be able to deal with
+ * parameters that vary according to the originating XML schema.
+ * @param licenses The licenses loaded from the XML originating document.
+ */
+ NoPreviewRevisionPackage(SdkSource source,
+ Node packageNode,
+ String nsUri,
+ Map<String,String> licenses) {
+ super(source, packageNode, nsUri, licenses);
+
+ mRevision = PackageParserUtils.parseNoPreviewRevisionElement(
+ PackageParserUtils.findChildElement(packageNode, SdkRepoConstants.NODE_REVISION));
+ }
+
+ /**
+ * Manually create a new package with one archive and the given attributes.
+ * This is used to create packages from local directories in which case there must be
+ * one archive which URL is the actual target location.
+ * <p/>
+ * Properties from props are used first when possible, e.g. if props is non null.
+ * <p/>
+ * By design, this creates a package with one and only one archive.
+ */
+ public NoPreviewRevisionPackage(
+ SdkSource source,
+ Properties props,
+ int revision,
+ String license,
+ String description,
+ String descUrl,
+ Os archiveOs,
+ Arch archiveArch,
+ String archiveOsPath) {
+ super(source, props, revision, license, description, descUrl,
+ archiveOs, archiveArch, archiveOsPath);
+
+ String revStr = getProperty(props, PkgProps.PKG_REVISION, null);
+
+ NoPreviewRevision rev = null;
+ if (revStr != null) {
+ try {
+ rev = NoPreviewRevision.parseRevision(revStr);
+ } catch (NumberFormatException ignore) {}
+ }
+ if (rev == null) {
+ rev = new NoPreviewRevision(revision);
+ }
+
+ mRevision = rev;
+ }
+
+ /**
+ * Returns the revision, an int > 0, for all packages (platform, add-on, tool, doc).
+ * Can be 0 if this is a local package of unknown revision.
+ */
+ @Override
+ public NoPreviewRevision getRevision() {
+ return mRevision;
+ }
+
+
+ @Override
+ public void saveProperties(Properties props) {
+ super.saveProperties(props);
+ props.setProperty(PkgProps.PKG_REVISION, mRevision.toString());
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + ((mRevision == null) ? 0 : mRevision.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof NoPreviewRevisionPackage)) {
+ return false;
+ }
+ NoPreviewRevisionPackage other = (NoPreviewRevisionPackage) obj;
+ if (mRevision == null) {
+ if (other.mRevision != null) {
+ return false;
+ }
+ } else if (!mRevision.equals(other.mRevision)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public UpdateInfo canBeUpdatedBy(Package replacementPackage) {
+ if (replacementPackage == null) {
+ return UpdateInfo.INCOMPATIBLE;
+ }
+
+ // check they are the same item.
+ if (!sameItemAs(replacementPackage)) {
+ return UpdateInfo.INCOMPATIBLE;
+ }
+
+ // check revision number
+ if (replacementPackage.getRevision().compareTo(this.getRevision()) > 0) {
+ return UpdateInfo.UPDATE;
+ }
+
+ // not an upgrade but not incompatible either.
+ return UpdateInfo.NOT_UPDATE;
+ }
+
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/Package.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/Package.java
index 1ca90dd..1ecf267 100755
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/Package.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/Package.java
@@ -24,6 +24,7 @@
import com.android.sdklib.AndroidVersion;
import com.android.sdklib.SdkManager;
import com.android.sdklib.internal.repository.IDescription;
+import com.android.sdklib.internal.repository.IListDescription;
import com.android.sdklib.internal.repository.ITaskMonitor;
import com.android.sdklib.internal.repository.archives.Archive;
import com.android.sdklib.internal.repository.archives.Archive.Arch;
@@ -58,7 +59,7 @@
* <p/>
* Derived classes must implement the {@link IDescription} methods.
*/
-public abstract class Package implements IDescription, Comparable<Package> {
+public abstract class Package implements IDescription, IListDescription, Comparable<Package> {
private final String mObsolete;
private final License mLicense;
@@ -305,10 +306,7 @@
@Nullable Properties props,
@NonNull String propKey,
@Nullable String defaultValue) {
- if (props == null) {
- return defaultValue;
- }
- return props.getProperty(propKey, defaultValue);
+ return PackageParserUtils.getProperty(props, propKey, defaultValue);
}
/**
@@ -327,13 +325,7 @@
@Nullable Properties props,
@NonNull String propKey,
int defaultValue) {
- String s = props != null ? props.getProperty(propKey, null) : null;
- if (s != null) {
- try {
- return Integer.parseInt(s);
- } catch (Exception ignore) {}
- }
- return defaultValue;
+ return PackageParserUtils.getPropertyInt(props, propKey, defaultValue);
}
/**
@@ -580,6 +572,7 @@
* In contrast, {@link #getShortDescription()} should be used if you want more details
* such as the package revision number or the API, if applicable.
*/
+ @Override
public abstract String getListDescription();
/**
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/PackageParserUtils.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/PackageParserUtils.java
index a71247b..993a348 100755
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/PackageParserUtils.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/PackageParserUtils.java
@@ -16,11 +16,18 @@
package com.android.sdklib.internal.repository.packages;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.sdklib.repository.FullRevision;
+import com.android.sdklib.repository.NoPreviewRevision;
+import com.android.sdklib.repository.MajorRevision;
+import com.android.sdklib.repository.PkgProps;
import com.android.sdklib.repository.SdkRepoConstants;
import org.w3c.dom.Node;
+import java.util.Properties;
+
/**
* Misc utilities to help extracting elements and attributes out of an XML document.
*/
@@ -70,6 +77,46 @@
}
/**
+ * Parses a no-preview revision element such as <revision>>.
+ * This supports both the single-integer format as well as the full revision
+ * format with major/minor/micro sub-elements.
+ *
+ * @param revisionNode The node to parse.
+ * @return A new {@link NoPreviewRevision}. If parsing failed, major is set to
+ * {@link FullRevision#MISSING_MAJOR_REV}.
+ */
+ public static NoPreviewRevision parseNoPreviewRevisionElement(Node revisionNode) {
+ // This needs to support two modes:
+ // - For addon XSD >= 6, <revision> contains sub-elements such as <major> or <minor>.
+ // - Otherwise for addon XSD < 6, <revision> contains an integer.
+ // The <major> element is mandatory, so it's easy to distinguish between both cases.
+ int major = FullRevision.MISSING_MAJOR_REV,
+ minor = FullRevision.IMPLICIT_MINOR_REV,
+ micro = FullRevision.IMPLICIT_MICRO_REV;
+
+ if (revisionNode != null) {
+ if (PackageParserUtils.findChildElement(revisionNode,
+ SdkRepoConstants.NODE_MAJOR_REV) != null) {
+ // <revision> has a <major> sub-element, so it's a repository XSD >= 7.
+ major = PackageParserUtils.getXmlInt(revisionNode,
+ SdkRepoConstants.NODE_MAJOR_REV, FullRevision.MISSING_MAJOR_REV);
+ minor = PackageParserUtils.getXmlInt(revisionNode,
+ SdkRepoConstants.NODE_MINOR_REV, FullRevision.IMPLICIT_MINOR_REV);
+ micro = PackageParserUtils.getXmlInt(revisionNode,
+ SdkRepoConstants.NODE_MICRO_REV, FullRevision.IMPLICIT_MICRO_REV);
+ } else {
+ try {
+ String majorStr = revisionNode.getTextContent().trim();
+ major = Integer.parseInt(majorStr);
+ } catch (Exception e) {
+ }
+ }
+ }
+
+ return new NoPreviewRevision(major, minor, micro);
+ }
+
+ /**
* Returns the first child element with the given XML local name.
* If xmlLocalName is null, returns the very first child element.
*/
@@ -178,4 +225,116 @@
return defaultValue;
}
+ /**
+ * Utility method that returns a property from a {@link Properties} object.
+ * Returns the default value if props is null or if the property is not defined.
+ *
+ * @param props The {@link Properties} to search into.
+ * If null, the default value is returned.
+ * @param propKey The name of the property. Must not be null.
+ * @param defaultValue The default value to return if {@code props} is null or if the
+ * key is not found. Can be null.
+ * @return The string value of the given key in the properties, or null if the key
+ * isn't found or if {@code props} is null.
+ */
+ @Nullable
+ public static String getProperty(
+ @Nullable Properties props,
+ @NonNull String propKey,
+ @Nullable String defaultValue) {
+ if (props == null) {
+ return defaultValue;
+ }
+ return props.getProperty(propKey, defaultValue);
+ }
+
+ /**
+ * Utility method that returns an integer property from a {@link Properties} object.
+ * Returns the default value if props is null or if the property is not defined or
+ * cannot be parsed to an integer.
+ *
+ * @param props The {@link Properties} to search into.
+ * If null, the default value is returned.
+ * @param propKey The name of the property. Must not be null.
+ * @param defaultValue The default value to return if {@code props} is null or if the
+ * key is not found. Can be null.
+ * @return The integer value of the given key in the properties, or the {@code defaultValue}.
+ */
+ public static int getPropertyInt(
+ @Nullable Properties props,
+ @NonNull String propKey,
+ int defaultValue) {
+ String s = props != null ? props.getProperty(propKey, null) : null;
+ if (s != null) {
+ try {
+ return Integer.parseInt(s);
+ } catch (Exception ignore) {}
+ }
+ return defaultValue;
+ }
+
+ /**
+ * Utility method to parse the {@link PkgProps#PKG_REVISION} property as a full
+ * revision (major.minor.micro.preview).
+ *
+ * @param props The properties to parse.
+ * @return A {@link FullRevision} or null if there is no such property or it couldn't be parsed.
+ */
+ @Nullable
+ public static FullRevision getPropertyFullRevision(@Nullable Properties props) {
+ String revStr = getProperty(props, PkgProps.PKG_REVISION, null);
+
+ FullRevision rev = null;
+ if (revStr != null) {
+ try {
+ rev = FullRevision.parseRevision(revStr);
+ } catch (NumberFormatException ignore) {}
+ }
+
+ return rev;
+ }
+
+ /**
+ * Utility method to parse the {@link PkgProps#PKG_REVISION} property as a major
+ * revision (major integer, no minor/micro/preview parts.)
+ *
+ * @param props The properties to parse.
+ * @return A {@link MajorRevision} or null if there is no such property or it couldn't be parsed.
+ */
+ @Nullable
+ public static MajorRevision getPropertyMajorRevision(@Nullable Properties props) {
+ String revStr = getProperty(props, PkgProps.PKG_REVISION, null);
+
+ MajorRevision rev = null;
+ if (revStr != null) {
+ try {
+ rev = MajorRevision.parseRevision(revStr);
+ } catch (NumberFormatException ignore) {}
+ }
+
+ return rev;
+ }
+
+ /**
+ * Utility method to parse the {@link PkgProps#PKG_REVISION} property as a no-preview
+ * revision (major.minor.micro integers but no preview part.)
+ *
+ * @param props The properties to parse.
+ * @return A {@link NoPreviewRevision} or
+ * null if there is no such property or it couldn't be parsed.
+ */
+ @Nullable
+ public static NoPreviewRevision getPropertyNoPreviewRevision(@Nullable Properties props) {
+ String revStr = getProperty(props, PkgProps.PKG_REVISION, null);
+
+ NoPreviewRevision rev = null;
+ if (revStr != null) {
+ try {
+ rev = NoPreviewRevision.parseRevision(revStr);
+ } catch (NumberFormatException ignore) {}
+ }
+
+ return rev;
+ }
+
}
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/PlatformPackage.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/PlatformPackage.java
index bb3e303..31f36be 100755
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/PlatformPackage.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/packages/PlatformPackage.java
@@ -18,6 +18,7 @@
import com.android.SdkConstants;
import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.annotations.VisibleForTesting;
import com.android.annotations.VisibleForTesting.Visibility;
import com.android.sdklib.AndroidTargetHash;
@@ -99,17 +100,19 @@
* <p/>
* By design, this creates a package with one and only one archive.
*/
- public static Package create(IAndroidTarget target, Properties props) {
+ public static Package create(@NonNull IAndroidTarget target, @Nullable Properties props) {
return new PlatformPackage(target, props);
}
@VisibleForTesting(visibility=Visibility.PRIVATE)
- protected PlatformPackage(IAndroidTarget target, Properties props) {
+ protected PlatformPackage(@NonNull IAndroidTarget target, @Nullable Properties props) {
this(null /*source*/, target, props);
}
@VisibleForTesting(visibility=Visibility.PRIVATE)
- protected PlatformPackage(SdkSource source, IAndroidTarget target, Properties props) {
+ protected PlatformPackage(@Nullable SdkSource source,
+ @NonNull IAndroidTarget target,
+ @Nullable Properties props) {
super( source, //source
props, //properties
target.getRevision(), //revision
diff --git a/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkAddonSource.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkAddonSource.java
index 98bfc5a..4994db6 100755
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkAddonSource.java
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/sources/SdkAddonSource.java
@@ -59,7 +59,6 @@
return false;
}
-
@Override
protected String[] getDefaultXmlFileUrls() {
return new String[] { SdkAddonConstants.URL_DEFAULT_FILENAME };
diff --git a/sdklib/src/main/java/com/android/sdklib/io/FileOp.java b/sdklib/src/main/java/com/android/sdklib/io/FileOp.java
index aeb45e1..a15e6fd 100755
--- a/sdklib/src/main/java/com/android/sdklib/io/FileOp.java
+++ b/sdklib/src/main/java/com/android/sdklib/io/FileOp.java
@@ -25,6 +25,7 @@
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -70,7 +71,7 @@
* @param segments Individual folder or filename segments to append to the base file.
* @return A new file representing the concatenation of the base path with all the segments.
*/
- public static File append(File base, String...segments) {
+ public static File append(@NonNull File base, @NonNull String...segments) {
for (String segment : segments) {
base = new File(base, segment);
}
@@ -84,7 +85,7 @@
* @param segments Individual folder or filename segments to append to the base path.
* @return A new file representing the concatenation of the base path with all the segments.
*/
- public static File append(String base, String...segments) {
+ public static File append(@NonNull String base, @NonNull String...segments) {
return append(new File(base), segments);
}
@@ -96,7 +97,7 @@
* The argument can be null.
*/
@Override
- public void deleteFileOrFolder(File fileOrFolder) {
+ public void deleteFileOrFolder(@NonNull File fileOrFolder) {
if (fileOrFolder != null) {
if (isDirectory(fileOrFolder)) {
// Must delete content recursively first
@@ -158,7 +159,7 @@
* @throws IOException If an I/O error occurs
*/
@Override
- public void setExecutablePermission(File file) throws IOException {
+ public void setExecutablePermission(@NonNull File file) throws IOException {
if (sFileSetExecutable != null) {
try {
@@ -179,7 +180,7 @@
}
@Override
- public void setReadOnly(File file) {
+ public void setReadOnly(@NonNull File file) {
file.setReadOnly();
}
@@ -192,7 +193,7 @@
* @throws IOException if there's a problem reading or writing the file.
*/
@Override
- public void copyFile(File source, File dest) throws IOException {
+ public void copyFile(@NonNull File source, @NonNull File dest) throws IOException {
byte[] buffer = new byte[8192];
FileInputStream fis = null;
@@ -227,15 +228,15 @@
/**
* Checks whether 2 binary files are the same.
*
- * @param source the source file to copy
- * @param destination the destination file to write
+ * @param file1 the source file to copy
+ * @param file2 the destination file to write
* @throws FileNotFoundException if the source files don't exist.
* @throws IOException if there's a problem reading the files.
*/
@Override
- public boolean isSameFile(File source, File destination) throws IOException {
+ public boolean isSameFile(@NonNull File file1, @NonNull File file2) throws IOException {
- if (source.length() != destination.length()) {
+ if (file1.length() != file2.length()) {
return false;
}
@@ -243,8 +244,8 @@
FileInputStream fis2 = null;
try {
- fis1 = new FileInputStream(source);
- fis2 = new FileInputStream(destination);
+ fis1 = new FileInputStream(file1);
+ fis2 = new FileInputStream(file2);
byte[] buffer1 = new byte[8192];
byte[] buffer2 = new byte[8192];
@@ -289,25 +290,25 @@
/** Invokes {@link File#isFile()} on the given {@code file}. */
@Override
- public boolean isFile(File file) {
+ public boolean isFile(@NonNull File file) {
return file.isFile();
}
/** Invokes {@link File#isDirectory()} on the given {@code file}. */
@Override
- public boolean isDirectory(File file) {
+ public boolean isDirectory(@NonNull File file) {
return file.isDirectory();
}
/** Invokes {@link File#exists()} on the given {@code file}. */
@Override
- public boolean exists(File file) {
+ public boolean exists(@NonNull File file) {
return file.exists();
}
/** Invokes {@link File#length()} on the given {@code file}. */
@Override
- public long length(File file) {
+ public long length(@NonNull File file) {
return file.length();
}
@@ -316,36 +317,54 @@
* Note: for a recursive folder version, consider {@link #deleteFileOrFolder(File)}.
*/
@Override
- public boolean delete(File file) {
+ public boolean delete(@NonNull File file) {
return file.delete();
}
/** Invokes {@link File#mkdirs()} on the given {@code file}. */
@Override
- public boolean mkdirs(File file) {
+ public boolean mkdirs(@NonNull File file) {
return file.mkdirs();
}
- /** Invokes {@link File#listFiles()} on the given {@code file}. */
+ /**
+ * Invokes {@link File#listFiles()} on the given {@code file}.
+ * Contrary to the Java API, this returns an empty array instead of null when the
+ * directory does not exist.
+ */
@Override
- public File[] listFiles(File file) {
- return file.listFiles();
+ @NonNull
+ public File[] listFiles(@NonNull File file) {
+ File[] r = file.listFiles();
+ if (r == null) {
+ return new File[0];
+ } else {
+ return r;
+ }
}
/** Invokes {@link File#renameTo(File)} on the given files. */
@Override
- public boolean renameTo(File oldFile, File newFile) {
+ public boolean renameTo(@NonNull File oldFile, @NonNull File newFile) {
return oldFile.renameTo(newFile);
}
- /** Creates a new {@link FileOutputStream} for the given {@code file}. */
+ /** Creates a new {@link OutputStream} for the given {@code file}. */
@Override
- public OutputStream newFileOutputStream(File file) throws FileNotFoundException {
+ @NonNull
+ public OutputStream newFileOutputStream(@NonNull File file) throws FileNotFoundException {
return new FileOutputStream(file);
}
- @NonNull
+ /** Creates a new {@link InputStream} for the given {@code file}. */
@Override
+ @NonNull
+ public InputStream newFileInputStream(@NonNull File file) throws FileNotFoundException {
+ return new FileInputStream(file);
+ }
+
+ @Override
+ @NonNull
@SuppressWarnings("resource") // Eclipse doesn't understand Closeables.closeQuietly
public Properties loadProperties(@NonNull File file) {
Properties props = new Properties();
@@ -360,8 +379,8 @@
return props;
}
- @SuppressWarnings("resource") // Eclipse doesn't understand Closeables.closeQuietly
@Override
+ @SuppressWarnings("resource") // Eclipse doesn't understand Closeables.closeQuietly
public boolean saveProperties(@NonNull File file, @NonNull Properties props,
@NonNull String comments) {
OutputStream fos = null;
@@ -377,4 +396,9 @@
return false;
}
+
+ @Override
+ public long lastModified(@NonNull File file) {
+ return file.lastModified();
+ }
}
diff --git a/sdklib/src/main/java/com/android/sdklib/io/IFileOp.java b/sdklib/src/main/java/com/android/sdklib/io/IFileOp.java
index 7cebd4e..0aa784c 100755
--- a/sdklib/src/main/java/com/android/sdklib/io/IFileOp.java
+++ b/sdklib/src/main/java/com/android/sdklib/io/IFileOp.java
@@ -20,8 +20,8 @@
import java.io.File;
import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.OutputStream;
import java.util.Properties;
@@ -40,7 +40,7 @@
* It's ok for the file or folder to not exist at all.
* The argument can be null.
*/
- public abstract void deleteFileOrFolder(File fileOrFolder);
+ public abstract void deleteFileOrFolder(@NonNull File fileOrFolder);
/**
* Sets the executable Unix permission (+x) on a file or folder.
@@ -55,14 +55,14 @@
* @param file The file to set permissions on.
* @throws IOException If an I/O error occurs
*/
- public abstract void setExecutablePermission(File file) throws IOException;
+ public abstract void setExecutablePermission(@NonNull File file) throws IOException;
/**
* Sets the file or directory as read-only.
*
* @param file The file or directory to set permissions on.
*/
- public abstract void setReadOnly(File file);
+ public abstract void setReadOnly(@NonNull File file);
/**
* Copies a binary file.
@@ -72,48 +72,60 @@
* @throws FileNotFoundException if the source file doesn't exist.
* @throws IOException if there's a problem reading or writing the file.
*/
- public abstract void copyFile(File source, File dest) throws IOException;
+ public abstract void copyFile(@NonNull File source, @NonNull File dest) throws IOException;
/**
* Checks whether 2 binary files are the same.
*
- * @param source the source file to copy
- * @param destination the destination file to write
+ * @param file1 the source file to copy
+ * @param file2 the destination file to write
* @throws FileNotFoundException if the source files don't exist.
* @throws IOException if there's a problem reading the files.
*/
- public abstract boolean isSameFile(File source, File destination)
+ public abstract boolean isSameFile(@NonNull File file1, @NonNull File file2)
throws IOException;
/** Invokes {@link File#exists()} on the given {@code file}. */
- public abstract boolean exists(File file);
+ public abstract boolean exists(@NonNull File file);
/** Invokes {@link File#isFile()} on the given {@code file}. */
- public abstract boolean isFile(File file);
+ public abstract boolean isFile(@NonNull File file);
/** Invokes {@link File#isDirectory()} on the given {@code file}. */
- public abstract boolean isDirectory(File file);
+ public abstract boolean isDirectory(@NonNull File file);
/** Invokes {@link File#length()} on the given {@code file}. */
- public abstract long length(File file);
+ public abstract long length(@NonNull File file);
/**
* Invokes {@link File#delete()} on the given {@code file}.
* Note: for a recursive folder version, consider {@link #deleteFileOrFolder(File)}.
*/
- public abstract boolean delete(File file);
+ public abstract boolean delete(@NonNull File file);
/** Invokes {@link File#mkdirs()} on the given {@code file}. */
- public abstract boolean mkdirs(File file);
+ public abstract boolean mkdirs(@NonNull File file);
- /** Invokes {@link File#listFiles()} on the given {@code file}. */
- public abstract File[] listFiles(File file);
+ /**
+ * Invokes {@link File#listFiles()} on the given {@code file}.
+ * Contrary to the Java API, this returns an empty array instead of null when the
+ * directory does not exist.
+ */
+ @NonNull
+ public abstract File[] listFiles(@NonNull File file);
/** Invokes {@link File#renameTo(File)} on the given files. */
- public abstract boolean renameTo(File oldDir, File newDir);
+ public abstract boolean renameTo(@NonNull File oldDir, @NonNull File newDir);
- /** Creates a new {@link FileOutputStream} for the given {@code file}. */
- public abstract OutputStream newFileOutputStream(File file) throws FileNotFoundException;
+ /** Creates a new {@link OutputStream} for the given {@code file}. */
+ @NonNull
+ public abstract OutputStream newFileOutputStream(@NonNull File file)
+ throws FileNotFoundException;
+
+ /** Creates a new {@link InputStream} for the given {@code file}. */
+ @NonNull
+ public abstract InputStream newFileInputStream(@NonNull File file)
+ throws FileNotFoundException;
/**
* Load {@link Properties} from a file. Returns an empty property set on error.
@@ -137,4 +149,13 @@
@NonNull File file,
@NonNull Properties props,
@NonNull String comments);
+
+ /**
+ * Returns the lastModified attribute of the file.
+ *
+ * @see File#lastModified()
+ * @param file The non-null file of which to retrieve the lastModified attribute.
+ * @return The last-modified attribute of the file, in milliseconds since The Epoch.
+ */
+ long lastModified(@NonNull File file);
}
diff --git a/sdklib/src/main/java/com/android/sdklib/local/LocalAddonPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/local/LocalAddonPkgInfo.java
new file mode 100755
index 0000000..6181501
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/local/LocalAddonPkgInfo.java
@@ -0,0 +1,445 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.local;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.AndroidTargetHash;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.ISystemImage;
+import com.android.sdklib.ISystemImage.LocationType;
+import com.android.sdklib.SystemImage;
+import com.android.sdklib.internal.androidTarget.AddOnTarget;
+import com.android.sdklib.internal.androidTarget.PlatformTarget;
+import com.android.sdklib.internal.project.ProjectProperties;
+import com.android.sdklib.internal.repository.packages.AddonPackage;
+import com.android.sdklib.internal.repository.packages.Package;
+import com.android.sdklib.io.IFileOp;
+import com.android.sdklib.repository.MajorRevision;
+import com.android.utils.Pair;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@SuppressWarnings("MethodMayBeStatic")
+public class LocalAddonPkgInfo extends LocalPlatformPkgInfo {
+
+ public static final String ADDON_NAME = "name"; //$NON-NLS-1$
+ public static final String ADDON_VENDOR = "vendor"; //$NON-NLS-1$
+ public static final String ADDON_API = "api"; //$NON-NLS-1$
+ public static final String ADDON_DESCRIPTION = "description"; //$NON-NLS-1$
+ public static final String ADDON_LIBRARIES = "libraries"; //$NON-NLS-1$
+ public static final String ADDON_DEFAULT_SKIN = "skin"; //$NON-NLS-1$
+ public static final String ADDON_USB_VENDOR = "usb-vendor"; //$NON-NLS-1$
+ public static final String ADDON_REVISION = "revision"; //$NON-NLS-1$
+ public static final String ADDON_REVISION_OLD = "version"; //$NON-NLS-1$
+
+ private static final Pattern PATTERN_LIB_DATA = Pattern.compile(
+ "^([a-zA-Z0-9._-]+\\.jar);(.*)$", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
+
+ // usb ids are 16-bit hexadecimal values.
+ private static final Pattern PATTERN_USB_IDS = Pattern.compile(
+ "^0x[a-f0-9]{4}$", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
+
+ public LocalAddonPkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull AndroidVersion version,
+ @NonNull MajorRevision revision) {
+ super(localSdk, localDir, sourceProps, version, revision);
+ }
+
+ @Override
+ public int getType() {
+ return LocalSdk.PKG_ADDONS;
+ }
+
+ @NonNull
+ @Override
+ public String getTargetHash() {
+ IAndroidTarget target = getAndroidTarget();
+
+ String vendor = null;
+ String name = null;
+
+ if (target != null) {
+ vendor = target.getVendor();
+ name = target.getName();
+ } else {
+ Pair<Map<String, String>, String> infos = parseAddonProperties();
+ Map<String, String> map = infos.getFirst();
+ if (map != null) {
+ vendor = map.get(ADDON_VENDOR);
+ name = map.get(ADDON_NAME);
+ }
+ }
+
+ if (vendor == null || name == null) {
+ return "invalid";
+ }
+
+ return AndroidTargetHash.getAddonHashString(
+ vendor,
+ name,
+ getAndroidVersion());
+ }
+
+ //-----
+
+ /**
+ * Creates an AddonPackage wrapping the IAndroidTarget if defined.
+ * Invoked by {@link #getPackage()}.
+ *
+ * @return A Package or null if target isn't available.
+ */
+ @Override
+ @Nullable
+ protected Package createPackage() {
+ IAndroidTarget target = getAndroidTarget();
+ if (target != null) {
+ return AddonPackage.create(target, getSourceProperties());
+ }
+ return null;
+ }
+
+ /**
+ * Creates the AddOnTarget. Invoked by {@link #getAndroidTarget()}.
+ */
+ @Override
+ @Nullable
+ protected IAndroidTarget createAndroidTarget() {
+ LocalSdk sdk = getLocalSdk();
+ IFileOp fileOp = sdk.getFileOp();
+
+ // Parse the addon properties to ensure we can load it.
+ Pair<Map<String, String>, String> infos = parseAddonProperties();
+
+ Map<String, String> propertyMap = infos.getFirst();
+ String error = infos.getSecond();
+
+ if (error != null) {
+ appendLoadError("Ignoring add-on '%1$s': %2$s", getLocalDir().getName(), error);
+ return null;
+ }
+
+ // Since error==null we're not supposed to encounter any issues loading this add-on.
+ try {
+ assert propertyMap != null;
+
+ String api = propertyMap.get(ADDON_API);
+ String name = propertyMap.get(ADDON_NAME);
+ String vendor = propertyMap.get(ADDON_VENDOR);
+
+ assert api != null;
+ assert name != null;
+ assert vendor != null;
+
+ PlatformTarget baseTarget = null;
+
+ // Look for a platform that has a matching api level or codename.
+ LocalPkgInfo plat = sdk.getPkgInfo(LocalSdk.PKG_PLATFORMS, getAndroidVersion());
+ if (plat instanceof LocalPlatformPkgInfo) {
+ baseTarget = (PlatformTarget) ((LocalPlatformPkgInfo) plat).getAndroidTarget();
+ }
+ assert baseTarget != null;
+
+ // get the optional description
+ String description = propertyMap.get(ADDON_DESCRIPTION);
+
+ // get the add-on revision
+ int revisionValue = 1;
+ String revision = propertyMap.get(ADDON_REVISION);
+ if (revision == null) {
+ revision = propertyMap.get(ADDON_REVISION_OLD);
+ }
+ if (revision != null) {
+ revisionValue = Integer.parseInt(revision);
+ }
+
+ // get the optional libraries
+ String librariesValue = propertyMap.get(ADDON_LIBRARIES);
+ Map<String, String[]> libMap = null;
+
+ if (librariesValue != null) {
+ librariesValue = librariesValue.trim();
+ if (!librariesValue.isEmpty()) {
+ // split in the string into the libraries name
+ String[] libraries = librariesValue.split(";"); //$NON-NLS-1$
+ if (libraries.length > 0) {
+ libMap = new HashMap<String, String[]>();
+ for (String libName : libraries) {
+ libName = libName.trim();
+
+ // get the library data from the properties
+ String libData = propertyMap.get(libName);
+
+ if (libData != null) {
+ // split the jar file from the description
+ Matcher m = PATTERN_LIB_DATA.matcher(libData);
+ if (m.matches()) {
+ libMap.put(libName, new String[] {
+ m.group(1), m.group(2) });
+ } else {
+ appendLoadError(
+ "Ignoring library '%1$s', property value has wrong format\n\t%2$s",
+ libName, libData);
+ }
+ } else {
+ appendLoadError(
+ "Ignoring library '%1$s', missing property value",
+ libName, libData);
+ }
+ }
+ }
+ }
+ }
+
+ // get the abi list.
+ ISystemImage[] systemImages = getAddonSystemImages();
+
+ // check whether the add-on provides its own rendering info/library.
+ boolean hasRenderingLibrary = false;
+ boolean hasRenderingResources = false;
+
+ File dataFolder = new File(getLocalDir(), SdkConstants.FD_DATA);
+ if (fileOp.isDirectory(dataFolder)) {
+ hasRenderingLibrary =
+ fileOp.isFile(new File(dataFolder, SdkConstants.FN_LAYOUTLIB_JAR));
+ hasRenderingResources =
+ fileOp.isDirectory(new File(dataFolder, SdkConstants.FD_RES)) &&
+ fileOp.isDirectory(new File(dataFolder, SdkConstants.FD_FONTS));
+ }
+
+ AddOnTarget target = new AddOnTarget(
+ getLocalDir().getAbsolutePath(),
+ name,
+ vendor,
+ revisionValue,
+ description,
+ systemImages,
+ libMap,
+ hasRenderingLibrary,
+ hasRenderingResources,
+ baseTarget);
+
+ // need to parse the skins.
+ String[] skins = parseSkinFolder(target.getPath(IAndroidTarget.SKINS));
+
+ // get the default skin, or take it from the base platform if needed.
+ String defaultSkin = propertyMap.get(ADDON_DEFAULT_SKIN);
+ if (defaultSkin == null) {
+ if (skins.length == 1) {
+ defaultSkin = skins[0];
+ } else {
+ defaultSkin = baseTarget.getDefaultSkin();
+ }
+ }
+
+ // get the USB ID (if available)
+ int usbVendorId = convertId(propertyMap.get(ADDON_USB_VENDOR));
+ if (usbVendorId != IAndroidTarget.NO_USB_ID) {
+ target.setUsbVendorId(usbVendorId);
+ }
+
+ target.setSkins(skins, defaultSkin);
+
+ return target;
+
+ } catch (Exception e) {
+ appendLoadError("Ignoring add-on '%1$s': error %2$s.",
+ getLocalDir().getName(), e.toString());
+ }
+
+ return null;
+
+ }
+
+ /**
+ * Parses the add-on properties and decodes any error that occurs when loading an addon.
+ *
+ * @return A pair with the property map and an error string. Both can be null but not at the
+ * same time. If a non-null error is present then the property map must be ignored. The error
+ * should be translatable as it might show up in the SdkManager UI.
+ */
+ @NonNull
+ private Pair<Map<String, String>, String> parseAddonProperties() {
+ Map<String, String> propertyMap = null;
+ String error = null;
+
+ IFileOp fileOp = getLocalSdk().getFileOp();
+ File addOnManifest = new File(getLocalDir(), SdkConstants.FN_MANIFEST_INI);
+
+ do {
+ if (!fileOp.isFile(addOnManifest)) {
+ error = String.format("File not found: %1$s", SdkConstants.FN_MANIFEST_INI);
+ break;
+ }
+
+ try {
+ propertyMap = ProjectProperties.parsePropertyStream(
+ fileOp.newFileInputStream(addOnManifest),
+ addOnManifest.getPath(),
+ null /*log*/);
+ if (propertyMap == null) {
+ error = String.format("Failed to parse properties from %1$s",
+ SdkConstants.FN_MANIFEST_INI);
+ break;
+ }
+ } catch (FileNotFoundException ignore) {}
+ assert propertyMap != null;
+
+ // look for some specific values in the map.
+ // we require name, vendor, and api
+ String name = propertyMap.get(ADDON_NAME);
+ if (name == null) {
+ error = addonManifestWarning(ADDON_NAME);
+ break;
+ }
+
+ String vendor = propertyMap.get(ADDON_VENDOR);
+ if (vendor == null) {
+ error = addonManifestWarning(ADDON_VENDOR);
+ break;
+ }
+
+ String api = propertyMap.get(ADDON_API);
+ if (api == null) {
+ error = addonManifestWarning(ADDON_API);
+ break;
+ }
+
+ // Look for a platform that has a matching api level or codename.
+ IAndroidTarget baseTarget = null;
+ LocalPkgInfo plat = getLocalSdk().getPkgInfo(LocalSdk.PKG_PLATFORMS,
+ getAndroidVersion());
+ if (plat instanceof LocalPlatformPkgInfo) {
+ baseTarget = ((LocalPlatformPkgInfo) plat).getAndroidTarget();
+ }
+
+ if (baseTarget == null) {
+ error = String.format("Unable to find base platform with API level '%1$s'", api);
+ break;
+ }
+
+ // get the add-on revision
+ String revision = propertyMap.get(ADDON_REVISION);
+ if (revision == null) {
+ revision = propertyMap.get(ADDON_REVISION_OLD);
+ }
+ if (revision != null) {
+ try {
+ Integer.parseInt(revision);
+ } catch (NumberFormatException e) {
+ // looks like revision does not parse to a number.
+ error = String.format("%1$s is not a valid number in %2$s.",
+ ADDON_REVISION, SdkConstants.FN_BUILD_PROP);
+ break;
+ }
+ }
+
+ } while(false);
+
+ return Pair.of(propertyMap, error);
+ }
+
+ /**
+ * Prepares a warning about the addon being ignored due to a missing manifest value.
+ * This string will show up in the SdkManager UI.
+ *
+ * @param valueName The missing manifest value, for display.
+ */
+ @NonNull
+ private static String addonManifestWarning(@NonNull String valueName) {
+ return String.format("'%1$s' is missing from %2$s.",
+ valueName, SdkConstants.FN_MANIFEST_INI);
+ }
+
+ /**
+ * Converts a string representation of an hexadecimal ID into an int.
+ * @param value the string to convert.
+ * @return the int value, or {@link IAndroidTarget#NO_USB_ID} if the conversion failed.
+ */
+ private int convertId(@Nullable String value) {
+ if (value != null && !value.isEmpty()) {
+ if (PATTERN_USB_IDS.matcher(value).matches()) {
+ String v = value.substring(2);
+ try {
+ return Integer.parseInt(v, 16);
+ } catch (NumberFormatException e) {
+ // this shouldn't happen since we check the pattern above, but this is safer.
+ // the method will return 0 below.
+ }
+ }
+ }
+
+ return IAndroidTarget.NO_USB_ID;
+ }
+
+ /**
+ * Get all the system images supported by an add-on target.
+ * For an add-on, we first look for sub-folders in the addon/images directory.
+ * If none are found but the directory exists and is not empty, assume it's a legacy
+ * arm eabi system image.
+ * <p/>
+ * Note that it's OK for an add-on to have no system-images at all, since it can always
+ * rely on the ones from its base platform.
+ *
+ * @return an array of ISystemImage containing all the system images for the target.
+ * The list can be empty but not null.
+ */
+ @NonNull
+ private ISystemImage[] getAddonSystemImages() {
+ Set<ISystemImage> found = new TreeSet<ISystemImage>();
+
+ IFileOp fileOp = getLocalSdk().getFileOp();
+ File imagesDir = new File(getLocalDir(), SdkConstants.OS_IMAGES_FOLDER);
+
+ // Look for sub-directories
+ boolean hasImgFiles = false;
+ File[] files = fileOp.listFiles(imagesDir);
+ for (File file : files) {
+ if (fileOp.isDirectory(file)) {
+ found.add(new SystemImage(file,
+ LocationType.IN_PLATFORM_SUBFOLDER,
+ file.getName()));
+ } else if (!hasImgFiles && fileOp.isFile(file)) {
+ if (file.getName().endsWith(".img")) { //$NON-NLS-1$
+ hasImgFiles = true;
+ }
+ }
+ }
+
+ if (found.isEmpty() && hasImgFiles && fileOp.isDirectory(imagesDir)) {
+ // We found no sub-folder system images but it looks like the top directory
+ // has some img files in it. It must be a legacy ARM EABI system image folder.
+ found.add(new SystemImage(imagesDir,
+ LocationType.IN_PLATFORM_LEGACY,
+ SdkConstants.ABI_ARMEABI));
+ }
+
+ return found.toArray(new ISystemImage[found.size()]);
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/local/LocalAndroidVersionPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/local/LocalAndroidVersionPkgInfo.java
new file mode 100755
index 0000000..e5b2d48
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/local/LocalAndroidVersionPkgInfo.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.local;
+
+import com.android.annotations.NonNull;
+import com.android.sdklib.AndroidVersion;
+
+import java.io.File;
+import java.util.Properties;
+
+abstract class LocalAndroidVersionPkgInfo extends LocalPkgInfo {
+
+ @NonNull
+ private final AndroidVersion mVersion;
+
+ public LocalAndroidVersionPkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull AndroidVersion version) {
+ super(localSdk, localDir, sourceProps);
+ mVersion = version;
+ }
+
+ @Override
+ public boolean hasAndroidVersion() {
+ return true;
+ }
+
+ @NonNull
+ @Override
+ public AndroidVersion getAndroidVersion() {
+ return mVersion;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/local/LocalBuildToolPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/local/LocalBuildToolPkgInfo.java
new file mode 100755
index 0000000..7e11470
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/local/LocalBuildToolPkgInfo.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.local;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.internal.repository.packages.BuildToolPackage;
+import com.android.sdklib.internal.repository.packages.Package;
+import com.android.sdklib.repository.FullRevision;
+
+import java.io.File;
+import java.util.Properties;
+
+public class LocalBuildToolPkgInfo extends LocalFullRevisionPkgInfo {
+
+ @Nullable
+ private final BuildToolInfo mBuildToolInfo;
+
+ public LocalBuildToolPkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull FullRevision revision,
+ @Nullable BuildToolInfo btInfo) {
+ super(localSdk, localDir, sourceProps, revision);
+ mBuildToolInfo = btInfo;
+ }
+
+ @Override
+ public int getType() {
+ return LocalSdk.PKG_BUILD_TOOLS;
+ }
+
+ @Nullable
+ public BuildToolInfo getBuildToolInfo() {
+ return mBuildToolInfo;
+ }
+
+ @Nullable
+ @Override
+ public Package getPackage() {
+ Package pkg = super.getPackage();
+ if (pkg == null) {
+ try {
+ pkg = BuildToolPackage.create(getLocalDir(), getSourceProperties());
+ setPackage(pkg);
+ } catch (Exception e) {
+ appendLoadError("Failed to parse package: %1$s", e.toString());
+ }
+ }
+ return pkg;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/local/LocalDocPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/local/LocalDocPkgInfo.java
new file mode 100755
index 0000000..8c3af52
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/local/LocalDocPkgInfo.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.local;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.internal.repository.archives.Archive.Arch;
+import com.android.sdklib.internal.repository.archives.Archive.Os;
+import com.android.sdklib.internal.repository.packages.DocPackage;
+import com.android.sdklib.internal.repository.packages.Package;
+import com.android.sdklib.repository.MajorRevision;
+
+import java.io.File;
+import java.util.Properties;
+
+public class LocalDocPkgInfo extends LocalMajorRevisionPkgInfo {
+
+ public LocalDocPkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull MajorRevision revision) {
+ super(localSdk, localDir, sourceProps, revision);
+ }
+
+ @Override
+ public int getType() {
+ return LocalSdk.PKG_DOCS;
+ }
+
+ @Nullable
+ @Override
+ public Package getPackage() {
+ Package pkg = super.getPackage();
+ if (pkg == null) {
+ try {
+ pkg = DocPackage.create(
+ null, //source
+ getSourceProperties(), //properties
+ 0, //apiLevel
+ null, //codename
+ 0, //revision
+ null, //license
+ null, //description
+ null, //descUrl
+ Os.getCurrentOs(), //archiveOs
+ Arch.getCurrentArch(), //archiveArch
+ getLocalDir().getPath() //archiveOsPath
+ );
+ setPackage(pkg);
+ } catch (Exception e) {
+ appendLoadError("Failed to parse package: %1$s", e.toString());
+ }
+ }
+ return pkg;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/local/LocalExtraPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/local/LocalExtraPkgInfo.java
new file mode 100755
index 0000000..90fbbfb
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/local/LocalExtraPkgInfo.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.local;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.internal.repository.archives.Archive.Arch;
+import com.android.sdklib.internal.repository.archives.Archive.Os;
+import com.android.sdklib.internal.repository.packages.ExtraPackage;
+import com.android.sdklib.internal.repository.packages.Package;
+import com.android.sdklib.repository.NoPreviewRevision;
+
+import java.io.File;
+import java.util.Properties;
+
+public class LocalExtraPkgInfo extends LocalFullRevisionPkgInfo {
+
+ private final @NonNull String mExtraPath;
+ private final @NonNull String mVendorId;
+
+ public LocalExtraPkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull String vendorId,
+ @NonNull String path,
+ @NonNull NoPreviewRevision revision) {
+ super(localSdk, localDir, sourceProps, revision);
+ mVendorId = vendorId;
+ mExtraPath = path;
+ }
+
+ @Override
+ public int getType() {
+ return LocalSdk.PKG_EXTRAS;
+ }
+
+ @Override
+ public boolean hasPath() {
+ return true;
+ }
+
+ @NonNull
+ public String getExtraPath() {
+ return mExtraPath;
+ }
+
+ @NonNull
+ public String getVendorId() {
+ return mVendorId;
+ }
+
+ @NonNull
+ @Override
+ public String getPath() {
+ return mVendorId + '/' + mExtraPath;
+ }
+
+ @Nullable
+ @Override
+ public Package getPackage() {
+ Package pkg = super.getPackage();
+ if (pkg == null) {
+ try {
+ pkg = ExtraPackage.create(
+ null, //source
+ getSourceProperties(), //properties
+ mVendorId, //vendor
+ mExtraPath, //path
+ 0, //revision
+ null, //license
+ null, //description
+ null, //descUrl
+ Os.getCurrentOs(), //archiveOs
+ Arch.getCurrentArch(), //archiveArch
+ getLocalDir().getPath() //archiveOsPath
+ );
+ setPackage(pkg);
+ } catch (Exception e) {
+ appendLoadError("Failed to parse package: %1$s", e.toString());
+ }
+ }
+ return pkg;
+ }
+}
+
diff --git a/sdklib/src/main/java/com/android/sdklib/local/LocalFullRevisionPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/local/LocalFullRevisionPkgInfo.java
new file mode 100755
index 0000000..b2f22ad
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/local/LocalFullRevisionPkgInfo.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.local;
+
+import com.android.annotations.NonNull;
+import com.android.sdklib.repository.FullRevision;
+
+import java.io.File;
+import java.util.Properties;
+
+abstract class LocalFullRevisionPkgInfo extends LocalPkgInfo {
+
+ @NonNull
+ private final FullRevision mRevision;
+
+ public LocalFullRevisionPkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull FullRevision revision) {
+ super(localSdk, localDir, sourceProps);
+ mRevision = revision;
+ }
+
+ @Override
+ public boolean hasFullRevision() {
+ return true;
+ }
+
+ @NonNull
+ @Override
+ public FullRevision getFullRevision() {
+ return mRevision;
+ }
+}
+
diff --git a/sdklib/src/main/java/com/android/sdklib/local/LocalMajorRevisionPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/local/LocalMajorRevisionPkgInfo.java
new file mode 100755
index 0000000..c7ed8f1
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/local/LocalMajorRevisionPkgInfo.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.local;
+
+import com.android.annotations.NonNull;
+import com.android.sdklib.repository.MajorRevision;
+
+import java.io.File;
+import java.util.Properties;
+
+abstract class LocalMajorRevisionPkgInfo extends LocalPkgInfo {
+
+ @NonNull
+ private final MajorRevision mRevision;
+
+ public LocalMajorRevisionPkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull MajorRevision revision) {
+ super(localSdk, localDir, sourceProps);
+ mRevision = revision;
+ }
+
+ @Override
+ public boolean hasMajorRevision() {
+ return true;
+ }
+
+ @NonNull
+ @Override
+ public MajorRevision getMajorRevision() {
+ return mRevision;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/local/LocalPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/local/LocalPkgInfo.java
new file mode 100755
index 0000000..4f7ec28
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/local/LocalPkgInfo.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.local;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.internal.repository.IListDescription;
+import com.android.sdklib.internal.repository.packages.Package;
+import com.android.sdklib.repository.FullRevision;
+import com.android.sdklib.repository.MajorRevision;
+
+import java.io.File;
+import java.util.Properties;
+
+public abstract class LocalPkgInfo implements IListDescription, Comparable<LocalPkgInfo> {
+
+ private final LocalSdk mLocalSdk;
+ private final File mLocalDir;
+ private final Properties mSourceProperties;
+
+ private Package mPackage;
+ private String mLoadError;
+
+ protected LocalPkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps) {
+ mLocalSdk = localSdk;
+ mLocalDir = localDir;
+ mSourceProperties = sourceProps;
+ }
+
+ //---- Attributes ----
+
+ @NonNull
+ public LocalSdk getLocalSdk() {
+ return mLocalSdk;
+ }
+
+ @NonNull
+ public File getLocalDir() {
+ return mLocalDir;
+ }
+
+ @NonNull
+ public Properties getSourceProperties() {
+ return mSourceProperties;
+ }
+
+ @Nullable
+ public String getLoadError() {
+ return mLoadError;
+ }
+
+ // ----
+
+ public abstract int getType();
+
+ public boolean hasFullRevision() {
+ return false;
+ }
+
+ public boolean hasMajorRevision() {
+ return false;
+ }
+
+ public boolean hasAndroidVersion() {
+ return false;
+ }
+
+ public boolean hasPath() {
+ return false;
+ }
+
+ @Nullable
+ public FullRevision getFullRevision() {
+ return null;
+ }
+
+ @Nullable
+ public MajorRevision getMajorRevision() {
+ return null;
+ }
+
+ @Nullable
+ public AndroidVersion getAndroidVersion() {
+ return null;
+ }
+
+ @Nullable
+ public String getPath() {
+ return null;
+ }
+
+ //---- Ordering ----
+
+ @Override
+ public int compareTo(@NonNull LocalPkgInfo o) {
+ int t1 = getType();
+ int t2 = o.getType();
+ if (t1 != t2) {
+ return t2 - t1;
+ }
+
+ if (hasAndroidVersion() && o.hasAndroidVersion()) {
+ t1 = getAndroidVersion().compareTo(o.getAndroidVersion());
+ if (t1 != 0) {
+ return t1;
+ }
+ }
+
+
+ if (hasPath() && o.hasPath()) {
+ t1 = getPath().compareTo(o.getPath());
+ if (t1 != 0) {
+ return t1;
+ }
+ }
+
+ if (hasFullRevision() && o.hasFullRevision()) {
+ t1 = getFullRevision().compareTo(o.getFullRevision());
+ if (t1 != 0) {
+ return t1;
+ }
+ }
+
+ if (hasMajorRevision() && o.hasMajorRevision()) {
+ t1 = getMajorRevision().compareTo(o.getMajorRevision());
+ if (t1 != 0) {
+ return t1;
+ }
+ }
+
+ return 0;
+ }
+
+ /** String representation for debugging purposes. */
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("<");
+ builder.append(this.getClass().getSimpleName());
+
+ if (hasAndroidVersion()) {
+ builder.append(" Android=").append(getAndroidVersion());
+ }
+
+ if (hasPath()) {
+ builder.append(" Path=").append(getPath());
+ }
+
+ if (hasFullRevision()) {
+ builder.append(" FullRev=").append(getFullRevision());
+ }
+
+ if (hasMajorRevision()) {
+ builder.append(" MajorRev=").append(getMajorRevision());
+ }
+
+ builder.append(">");
+ return builder.toString();
+ }
+
+ //---- Package Management ----
+
+ /** A "broken" package is installed but is not fully operational.
+ *
+ * For example an addon that lacks its underlying platform or a tool package
+ * that lacks some of its binaries or essentially files.
+ * <p/>
+ * Operational code should generally ignore broken packages.
+ * Only the SDK Updater cares about displaying them so that they can be fixed.
+ */
+ public boolean hasLoadError() {
+ return mLoadError != null;
+ }
+
+ void appendLoadError(@NonNull String format, Object...params) {
+ String loadError = String.format(format, params);
+ if (mLoadError == null) {
+ mLoadError = loadError;
+ } else {
+ mLoadError = mLoadError + '\n' + loadError;
+ }
+ }
+
+ void setPackage(@Nullable Package pkg) {
+ mPackage = pkg;
+ }
+
+ @Nullable
+ public Package getPackage() {
+ return mPackage;
+ }
+
+ @NonNull
+ @Override
+ public String getListDescription() {
+ Package pkg = getPackage();
+ return pkg == null ? "" : pkg.getListDescription();
+ }
+
+}
+
diff --git a/sdklib/src/main/java/com/android/sdklib/local/LocalPlatformPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/local/LocalPlatformPkgInfo.java
new file mode 100755
index 0000000..1178e40
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/local/LocalPlatformPkgInfo.java
@@ -0,0 +1,443 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.local;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.AndroidTargetHash;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.ISystemImage;
+import com.android.sdklib.ISystemImage.LocationType;
+import com.android.sdklib.SdkManager.LayoutlibVersion;
+import com.android.sdklib.SystemImage;
+import com.android.sdklib.internal.androidTarget.PlatformTarget;
+import com.android.sdklib.internal.project.ProjectProperties;
+import com.android.sdklib.internal.repository.packages.Package;
+import com.android.sdklib.internal.repository.packages.PlatformPackage;
+import com.android.sdklib.io.IFileOp;
+import com.android.sdklib.repository.MajorRevision;
+import com.android.sdklib.repository.PkgProps;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+
+@SuppressWarnings("ConstantConditions")
+public class LocalPlatformPkgInfo extends LocalAndroidVersionPkgInfo {
+
+ public static final String PROP_VERSION_SDK = "ro.build.version.sdk"; //$NON-NLS-1$
+ public static final String PROP_VERSION_CODENAME = "ro.build.version.codename"; //$NON-NLS-1$
+ public static final String PROP_VERSION_RELEASE = "ro.build.version.release"; //$NON-NLS-1$
+
+ private final MajorRevision mRevision;
+ /** Android target, lazyly loaded from #getAndroidTarget */
+ private IAndroidTarget mTarget;
+ private boolean mLoaded;
+
+ public LocalPlatformPkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull AndroidVersion version,
+ @NonNull MajorRevision revision) {
+ super(localSdk, localDir, sourceProps, version);
+ mRevision = revision;
+ }
+
+ @Override
+ public int getType() {
+ return LocalSdk.PKG_PLATFORMS;
+ }
+
+ @Override
+ public boolean hasPath() {
+ return true;
+ }
+
+ @Override
+ public String getPath() {
+ return getTargetHash();
+ }
+
+ @NonNull
+ public String getTargetHash() {
+ return AndroidTargetHash.getPlatformHashString(getAndroidVersion());
+ }
+
+ @Nullable
+ public IAndroidTarget getAndroidTarget() {
+ if (!mLoaded) {
+ mTarget = createAndroidTarget();
+ mLoaded = true;
+ }
+ return mTarget;
+ }
+
+ public boolean isLoaded() {
+ return mLoaded;
+ }
+
+ @Override
+ public boolean hasMajorRevision() {
+ return true;
+ }
+
+ @Override
+ public MajorRevision getMajorRevision() {
+ return mRevision;
+ }
+
+ @Override
+ public Package getPackage() {
+ Package pkg = super.getPackage();
+ if (pkg != null) {
+ return pkg;
+ }
+ pkg = createPackage();
+ setPackage(pkg);
+ return pkg;
+ }
+
+ //-----
+
+ /**
+ * Creates a PlatformPackage wrapping the IAndroidTarget if defined.
+ * Invoked by {@link #getPackage()}.
+ *
+ * @return A Package or null if target isn't available.
+ */
+ @Nullable
+ protected Package createPackage() {
+ IAndroidTarget target = getAndroidTarget();
+ if (target != null) {
+ return PlatformPackage.create(target, getSourceProperties());
+ }
+ return null;
+ }
+
+ /**
+ * Creates the PlatformTarget. Invoked by {@link #getAndroidTarget()}.
+ */
+ @SuppressWarnings("ConstantConditions")
+ @Nullable
+ protected IAndroidTarget createAndroidTarget() {
+ LocalSdk sdk = getLocalSdk();
+ IFileOp fileOp = sdk.getFileOp();
+ File platformFolder = getLocalDir();
+ File buildProp = new File(platformFolder, SdkConstants.FN_BUILD_PROP);
+ File sourcePropFile = new File(platformFolder, SdkConstants.FN_SOURCE_PROP);
+
+ if (!fileOp.isFile(buildProp) || !fileOp.isFile(sourcePropFile)) {
+ appendLoadError("Ignoring platform '%1$s': %2$s is missing.", //$NON-NLS-1$
+ platformFolder.getName(),
+ SdkConstants.FN_BUILD_PROP);
+ return null;
+ }
+
+ Map<String, String> platformProp = new HashMap<String, String>();
+
+ // add all the property files
+ Map<String, String> map = null;
+
+ try {
+ map = ProjectProperties.parsePropertyStream(
+ fileOp.newFileInputStream(buildProp),
+ buildProp.getPath(),
+ null /*log*/);
+ if (map != null) {
+ platformProp.putAll(map);
+ }
+ } catch (FileNotFoundException ignore) {}
+
+ try {
+ map = ProjectProperties.parsePropertyStream(
+ fileOp.newFileInputStream(sourcePropFile),
+ sourcePropFile.getPath(),
+ null /*log*/);
+ if (map != null) {
+ platformProp.putAll(map);
+ }
+ } catch (FileNotFoundException ignore) {}
+
+ File sdkPropFile = new File(platformFolder, SdkConstants.FN_SDK_PROP);
+ if (fileOp.isFile(sdkPropFile)) { // obsolete platforms don't have this.
+ try {
+ map = ProjectProperties.parsePropertyStream(
+ fileOp.newFileInputStream(sdkPropFile),
+ sdkPropFile.getPath(),
+ null /*log*/);
+ if (map != null) {
+ platformProp.putAll(map);
+ }
+ } catch (FileNotFoundException ignore) {}
+ }
+
+ // look for some specific values in the map.
+
+ // api level
+ int apiNumber;
+ String stringValue = platformProp.get(PROP_VERSION_SDK);
+ if (stringValue == null) {
+ appendLoadError("Ignoring platform '%1$s': %2$s is missing from '%3$s'",
+ platformFolder.getName(), PROP_VERSION_SDK,
+ SdkConstants.FN_BUILD_PROP);
+ return null;
+ } else {
+ try {
+ apiNumber = Integer.parseInt(stringValue);
+ } catch (NumberFormatException e) {
+ // looks like apiNumber does not parse to a number.
+ // Ignore this platform.
+ appendLoadError(
+ "Ignoring platform '%1$s': %2$s is not a valid number in %3$s.",
+ platformFolder.getName(), PROP_VERSION_SDK,
+ SdkConstants.FN_BUILD_PROP);
+ return null;
+ }
+ }
+
+ // Codename must be either null or a platform codename.
+ // REL means it's a release version and therefore the codename should be null.
+ AndroidVersion apiVersion =
+ new AndroidVersion(apiNumber, platformProp.get(PROP_VERSION_CODENAME));
+
+ // version string
+ String apiName = platformProp.get(PkgProps.PLATFORM_VERSION);
+ if (apiName == null) {
+ apiName = platformProp.get(PROP_VERSION_RELEASE);
+ }
+ if (apiName == null) {
+ appendLoadError(
+ "Ignoring platform '%1$s': %2$s is missing from '%3$s'",
+ platformFolder.getName(), PROP_VERSION_RELEASE,
+ SdkConstants.FN_BUILD_PROP);
+ return null;
+ }
+
+ // platform rev number & layoutlib version are extracted from the source.properties
+ // saved by the SDK Manager when installing the package.
+
+ int revision = 1;
+ LayoutlibVersion layoutlibVersion = null;
+ try {
+ revision = Integer.parseInt(platformProp.get(PkgProps.PKG_REVISION));
+ } catch (NumberFormatException e) {
+ // do nothing, we'll keep the default value of 1.
+ }
+
+ try {
+ String propApi = platformProp.get(PkgProps.LAYOUTLIB_API);
+ String propRev = platformProp.get(PkgProps.LAYOUTLIB_REV);
+ int llApi = propApi == null ? LayoutlibVersion.NOT_SPECIFIED :
+ Integer.parseInt(propApi);
+ int llRev = propRev == null ? LayoutlibVersion.NOT_SPECIFIED :
+ Integer.parseInt(propRev);
+ if (llApi > LayoutlibVersion.NOT_SPECIFIED &&
+ llRev >= LayoutlibVersion.NOT_SPECIFIED) {
+ layoutlibVersion = new LayoutlibVersion(llApi, llRev);
+ }
+ } catch (NumberFormatException e) {
+ // do nothing, we'll ignore the layoutlib version if it's invalid
+ }
+
+ // api number and name look valid, perform a few more checks
+ String err = checkPlatformContent(fileOp, platformFolder);
+ if (err != null) {
+ appendLoadError("%s", err); //$NLN-NLS-1$
+ return null;
+ }
+
+ ISystemImage[] systemImages = getPlatformSystemImages(fileOp, platformFolder, apiVersion);
+
+ // create the target.
+ PlatformTarget pt = new PlatformTarget(
+ sdk.getLocation().getPath(),
+ platformFolder.getAbsolutePath(),
+ apiVersion,
+ apiName,
+ revision,
+ layoutlibVersion,
+ systemImages,
+ platformProp,
+ sdk.getLatestBuildTool());
+
+ // add the skins.
+ String[] skins = parseSkinFolder(pt.getPath(IAndroidTarget.SKINS));
+ pt.setSkins(skins);
+
+ // add path to the non-legacy samples package if it exists
+ LocalPkgInfo samples = sdk.getPkgInfo(LocalSdk.PKG_SAMPLES, getAndroidVersion());
+ if (samples != null) {
+ pt.setSamplesPath(samples.getLocalDir().getAbsolutePath());
+ }
+
+ // add path to the non-legacy sources package if it exists
+ LocalPkgInfo sources = sdk.getPkgInfo(LocalSdk.PKG_SOURCES, getAndroidVersion());
+ if (sources != null) {
+ pt.setSourcesPath(sources.getLocalDir().getAbsolutePath());
+ }
+
+ return pt;
+ }
+
+ /**
+ * Get all the system images supported by a platform target.
+ * For a platform, we first look in the new sdk/system-images folders then we
+ * look for sub-folders in the platform/images directory and/or the one legacy
+ * folder.
+ * If any given API appears twice or more, the first occurrence wins.
+ *
+ * @param fileOp File operation wrapper.
+ * @param platformDir Root of the platform target being loaded.
+ * @param apiVersion API level + codename of platform being loaded.
+ * @return an array of ISystemImage containing all the system images for the target.
+ * The list can be empty but not null.
+ */
+ @NonNull
+ private ISystemImage[] getPlatformSystemImages(IFileOp fileOp,
+ File platformDir,
+ AndroidVersion apiVersion) {
+ Set<ISystemImage> found = new TreeSet<ISystemImage>();
+ Set<String> abiFound = new HashSet<String>();
+
+ // First look in the SDK/system-image/platform-n/abi folders.
+ // If we find multiple occurrences of the same platform/abi, the first one read wins.
+
+ for (LocalPkgInfo pkg : getLocalSdk().getPkgsInfos(LocalSdk.PKG_SYS_IMAGES)) {
+ if (pkg instanceof LocalSysImgPkgInfo && apiVersion.equals(pkg.getAndroidVersion())) {
+ String abi = ((LocalSysImgPkgInfo)pkg).getAbi();
+ if (abi != null && !abiFound.contains(abi)) {
+ found.add(new SystemImage(
+ pkg.getLocalDir(),
+ LocationType.IN_SYSTEM_IMAGE,
+ abi));
+ abiFound.add(abi);
+ }
+ }
+ }
+
+ // Then look in either the platform/images/abi or the legacy folder
+ File imgDir = new File(platformDir, SdkConstants.OS_IMAGES_FOLDER);
+ File[] files = fileOp.listFiles(imgDir);
+ boolean useLegacy = true;
+ boolean hasImgFiles = false;
+
+ // Look for sub-directories
+ for (File file : files) {
+ if (fileOp.isDirectory(file)) {
+ useLegacy = false;
+ String abi = file.getName();
+ if (!abiFound.contains(abi)) {
+ found.add(new SystemImage(
+ file,
+ LocationType.IN_PLATFORM_SUBFOLDER,
+ abi));
+ abiFound.add(abi);
+ }
+ } else if (!hasImgFiles && fileOp.isFile(file)) {
+ if (file.getName().endsWith(".img")) { //$NON-NLS-1$
+ hasImgFiles = true;
+ }
+ }
+ }
+
+ if (useLegacy &&
+ hasImgFiles &&
+ fileOp.isDirectory(imgDir) &&
+ !abiFound.contains(SdkConstants.ABI_ARMEABI)) {
+ // We found no sub-folder system images but it looks like the top directory
+ // has some img files in it. It must be a legacy ARM EABI system image folder.
+ found.add(new SystemImage(
+ imgDir,
+ LocationType.IN_PLATFORM_LEGACY,
+ SdkConstants.ABI_ARMEABI));
+ }
+
+ return found.toArray(new ISystemImage[found.size()]);
+ }
+
+ /**
+ * Parses the skin folder and builds the skin list.
+ * @param osPath The path of the skin root folder.
+ */
+ @NonNull
+ protected String[] parseSkinFolder(@NonNull String osPath) {
+ IFileOp fileOp = getLocalSdk().getFileOp();
+ File skinRootFolder = new File(osPath);
+
+ if (fileOp.isDirectory(skinRootFolder)) {
+ ArrayList<String> skinList = new ArrayList<String>();
+
+ File[] files = fileOp.listFiles(skinRootFolder);
+
+ for (File skinFolder : files) {
+ if (fileOp.isDirectory(skinFolder)) {
+ // check for layout file
+ File layout = new File(skinFolder, SdkConstants.FN_SKIN_LAYOUT);
+
+ if (fileOp.isFile(layout)) {
+ // for now we don't parse the content of the layout and
+ // simply add the directory to the list.
+ skinList.add(skinFolder.getName());
+ }
+ }
+ }
+
+ return skinList.toArray(new String[skinList.size()]);
+ }
+
+ return new String[0];
+ }
+
+
+ /** List of items in the platform to check when parsing it. These paths are relative to the
+ * platform root folder. */
+ private static final String[] sPlatformContentList = new String[] {
+ SdkConstants.FN_FRAMEWORK_LIBRARY,
+ SdkConstants.FN_FRAMEWORK_AIDL,
+ };
+
+ /**
+ * Checks the given platform has all the required files, and returns true if they are all
+ * present.
+ * <p/>This checks the presence of the following files: android.jar, framework.aidl, aapt(.exe),
+ * aidl(.exe), dx(.bat), and dx.jar
+ *
+ * @param fileOp File operation wrapper.
+ * @param platform The folder containing the platform.
+ * @return An error description if platform is rejected; null if no error is detected.
+ */
+ @NonNull
+ private static String checkPlatformContent(IFileOp fileOp, @NonNull File platform) {
+ for (String relativePath : sPlatformContentList) {
+ File f = new File(platform, relativePath);
+ if (!fileOp.exists(f)) {
+ return String.format(
+ "Ignoring platform '%1$s': %2$s is missing.", //$NON-NLS-1$
+ platform.getName(), relativePath);
+ }
+ }
+ return null;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/local/LocalPlatformToolPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/local/LocalPlatformToolPkgInfo.java
new file mode 100755
index 0000000..80ef933
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/local/LocalPlatformToolPkgInfo.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.local;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.internal.repository.archives.Archive.Arch;
+import com.android.sdklib.internal.repository.archives.Archive.Os;
+import com.android.sdklib.internal.repository.packages.Package;
+import com.android.sdklib.internal.repository.packages.PlatformToolPackage;
+import com.android.sdklib.repository.FullRevision;
+
+import java.io.File;
+import java.util.Properties;
+
+public class LocalPlatformToolPkgInfo extends LocalFullRevisionPkgInfo {
+
+ public LocalPlatformToolPkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull FullRevision revision) {
+ super(localSdk, localDir, sourceProps, revision);
+ }
+
+ @Override
+ public int getType() {
+ return LocalSdk.PKG_PLATFORM_TOOLS;
+ }
+
+ @Nullable
+ @Override
+ public Package getPackage() {
+ Package pkg = super.getPackage();
+ if (pkg == null) {
+ try {
+ pkg = PlatformToolPackage.create(
+ null, //source
+ getSourceProperties(), //properties
+ 0, //revision
+ null, //license
+ "Platform Tools", //description
+ null, //descUrl
+ Os.getCurrentOs(), //archiveOs
+ Arch.getCurrentArch(), //archiveArch
+ getLocalDir().getPath() //archiveOsPath
+ );
+ setPackage(pkg);
+ } catch (Exception e) {
+ appendLoadError("Failed to parse package: %1$s", e.toString());
+ }
+ }
+ return pkg;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/local/LocalSamplePkgInfo.java b/sdklib/src/main/java/com/android/sdklib/local/LocalSamplePkgInfo.java
new file mode 100755
index 0000000..7eb3b6d
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/local/LocalSamplePkgInfo.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.local;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.internal.repository.packages.Package;
+import com.android.sdklib.internal.repository.packages.SamplePackage;
+import com.android.sdklib.repository.MajorRevision;
+
+import java.io.File;
+import java.util.Properties;
+
+/**
+ * Local sample package, for a given platform's {@link AndroidVersion}.
+ * The package itself has a major revision.
+ * There should be only one for a given android platform version.
+ */
+public class LocalSamplePkgInfo extends LocalAndroidVersionPkgInfo {
+
+ @NonNull
+ private final MajorRevision mRevision;
+
+ public LocalSamplePkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull AndroidVersion version,
+ @NonNull MajorRevision revision) {
+ super(localSdk, localDir, sourceProps, version);
+ mRevision = revision;
+ }
+
+ @Override
+ public int getType() {
+ return LocalSdk.PKG_SAMPLES;
+ }
+
+ @Override
+ public boolean hasMajorRevision() {
+ return true;
+ }
+
+ @NonNull
+ @Override
+ public MajorRevision getMajorRevision() {
+ return mRevision;
+ }
+
+ @Nullable
+ @Override
+ public Package getPackage() {
+ Package pkg = super.getPackage();
+ if (pkg == null) {
+ try {
+ pkg = SamplePackage.create(getLocalDir().getPath(), getSourceProperties());
+ setPackage(pkg);
+ } catch (Exception e) {
+ appendLoadError("Failed to parse package: %1$s", e.toString());
+ }
+ }
+ return pkg;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/local/LocalSdk.java b/sdklib/src/main/java/com/android/sdklib/local/LocalSdk.java
new file mode 100755
index 0000000..82ab484
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/local/LocalSdk.java
@@ -0,0 +1,1129 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.local;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.annotations.VisibleForTesting.Visibility;
+import com.android.sdklib.AndroidTargetHash;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.AndroidVersion.AndroidVersionException;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.internal.repository.packages.PackageParserUtils;
+import com.android.sdklib.io.FileOp;
+import com.android.sdklib.io.IFileOp;
+import com.android.sdklib.repository.FullRevision;
+import com.android.sdklib.repository.MajorRevision;
+import com.android.sdklib.repository.NoPreviewRevision;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.TreeMultimap;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.zip.Adler32;
+
+/**
+ * This class keeps information on the current locally installed SDK.
+ * It tries to lazily load information as much as possible.
+ * <p/>
+ * Packages are accessed by their type and a main query attribute, depending on the
+ * package type. There are different versions of {@link #getPkgInfo} which depend on the
+ * query attribute.
+ *
+ * <table border='1' cellpadding='3'>
+ * <tr>
+ * <th>Type</th>
+ * <th>Query parameter</th>
+ * <th>Getter</th>
+ * </tr>
+ *
+ * <tr>
+ * <td>Tools</td>
+ * <td>Unique instance</td>
+ * <td>{@code getPkgInfo(PKG_TOOLS)} => {@link LocalPkgInfo}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>Platform-Tools</td>
+ * <td>Unique instance</td>
+ * <td>{@code getPkgInfo(PKG_PLATFORM_TOOLS)} => {@link LocalPkgInfo}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>Docs</td>
+ * <td>Unique instance</td>
+ * <td>{@code getPkgInfo(PKG_DOCS)} => {@link LocalPkgInfo}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>Build-Tools</td>
+ * <td>{@link FullRevision}</td>
+ * <td>{@code getLatestBuildTool()} => {@link BuildToolInfo}, <br/>
+ * or {@code getBuildTool(FullRevision)} => {@link BuildToolInfo}, <br/>
+ * or {@code getPkgInfo(PKG_BUILD_TOOLS, FullRevision)} => {@link LocalPkgInfo}, <br/>
+ * or {@code getPkgsInfos(PKG_BUILD_TOOLS)} => {@link LocalPkgInfo}[]</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>Extras</td>
+ * <td>String vendor/path</td>
+ * <td>{@code getExtra(String)} => {@link LocalExtraPkgInfo}, <br/>
+ * or {@code getPkgInfo(PKG_EXTRAS, String)} => {@link LocalPkgInfo}, <br/>
+ * or {@code getPkgsInfos(PKG_EXTRAS)} => {@link LocalPkgInfo}[]</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>Sources</td>
+ * <td>{@link AndroidVersion}</td>
+ * <td>{@code getPkgInfo(PKG_SOURCES, AndroidVersion)} => {@link LocalPkgInfo}, <br/>
+ * or {@code getPkgsInfos(PKG_SOURCES)} => {@link LocalPkgInfo}[]</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>Samples</td>
+ * <td>{@link AndroidVersion}</td>
+ * <td>{@code getPkgInfo(PKG_SAMPLES, AndroidVersion)} => {@link LocalPkgInfo}, <br/>
+ * or {@code getPkgsInfos(PKG_SAMPLES)} => {@link LocalPkgInfo}[]</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>Platforms</td>
+ * <td>{@link AndroidVersion}</td>
+ * <td>{@code getPkgInfo(PKG_PLATFORMS, AndroidVersion)} => {@link LocalPkgInfo}, <br/>
+ * or {@code getPkgInfo(PKG_ADDONS, String)} => {@link LocalPkgInfo}, <br/>
+ * or {@code getPkgsInfos(PKG_PLATFORMS)} => {@link LocalPkgInfo}[], <br/>
+ * or {@code getTargetFromHashString(String)} => {@link IAndroidTarget}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>Add-ons</td>
+ * <td>{@link AndroidVersion} x String vendor/path</td>
+ * <td>{@code getPkgInfo(PKG_ADDONS, String)} => {@link LocalPkgInfo}, <br/>
+ * or {@code getPkgsInfos(PKG_ADDONS)} => {@link LocalPkgInfo}[], <br/>
+ * or {@code getTargetFromHashString(String)} => {@link IAndroidTarget}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>System images</td>
+ * <td>{@link AndroidVersion} x {@link String} ABI</td>
+ * <td>{@code getPkgsInfos(PKG_SYS_IMAGES)} => {@link LocalPkgInfo}[]</td>
+ * </tr>
+ *
+ * </table>
+ *
+ * Apps/libraries that use it are encouraged to keep an existing instance around
+ * (using a singleton or similar mechanism).
+ * <p/>
+ *
+ * Background:
+ * <ul>
+ * <li> The sdk manager has a set of "Package" classes that cover both local
+ * and remote SDK operations.
+ * <li> Goal is to split it in 2 cleanly separated part: local sdk parses sdk on disk, and then
+ * there will be a set of "remote package" classes that wrap the downloaded manifest.
+ * <li> The local SDK should be a singleton accessible somewhere, so there will be one in ADT
+ * (via the Sdk instance), one in Studio, and one in the command line tool. <br/>
+ * Right now there's a bit of mess with some classes creating a temp LocalSdkParser,
+ * some others using an SdkManager instance, and that needs to be sorted out.
+ * <li> As a transition, the SdkManager instance wraps a LocalSdk and use this. Eventually the
+ * SdkManager.java class will go away (its name is totally misleading, for starters.)
+ * <li> The current LocalSdkParser stays as-is for compatibility purposes and the goal is also
+ * to totally remove it when the SdkManager class goes away.
+ * </ul>
+ * @version 2 of the {@code SdkManager} class, essentially.
+ */
+public class LocalSdk {
+
+ /** Filter all SDK folders. */
+ public static final int PKG_ALL = 0xFFFF;
+
+ /** Filter the SDK/tools folder.
+ * Has {@link FullRevision}. */
+ public static final int PKG_TOOLS = 0x0001;
+ /** Filter the SDK/platform-tools folder.
+ * Has {@link FullRevision}. */
+ public static final int PKG_PLATFORM_TOOLS = 0x0002;
+ /** Filter the SDK/build-tools folder.
+ * Has {@link FullRevision}. */
+ public static final int PKG_BUILD_TOOLS = 0x0004;
+
+ /** Filter the SDK/docs folder.
+ * Has {@link MajorRevision}. */
+ public static final int PKG_DOCS = 0x0010;
+ /** Filter the SDK/extras folder.
+ * Has {@code Path}. Has {@link MajorRevision}. */
+ public static final int PKG_EXTRAS = 0x0020;
+
+ /** Filter the SDK/platforms.
+ * Has {@link AndroidVersion}. Has {@link MajorRevision}. */
+ public static final int PKG_PLATFORMS = 0x0100;
+ /** Filter the SDK/sys-images.
+ * Has {@link AndroidVersion}. Has {@link MajorRevision}. */
+ public static final int PKG_SYS_IMAGES = 0x0200;
+ /** Filter the SDK/addons.
+ * Has {@link AndroidVersion}. Has {@link MajorRevision}. */
+ public static final int PKG_ADDONS = 0x0400;
+ /** Filter the SDK/samples folder.
+ * Note: this will not detect samples located in the SDK/extras packages.
+ * Has {@link AndroidVersion}. Has {@link MajorRevision}. */
+ public static final int PKG_SAMPLES = 0x0800;
+ /** Filter the SDK/sources folder.
+ * Has {@link AndroidVersion}. Has {@link MajorRevision}. */
+ public static final int PKG_SOURCES = 0x1000;
+
+ /** Location of the SDK. Maybe null. Can be changed. */
+ private File mSdkRoot;
+ /** File operation object. (Used for overriding in mock testing.) */
+ private final IFileOp mFileOp;
+ /** List of package information loaded so far. Lazily populated. */
+ private final Multimap<Integer, LocalPkgInfo> mLocalPackages = TreeMultimap.create();
+ /** Directories already parsed into {@link #mLocalPackages}. */
+ private final Multimap<Integer, DirInfo> mVisitedDirs = HashMultimap.create();
+ /** A legacy build-tool for older platform-tools < 17. */
+ private BuildToolInfo mLegacyBuildTools;
+
+ private final static Map<Integer, String> sFolderName = Maps.newHashMap();
+
+ static {
+ sFolderName.put(PKG_TOOLS, SdkConstants.FD_TOOLS);
+ sFolderName.put(PKG_PLATFORM_TOOLS, SdkConstants.FD_PLATFORM_TOOLS);
+ sFolderName.put(PKG_BUILD_TOOLS, SdkConstants.FD_BUILD_TOOLS);
+ sFolderName.put(PKG_DOCS, SdkConstants.FD_DOCS);
+ sFolderName.put(PKG_PLATFORMS, SdkConstants.FD_PLATFORMS);
+ sFolderName.put(PKG_SYS_IMAGES, SdkConstants.FD_SYSTEM_IMAGES);
+ sFolderName.put(PKG_ADDONS, SdkConstants.FD_ADDONS);
+ sFolderName.put(PKG_SOURCES, SdkConstants.FD_ANDROID_SOURCES);
+ sFolderName.put(PKG_SAMPLES, SdkConstants.FD_SAMPLES);
+ sFolderName.put(PKG_EXTRAS, SdkConstants.FD_EXTRAS);
+ }
+
+ /**
+ * Creates an initial LocalSdk instance with an unknown location.
+ */
+ public LocalSdk() {
+ mFileOp = new FileOp();
+ }
+
+ /**
+ * Creates an initial LocalSdk instance for a known SDK location.
+ *
+ * @param sdkRoot The location of the SDK root folder.
+ */
+ public LocalSdk(@NonNull File sdkRoot) {
+ this();
+ setLocation(sdkRoot);
+ }
+
+ /**
+ * Creates an initial LocalSdk instance with an unknown location.
+ * This is designed for unit tests to override the {@link FileOp} being used.
+ *
+ * @param fileOp The alternate {@link FileOp} to use for all file-based interactions.
+ */
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected LocalSdk(@NonNull IFileOp fileOp) {
+ mFileOp = fileOp;
+ }
+
+ /*
+ * Returns the current IFileOp being used.
+ */
+ public IFileOp getFileOp() {
+ return mFileOp;
+ }
+
+ /**
+ * Sets or changes the SDK root location. This also clears any cached information.
+ *
+ * @param sdkRoot The location of the SDK root folder.
+ */
+ public void setLocation(@NonNull File sdkRoot) {
+ assert sdkRoot != null;
+ mSdkRoot = sdkRoot;
+ clearLocalPkg(PKG_ALL);
+ }
+
+ /**
+ * Location of the SDK. Maybe null. Can be changed.
+ *
+ * @return The location of the SDK. Null if not initialized yet.
+ */
+ @Nullable
+ public File getLocation() {
+ return mSdkRoot;
+ }
+
+ /**
+ * Clear the tracked visited folders & the cached {@link LocalPkgInfo} for the
+ * given filter types.
+ *
+ * @param filters An OR of the PKG_ constants or {@link #PKG_ALL} to clear everything.
+ */
+ public void clearLocalPkg(int filters) {
+ mLegacyBuildTools = null;
+
+ int minf = Integer.lowestOneBit(filters);
+ for (int filter = minf; filters != 0 && filter <= PKG_ALL; filter <<= 1) {
+ if ((filters & filter) == 0) {
+ continue;
+ }
+ filters ^= filter;
+ mVisitedDirs.removeAll(filter);
+ mLocalPackages.removeAll(filter);
+ }
+ }
+
+ /**
+ * Check the tracked visited folders to see if anything has changed for the
+ * requested filter types.
+ * This does not refresh or reload any package information.
+ *
+ * @param filters An OR of the PKG_ constants or {@link #PKG_ALL} to clear everything.
+ */
+ public boolean hasChanged(int filters) {
+ int minf = Integer.lowestOneBit(filters);
+ for (int filter = minf; filters != 0 && filter <= PKG_ALL; filter <<= 1) {
+ if ((filters & filter) == 0) {
+ continue;
+ }
+ filters ^= filter;
+
+ for(DirInfo dirInfo : mVisitedDirs.get(filter)) {
+ if (dirInfo.hasChanged()) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+
+
+ //--------- Generic querying ---------
+
+ /**
+ * Retrieves information on a package identified by an {@link AndroidVersion}.
+ *
+ * Note: don't use this for {@link #PKG_SYS_IMAGES} since there can be more than
+ * one ABI and this method only returns a single package per filter type.
+ *
+ * @param filter {@link #PKG_PLATFORMS}, {@link #PKG_SAMPLES} or {@link #PKG_SOURCES}.
+ * @param version The {@link AndroidVersion} specific for this package type.
+ * @return An existing package information or null if not found.
+ */
+ public LocalPkgInfo getPkgInfo(int filter, AndroidVersion version) {
+ assert filter == PKG_PLATFORMS ||
+ filter == PKG_SAMPLES ||
+ filter == PKG_SOURCES;
+
+ for (LocalPkgInfo pkg : getPkgsInfos(filter)) {
+ if (pkg instanceof LocalAndroidVersionPkgInfo) {
+ LocalAndroidVersionPkgInfo p = (LocalAndroidVersionPkgInfo) pkg;
+ if (p.getAndroidVersion().equals(version)) {
+ return p;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Retrieves information on a package identified by its {@link FullRevision}.
+ * <p/>
+ * Note that {@link #PKG_TOOLS} and {@link #PKG_PLATFORM_TOOLS} are unique in a local SDK
+ * so you'll want to use {@link #getPkgInfo(int)} to retrieve them instead.
+ *
+ * @param filter {@link #PKG_BUILD_TOOLS}.
+ * @param revision The {@link FullRevision} uniquely identifying this package.
+ * @return An existing package information or null if not found.
+ */
+ public LocalPkgInfo getPkgInfo(int filter, FullRevision revision) {
+
+ assert filter == PKG_BUILD_TOOLS;
+
+ for (LocalPkgInfo pkg : getPkgsInfos(filter)) {
+ if (pkg instanceof LocalFullRevisionPkgInfo) {
+ LocalFullRevisionPkgInfo p = (LocalFullRevisionPkgInfo) pkg;
+ if (p.getFullRevision().equals(revision)) {
+ return p;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Retrieves information on a package identified by its {@link String} vendor/path.
+ *
+ * @param filter {@link #PKG_EXTRAS}, {@link #PKG_ADDONS}, {@link #PKG_PLATFORMS}.
+ * @param vendorPath The vendor/path uniquely identifying this package.
+ * @return An existing package information or null if not found.
+ */
+ public LocalPkgInfo getPkgInfo(int filter, String vendorPath) {
+
+ assert filter == PKG_EXTRAS ||
+ filter == PKG_ADDONS ||
+ filter == PKG_PLATFORMS;
+
+ for (LocalPkgInfo pkg : getPkgsInfos(filter)) {
+ if (pkg.hasPath() && vendorPath.equals(pkg.getPath())) {
+ return pkg;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Retrieves information on an extra package identified by its {@link String} vendor/path.
+ *
+ * @param vendorPath The vendor/path uniquely identifying this package.
+ * @return An existing extra package information or null if not found.
+ */
+ public LocalExtraPkgInfo getExtra(String vendorPath) {
+ return (LocalExtraPkgInfo) getPkgInfo(PKG_EXTRAS, vendorPath);
+ }
+
+ /**
+ * For unique local packages.
+ * Returns the cached LocalPkgInfo for the requested type.
+ * Loads it from disk if not cached.
+ *
+ * @param filter {@link #PKG_TOOLS} or {@link #PKG_PLATFORM_TOOLS} or {@link #PKG_DOCS}.
+ * @return null if the package is not installed.
+ */
+ public LocalPkgInfo getPkgInfo(int filter) {
+
+ assert filter == PKG_TOOLS ||
+ filter == PKG_PLATFORM_TOOLS ||
+ filter == PKG_DOCS;
+
+ switch(filter) {
+ case PKG_TOOLS:
+ case PKG_PLATFORM_TOOLS:
+ case PKG_DOCS:
+ break;
+ default:
+ return null;
+ }
+
+ Collection<LocalPkgInfo> existing = mLocalPackages.get(filter);
+ assert existing.size() <= 1;
+ if (existing.size() > 0) {
+ return existing.iterator().next();
+ }
+
+ File uniqueDir = new File(mSdkRoot, sFolderName.get(filter));
+ LocalPkgInfo info = null;
+
+ if (!mVisitedDirs.containsEntry(filter, uniqueDir)) {
+ switch(filter) {
+ case PKG_TOOLS:
+ info = scanTools(uniqueDir);
+ break;
+ case PKG_PLATFORM_TOOLS:
+ info = scanPlatformTools(uniqueDir);
+ break;
+ case PKG_DOCS:
+ info = scanDoc(uniqueDir);
+ break;
+ }
+ }
+
+ // Whether we have found a valid pkg or not, this directory has been visited.
+ mVisitedDirs.put(filter, new DirInfo(uniqueDir));
+
+ if (info != null) {
+ mLocalPackages.put(filter, info);
+ }
+
+ return info;
+ }
+
+ /**
+ * Retrieve all the info about the requested package type.
+ * This is used for the package types that have one or more instances, each with different
+ * versions.
+ * <p/>
+ * To force the LocalSdk parser to load <b>everything</b>, simply call this method
+ * with the {@link #PKG_ALL} argument to load all the known package types.
+ * <p/>
+ * Note: you can use this with {@link #PKG_TOOLS}, {@link #PKG_PLATFORM_TOOLS} and
+ * {@link #PKG_DOCS} but since there can only be one package of these types, it is
+ * more efficient to use {@link #getPkgInfo(int)} to query them.
+ *
+ * @param filters One or more of {@link #PKG_ADDONS}, {@link #PKG_PLATFORMS},
+ * {@link #PKG_BUILD_TOOLS}, {@link #PKG_EXTRAS},
+ * {@link #PKG_SOURCES}, {@link #PKG_SYS_IMAGES}
+ * @return A list (possibly empty) of matching installed packages. Never returns null.
+ */
+ public LocalPkgInfo[] getPkgsInfos(int filters) {
+
+ List<LocalPkgInfo> list = Lists.newArrayList();
+
+ int minf = Integer.lowestOneBit(filters);
+
+ for (int filter = minf; filters != 0 && filter <= PKG_ALL; filter <<= 1) {
+ if ((filters & filter) == 0) {
+ continue;
+ }
+ filters ^= filter;
+
+ switch(filter) {
+ case PKG_TOOLS:
+ case PKG_PLATFORM_TOOLS:
+ case PKG_DOCS:
+ LocalPkgInfo info = getPkgInfo(filter);
+ if (info != null) {
+ list.add(info);
+ }
+ break;
+
+ case PKG_BUILD_TOOLS:
+ case PKG_PLATFORMS:
+ case PKG_SYS_IMAGES:
+ case PKG_ADDONS:
+ case PKG_SAMPLES:
+ case PKG_SOURCES:
+ case PKG_EXTRAS:
+ Collection<LocalPkgInfo> existing = mLocalPackages.get(filter);
+ if (existing.size() > 0) {
+ list.addAll(existing);
+ continue;
+ }
+
+ File subDir = new File(mSdkRoot, sFolderName.get(filter));
+
+ if (!mVisitedDirs.containsEntry(filter, subDir)) {
+ switch(filter) {
+ case PKG_BUILD_TOOLS:
+ scanBuildTools(subDir, existing);
+ break;
+ case PKG_PLATFORMS:
+ scanPlatforms(subDir, existing);
+ break;
+ case PKG_SYS_IMAGES:
+ scanSysImages(subDir, existing);
+ break;
+ case PKG_ADDONS:
+ scanAddons(subDir, existing);
+ break;
+ case PKG_SAMPLES:
+ scanSamples(subDir, existing);
+ break;
+ case PKG_SOURCES:
+ scanSources(subDir, existing);
+ break;
+ case PKG_EXTRAS:
+ scanExtras(subDir, existing);
+ break;
+ }
+ mVisitedDirs.put(filter, new DirInfo(subDir));
+ list.addAll(existing);
+ }
+ break;
+ }
+ }
+
+ return list.toArray(new LocalPkgInfo[list.size()]);
+ }
+
+ //---------- Package-specific querying --------
+
+ /**
+ * Returns the {@link BuildToolInfo} for the given revision.
+ *
+ * @param revision The requested revision.
+ * @return A {@link BuildToolInfo}. Can be null if {@code revision} is null or is
+ * not part of the known set returned by {@code getPkgsInfos(PKG_BUILD_TOOLS)}.
+ */
+ @Nullable
+ public BuildToolInfo getBuildTool(@Nullable FullRevision revision) {
+ LocalPkgInfo pkg = getPkgInfo(PKG_BUILD_TOOLS, revision);
+ if (pkg instanceof LocalBuildToolPkgInfo) {
+ return ((LocalBuildToolPkgInfo) pkg).getBuildToolInfo();
+ }
+ return null;
+ }
+
+ /**
+ * Returns the highest build-tool revision known, or null if there are are no build-tools.
+ * <p/>
+ * If no specific build-tool package is installed but the platform-tools is lower than 17,
+ * then this creates and returns a "legacy" built-tool package using platform-tools.
+ * (We only split build-tools out of platform-tools starting with revision 17,
+ * before they were both the same thing.)
+ *
+ * @return The highest build-tool revision known, or null.
+ */
+ @Nullable
+ public BuildToolInfo getLatestBuildTool() {
+ if (mLegacyBuildTools != null) {
+ return mLegacyBuildTools;
+ }
+
+ LocalPkgInfo[] pkgs = getPkgsInfos(PKG_BUILD_TOOLS);
+
+ if (pkgs.length == 0) {
+ LocalPkgInfo ptPkg = getPkgInfo(PKG_PLATFORM_TOOLS);
+ if (ptPkg instanceof LocalPlatformToolPkgInfo &&
+ ptPkg.getFullRevision().compareTo(new FullRevision(17)) < 0) {
+ // older SDK, create a compatible build-tools
+ mLegacyBuildTools = createLegacyBuildTools((LocalPlatformToolPkgInfo) ptPkg);
+ return mLegacyBuildTools;
+ }
+ return null;
+ }
+
+ assert pkgs.length > 0;
+
+ // Note: the pkgs come from a TreeMultimap so they should already be sorted.
+ // Just in case, sort them again.
+ Arrays.sort(pkgs);
+
+ // LocalBuildToolPkgInfo's comparator sorts on its FullRevision so we just
+ // need to take the latest element.
+ LocalPkgInfo pkg = pkgs[pkgs.length - 1];
+ if (pkg instanceof LocalBuildToolPkgInfo) {
+ return ((LocalBuildToolPkgInfo) pkg).getBuildToolInfo();
+ }
+
+ return null;
+ }
+
+ private BuildToolInfo createLegacyBuildTools(LocalPlatformToolPkgInfo ptInfo) {
+ File platformTools = new File(getLocation(), SdkConstants.FD_PLATFORM_TOOLS);
+ File platformToolsLib = ptInfo.getLocalDir();
+ File platformToolsRs = new File(platformTools, SdkConstants.FN_FRAMEWORK_RENDERSCRIPT);
+
+ return new BuildToolInfo(
+ ptInfo.getFullRevision(),
+ platformTools,
+ new File(platformTools, SdkConstants.FN_AAPT),
+ new File(platformTools, SdkConstants.FN_AIDL),
+ new File(platformTools, SdkConstants.FN_DX),
+ new File(platformToolsLib, SdkConstants.FN_DX_JAR),
+ new File(platformTools, SdkConstants.FN_RENDERSCRIPT),
+ new File(platformToolsRs, SdkConstants.FN_FRAMEWORK_INCLUDE),
+ new File(platformToolsRs, SdkConstants.FN_FRAMEWORK_INCLUDE_CLANG),
+ null,
+ null,
+ null,
+ null);
+ }
+
+ /**
+ * Returns a target from a hash that was generated by {@link IAndroidTarget#hashString()}.
+ *
+ * @param hash the {@link IAndroidTarget} hash string.
+ * @return The matching {@link IAndroidTarget} or null.
+ */
+ @Nullable
+ public IAndroidTarget getTargetFromHashString(@Nullable String hash) {
+
+ if (hash != null) {
+ boolean isPlatform = AndroidTargetHash.isPlatform(hash);
+ LocalPkgInfo[] pkgs = getPkgsInfos(isPlatform ? PKG_PLATFORMS : PKG_ADDONS);
+
+ for (LocalPkgInfo pkg : pkgs) {
+ if (pkg instanceof LocalPlatformPkgInfo) {
+ IAndroidTarget target = ((LocalPlatformPkgInfo) pkg).getAndroidTarget();
+ if (target != null &&
+ hash.equals(AndroidTargetHash.getTargetHashString(target))) {
+ return target;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ // -------------
+
+ /**
+ * Try to find a tools package at the given location.
+ * Returns null if not found.
+ */
+ private LocalToolPkgInfo scanTools(File toolFolder) {
+ // Can we find some properties?
+ Properties props = parseProperties(new File(toolFolder, SdkConstants.FN_SOURCE_PROP));
+ FullRevision rev = PackageParserUtils.getPropertyFullRevision(props);
+ if (rev == null) {
+ return null;
+ }
+
+ LocalToolPkgInfo info = new LocalToolPkgInfo(this, toolFolder, props, rev);
+
+ // We're not going to check that all tools are present. At the very least
+ // we should expect to find android and an emulator adapted to the current OS.
+ boolean hasEmulator = false;
+ boolean hasAndroid = false;
+ String android1 = SdkConstants.androidCmdName().replace(".bat", ".exe");
+ String android2 = android1.indexOf('.') == -1 ? null : android1.replace(".exe", ".bat");
+ File[] files = mFileOp.listFiles(toolFolder);
+ for (File file : files) {
+ String name = file.getName();
+ if (SdkConstants.FN_EMULATOR.equals(name)) {
+ hasEmulator = true;
+ }
+ if (android1.equals(name) || (android2 != null && android2.equals(name))) {
+ hasAndroid = true;
+ }
+ }
+ if (!hasAndroid) {
+ info.appendLoadError("Missing %1$s", SdkConstants.androidCmdName());
+ }
+ if (!hasEmulator) {
+ info.appendLoadError("Missing %1$s", SdkConstants.FN_EMULATOR);
+ }
+
+ return info;
+ }
+
+ /**
+ * Try to find a platform-tools package at the given location.
+ * Returns null if not found.
+ */
+ private LocalPlatformToolPkgInfo scanPlatformTools(File ptFolder) {
+ // Can we find some properties?
+ Properties props = parseProperties(new File(ptFolder, SdkConstants.FN_SOURCE_PROP));
+ FullRevision rev = PackageParserUtils.getPropertyFullRevision(props);
+ if (rev == null) {
+ return null;
+ }
+
+ LocalPlatformToolPkgInfo info = new LocalPlatformToolPkgInfo(this, ptFolder, props, rev);
+ return info;
+ }
+
+ /**
+ * Try to find a docs package at the given location.
+ * Returns null if not found.
+ */
+ private LocalDocPkgInfo scanDoc(File docFolder) {
+ // Can we find some properties?
+ Properties props = parseProperties(new File(docFolder, SdkConstants.FN_SOURCE_PROP));
+ MajorRevision rev = PackageParserUtils.getPropertyMajorRevision(props);
+ if (rev == null) {
+ return null;
+ }
+
+ LocalDocPkgInfo info = new LocalDocPkgInfo(this, docFolder, props, rev);
+
+ // To start with, a doc folder should have an "index.html" to be acceptable.
+ // We don't actually check the content of the file.
+ if (!mFileOp.isFile(new File(docFolder, "index.html"))) {
+ info.appendLoadError("Missing index.html");
+ }
+ return info;
+ }
+
+ private void scanBuildTools(File collectionDir, Collection<LocalPkgInfo> outCollection) {
+ // The build-tool root folder contains a list of per-revision folders.
+ for (File buildToolDir : mFileOp.listFiles(collectionDir)) {
+ if (!mFileOp.isDirectory(buildToolDir) ||
+ mVisitedDirs.containsEntry(PKG_BUILD_TOOLS, buildToolDir)) {
+ continue;
+ }
+ mVisitedDirs.put(PKG_BUILD_TOOLS, new DirInfo(buildToolDir));
+
+ Properties props = parseProperties(new File(buildToolDir, SdkConstants.FN_SOURCE_PROP));
+ FullRevision rev = PackageParserUtils.getPropertyFullRevision(props);
+ if (rev == null) {
+ continue; // skip, no revision
+ }
+
+ BuildToolInfo btInfo = new BuildToolInfo(rev, buildToolDir);
+ LocalBuildToolPkgInfo pkgInfo =
+ new LocalBuildToolPkgInfo(this, buildToolDir, props, rev, btInfo);
+ outCollection.add(pkgInfo);
+ }
+ }
+
+ private void scanPlatforms(File collectionDir, Collection<LocalPkgInfo> outCollection) {
+ for (File platformDir : mFileOp.listFiles(collectionDir)) {
+ if (!mFileOp.isDirectory(platformDir) ||
+ mVisitedDirs.containsEntry(PKG_PLATFORMS, platformDir)) {
+ continue;
+ }
+ mVisitedDirs.put(PKG_PLATFORMS, new DirInfo(platformDir));
+
+ Properties props = parseProperties(new File(platformDir, SdkConstants.FN_SOURCE_PROP));
+ MajorRevision rev = PackageParserUtils.getPropertyMajorRevision(props);
+ if (rev == null) {
+ continue; // skip, no revision
+ }
+
+ try {
+ AndroidVersion vers = new AndroidVersion(props);
+
+ LocalPlatformPkgInfo pkgInfo =
+ new LocalPlatformPkgInfo(this, platformDir, props, vers, rev);
+ outCollection.add(pkgInfo);
+
+ } catch (AndroidVersionException e) {
+ continue; // skip invalid or missing android version.
+ }
+ }
+ }
+
+ private void scanAddons(File collectionDir, Collection<LocalPkgInfo> outCollection) {
+ for (File addonDir : mFileOp.listFiles(collectionDir)) {
+ if (!mFileOp.isDirectory(addonDir) ||
+ mVisitedDirs.containsEntry(PKG_ADDONS, addonDir)) {
+ continue;
+ }
+ mVisitedDirs.put(PKG_ADDONS, new DirInfo(addonDir));
+
+ Properties props = parseProperties(new File(addonDir, SdkConstants.FN_SOURCE_PROP));
+ MajorRevision rev = PackageParserUtils.getPropertyMajorRevision(props);
+ if (rev == null) {
+ continue; // skip, no revision
+ }
+
+ try {
+ AndroidVersion vers = new AndroidVersion(props);
+
+ LocalAddonPkgInfo pkgInfo =
+ new LocalAddonPkgInfo(this, addonDir, props, vers, rev);
+ outCollection.add(pkgInfo);
+
+ } catch (AndroidVersionException e) {
+ continue; // skip invalid or missing android version.
+ }
+ }
+ }
+
+ private void scanSysImages(File collectionDir, Collection<LocalPkgInfo> outCollection) {
+ for (File platformDir : mFileOp.listFiles(collectionDir)) {
+ if (!mFileOp.isDirectory(platformDir) ||
+ mVisitedDirs.containsEntry(PKG_SYS_IMAGES, platformDir)) {
+ continue;
+ }
+ mVisitedDirs.put(PKG_SYS_IMAGES, new DirInfo(platformDir));
+
+ for (File abiDir : mFileOp.listFiles(platformDir)) {
+ if (!mFileOp.isDirectory(abiDir) ||
+ mVisitedDirs.containsEntry(PKG_SYS_IMAGES, abiDir)) {
+ continue;
+ }
+ mVisitedDirs.put(PKG_SYS_IMAGES, new DirInfo(abiDir));
+
+ Properties props = parseProperties(new File(abiDir, SdkConstants.FN_SOURCE_PROP));
+ MajorRevision rev = PackageParserUtils.getPropertyMajorRevision(props);
+ if (rev == null) {
+ continue; // skip, no revision
+ }
+
+ try {
+ AndroidVersion vers = new AndroidVersion(props);
+
+ LocalSysImgPkgInfo pkgInfo =
+ new LocalSysImgPkgInfo(this, abiDir, props, vers, abiDir.getName(), rev);
+ outCollection.add(pkgInfo);
+
+ } catch (AndroidVersionException e) {
+ continue; // skip invalid or missing android version.
+ }
+ }
+ }
+ }
+
+ private void scanSamples(File collectionDir, Collection<LocalPkgInfo> outCollection) {
+ for (File platformDir : mFileOp.listFiles(collectionDir)) {
+ if (!mFileOp.isDirectory(platformDir) ||
+ mVisitedDirs.containsEntry(PKG_SAMPLES, platformDir)) {
+ continue;
+ }
+ mVisitedDirs.put(PKG_SAMPLES, new DirInfo(platformDir));
+
+ Properties props = parseProperties(new File(platformDir, SdkConstants.FN_SOURCE_PROP));
+ MajorRevision rev = PackageParserUtils.getPropertyMajorRevision(props);
+ if (rev == null) {
+ continue; // skip, no revision
+ }
+
+ try {
+ AndroidVersion vers = new AndroidVersion(props);
+
+ LocalSamplePkgInfo pkgInfo =
+ new LocalSamplePkgInfo(this, platformDir, props, vers, rev);
+ outCollection.add(pkgInfo);
+ } catch (AndroidVersionException e) {
+ continue; // skip invalid or missing android version.
+ }
+ }
+ }
+
+ private void scanSources(File collectionDir, Collection<LocalPkgInfo> outCollection) {
+ // The build-tool root folder contains a list of per-revision folders.
+ for (File platformDir : mFileOp.listFiles(collectionDir)) {
+ if (!mFileOp.isDirectory(platformDir) ||
+ mVisitedDirs.containsEntry(PKG_SOURCES, platformDir)) {
+ continue;
+ }
+ mVisitedDirs.put(PKG_SOURCES, new DirInfo(platformDir));
+
+ Properties props = parseProperties(new File(platformDir, SdkConstants.FN_SOURCE_PROP));
+ MajorRevision rev = PackageParserUtils.getPropertyMajorRevision(props);
+ if (rev == null) {
+ continue; // skip, no revision
+ }
+
+ try {
+ AndroidVersion vers = new AndroidVersion(props);
+
+ LocalSourcePkgInfo pkgInfo =
+ new LocalSourcePkgInfo(this, platformDir, props, vers, rev);
+ outCollection.add(pkgInfo);
+ } catch (AndroidVersionException e) {
+ continue; // skip invalid or missing android version.
+ }
+ }
+ }
+
+ private void scanExtras(File collectionDir, Collection<LocalPkgInfo> outCollection) {
+ for (File vendorDir : mFileOp.listFiles(collectionDir)) {
+ if (!mFileOp.isDirectory(vendorDir) || mVisitedDirs.containsEntry(PKG_EXTRAS, vendorDir)) {
+ continue;
+ }
+ mVisitedDirs.put(PKG_EXTRAS, new DirInfo(vendorDir));
+
+ for (File extraDir : mFileOp.listFiles(vendorDir)) {
+ if (!mFileOp.isDirectory(extraDir) ||
+ mVisitedDirs.containsEntry(PKG_EXTRAS, extraDir)) {
+ continue;
+ }
+ mVisitedDirs.put(PKG_EXTRAS, new DirInfo(extraDir));
+
+ Properties props = parseProperties(new File(extraDir, SdkConstants.FN_SOURCE_PROP));
+ NoPreviewRevision rev = PackageParserUtils.getPropertyNoPreviewRevision(props);
+ if (rev == null) {
+ continue; // skip, no revision
+ }
+
+ LocalExtraPkgInfo pkgInfo = new LocalExtraPkgInfo(this,
+ extraDir,
+ props,
+ vendorDir.getName(),
+ extraDir.getName(),
+ rev);
+ outCollection.add(pkgInfo);
+ }
+ }
+ }
+
+ /**
+ * Parses the given file as properties file if it exists.
+ * Returns null if the file does not exist, cannot be parsed or has no properties.
+ */
+ private Properties parseProperties(File propsFile) {
+ InputStream fis = null;
+ try {
+ if (mFileOp.exists(propsFile)) {
+ fis = mFileOp.newFileInputStream(propsFile);
+
+ Properties props = new Properties();
+ props.load(fis);
+
+ // To be valid, there must be at least one property in it.
+ if (props.size() > 0) {
+ return props;
+ }
+ }
+ } catch (IOException e) {
+ // Ignore
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException e) {}
+ }
+ }
+ return null;
+ }
+
+ // -------------
+
+ /**
+ * Keeps information on a visited directory to quickly determine if it
+ * has changed later. A directory has changed if its timestamp has been
+ * modified, or if an underlying source.properties file has changed in
+ * timestamp or checksum.
+ * <p/>
+ * Note that depending on the filesystem & OS, the content of the files in
+ * a directory can change without the directory's last-modified property
+ * changing. A generic directory monitor would work around that by checking
+ * the list of files. Instead here we know that each directory is an SDK
+ * directory and the source.property file will change if a new package is
+ * installed or updated.
+ * <p/>
+ * The {@link #hashCode()} and {@link #equals(Object)} methods directly
+ * defer to the underlying File object. This allows the DirInfo to be placed
+ * into a map and still call {@link Map#containsKey(Object)} with a File
+ * object to check whether there's a corresponding DirInfo in the map.
+ */
+ private class DirInfo {
+ @NonNull
+ private final File mDir;
+ private final long mDirModifiedTS;
+ private final long mPropsModifiedTS;
+ private final long mPropsChecksum;
+
+ /**
+ * Creates a new immutable {@link DirInfo}.
+ *
+ * @param dir The platform/addon directory of the target. It should be a directory.
+ */
+ public DirInfo(@NonNull File dir) {
+ mDir = dir;
+ mDirModifiedTS = mFileOp.lastModified(dir);
+
+ // Capture some info about the source.properties file if it exists.
+ // We use propsModifiedTS == 0 to mean there is no props file.
+ long propsChecksum = 0;
+ long propsModifiedTS = 0;
+ File props = new File(dir, SdkConstants.FN_SOURCE_PROP);
+ if (mFileOp.isFile(props)) {
+ propsModifiedTS = mFileOp.lastModified(props);
+ propsChecksum = getFileChecksum(props);
+ }
+ mPropsModifiedTS = propsModifiedTS;
+ mPropsChecksum = propsChecksum;
+ }
+
+ /**
+ * Checks whether the directory/source.properties attributes have changed.
+ *
+ * @return True if the directory modified timestamp or
+ * its source.property files have changed.
+ */
+ public boolean hasChanged() {
+ // Does platform directory still exist?
+ if (!mFileOp.isDirectory(mDir)) {
+ return true;
+ }
+ // Has platform directory modified-timestamp changed?
+ if (mDirModifiedTS != mFileOp.lastModified(mDir)) {
+ return true;
+ }
+
+ File props = new File(mDir, SdkConstants.FN_SOURCE_PROP);
+
+ // The directory did not have a props file if target was null or
+ // if mPropsModifiedTS is 0.
+ boolean hadProps = mPropsModifiedTS != 0;
+
+ // Was there a props file and it vanished, or there wasn't and there's one now?
+ if (hadProps != mFileOp.isFile(props)) {
+ return true;
+ }
+
+ if (hadProps) {
+ // Has source.props file modified-timestamp changed?
+ if (mPropsModifiedTS != mFileOp.lastModified(props)) {
+ return true;
+ }
+ // Had the content of source.props changed?
+ if (mPropsChecksum != getFileChecksum(props)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Computes an adler32 checksum (source.props are small files, so this
+ * should be OK with an acceptable collision rate.)
+ */
+ private long getFileChecksum(@NonNull File file) {
+ InputStream fis = null;
+ try {
+ fis = mFileOp.newFileInputStream(file);
+ Adler32 a = new Adler32();
+ byte[] buf = new byte[1024];
+ int n;
+ while ((n = fis.read(buf)) > 0) {
+ a.update(buf, 0, n);
+ }
+ return a.getValue();
+ } catch (Exception ignore) {
+ } finally {
+ try {
+ if (fis != null) {
+ fis.close();
+ }
+ } catch(Exception ignore) {}
+ }
+ return 0;
+ }
+
+ /** Returns a visual representation of this object for debugging. */
+ @Override
+ public String toString() {
+ String s = String.format("<DirInfo %1$s TS=%2$d", mDir, mDirModifiedTS); //$NON-NLS-1$
+ if (mPropsModifiedTS != 0) {
+ s += String.format(" | Props TS=%1$d, Chksum=%2$s", //$NON-NLS-1$
+ mPropsModifiedTS, mPropsChecksum);
+ }
+ return s + ">"; //$NON-NLS-1$
+ }
+
+ /**
+ * Returns the hashCode of the underlying File object.
+ * <p/>
+ * When a {@link DirInfo} is placed in a map, what matters is to use the underlying
+ * File object as the key so {@link #hashCode()} and {@link #equals(Object)} both
+ * return the properties of the underlying File object.
+ *
+ * @see File#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return mDir.hashCode();
+ }
+
+ /**
+ * Checks equality of the underlying File object.
+ * <p/>
+ * When a {@link DirInfo} is placed in a map, what matters is to use the underlying
+ * File object as the key so {@link #hashCode()} and {@link #equals(Object)} both
+ * return the properties of the underlying File object.
+ *
+ * @see File#equals(Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ return mDir.equals(obj);
+ };
+ }
+
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/local/LocalSourcePkgInfo.java b/sdklib/src/main/java/com/android/sdklib/local/LocalSourcePkgInfo.java
new file mode 100755
index 0000000..fec0877
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/local/LocalSourcePkgInfo.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.local;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.internal.repository.packages.Package;
+import com.android.sdklib.internal.repository.packages.SourcePackage;
+import com.android.sdklib.repository.MajorRevision;
+
+import java.io.File;
+import java.util.Properties;
+
+/**
+ * Local source package, for a given platform's {@link AndroidVersion}.
+ * The package itself has a major revision.
+ * There should be only one for a given android platform version.
+ */
+public class LocalSourcePkgInfo extends LocalAndroidVersionPkgInfo {
+
+ @NonNull
+ private final MajorRevision mRevision;
+
+ public LocalSourcePkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull AndroidVersion version,
+ @NonNull MajorRevision revision) {
+ super(localSdk, localDir, sourceProps, version);
+ mRevision = revision;
+ }
+
+ @Override
+ public int getType() {
+ return LocalSdk.PKG_SOURCES;
+ }
+
+ @Override
+ public boolean hasMajorRevision() {
+ return true;
+ }
+
+ @NonNull
+ @Override
+ public MajorRevision getMajorRevision() {
+ return mRevision;
+ }
+
+ @Nullable
+ @Override
+ public Package getPackage() {
+ Package pkg = super.getPackage();
+ if (pkg == null) {
+ try {
+ pkg = SourcePackage.create(getLocalDir(), getSourceProperties());
+ setPackage(pkg);
+ } catch (Exception e) {
+ appendLoadError("Failed to parse package: %1$s", e.toString());
+ }
+ }
+ return pkg;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/local/LocalSysImgPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/local/LocalSysImgPkgInfo.java
new file mode 100755
index 0000000..01c16b7
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/local/LocalSysImgPkgInfo.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.local;
+
+import com.android.annotations.NonNull;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.repository.MajorRevision;
+
+import java.io.File;
+import java.util.Properties;
+
+/**
+ * Local system-image package, for a given platform's {@link AndroidVersion}
+ * and given ABI.
+ * The package itself has a major revision.
+ * There should be only one for a given android platform version & ABI.
+ */
+public class LocalSysImgPkgInfo extends LocalAndroidVersionPkgInfo {
+
+ @NonNull
+ private final MajorRevision mRevision;
+ @NonNull
+ private final String mAbi;
+
+ public LocalSysImgPkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull AndroidVersion version,
+ @NonNull String abi,
+ @NonNull MajorRevision revision) {
+ super(localSdk, localDir, sourceProps, version);
+ mAbi = abi;
+ mRevision = revision;
+
+ }
+
+ @Override
+ public int getType() {
+ return LocalSdk.PKG_SYS_IMAGES;
+ }
+
+ @Override
+ public boolean hasMajorRevision() {
+ return true;
+ }
+
+ @NonNull
+ @Override
+ public MajorRevision getMajorRevision() {
+ return mRevision;
+ }
+
+ @Override
+ public boolean hasPath() {
+ return true;
+ }
+
+ /** The System-image path is its ABI. */
+ @NonNull
+ @Override
+ public String getPath() {
+ return getAbi();
+ }
+
+ @NonNull
+ public String getAbi() {
+ return mAbi;
+ }
+
+ // TODO create package on demand if needed. This might not be needed
+ // since typically system-images are retrieved via IAndroidTarget.
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/local/LocalToolPkgInfo.java b/sdklib/src/main/java/com/android/sdklib/local/LocalToolPkgInfo.java
new file mode 100755
index 0000000..832b278
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/local/LocalToolPkgInfo.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.local;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.internal.repository.archives.Archive.Arch;
+import com.android.sdklib.internal.repository.archives.Archive.Os;
+import com.android.sdklib.internal.repository.packages.Package;
+import com.android.sdklib.internal.repository.packages.ToolPackage;
+import com.android.sdklib.repository.FullRevision;
+
+import java.io.File;
+import java.util.Properties;
+
+public class LocalToolPkgInfo extends LocalFullRevisionPkgInfo {
+
+ public LocalToolPkgInfo(@NonNull LocalSdk localSdk,
+ @NonNull File localDir,
+ @NonNull Properties sourceProps,
+ @NonNull FullRevision revision) {
+ super(localSdk, localDir, sourceProps, revision);
+ }
+
+ @Override
+ public int getType() {
+ return LocalSdk.PKG_TOOLS;
+ }
+
+ @Nullable
+ @Override
+ public Package getPackage() {
+ Package pkg = super.getPackage();
+ if (pkg == null) {
+ try {
+ pkg = ToolPackage.create(
+ null, //source
+ getSourceProperties(), //properties
+ 0, //revision
+ null, //license
+ "Tools", //description
+ null, //descUrl
+ Os.getCurrentOs(), //archiveOs
+ Arch.getCurrentArch(), //archiveArch
+ getLocalDir().getPath() //archiveOsPath
+ );
+ setPackage(pkg);
+ } catch (Exception e) {
+ appendLoadError("Failed to parse package: %1$s", e.toString());
+ }
+ }
+ return pkg;
+ }
+}
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/MajorRevision.java b/sdklib/src/main/java/com/android/sdklib/repository/MajorRevision.java
index 9ee7b31..d0aeb63 100755
--- a/sdklib/src/main/java/com/android/sdklib/repository/MajorRevision.java
+++ b/sdklib/src/main/java/com/android/sdklib/repository/MajorRevision.java
@@ -28,6 +28,10 @@
*/
public class MajorRevision extends FullRevision {
+ public MajorRevision(FullRevision fullRevision) {
+ super(fullRevision.getMajor(), IMPLICIT_MINOR_REV, IMPLICIT_MICRO_REV);
+ }
+
public MajorRevision(int major) {
super(major, IMPLICIT_MINOR_REV, IMPLICIT_MICRO_REV);
}
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/SdkAddonConstants.java b/sdklib/src/main/java/com/android/sdklib/repository/SdkAddonConstants.java
index 4af2276..f818820 100755
--- a/sdklib/src/main/java/com/android/sdklib/repository/SdkAddonConstants.java
+++ b/sdklib/src/main/java/com/android/sdklib/repository/SdkAddonConstants.java
@@ -27,6 +27,12 @@
public class SdkAddonConstants extends RepoConstants {
/**
+ * The latest version of the sdk-addon XML Schema.
+ * Valid version numbers are between 1 and this number, included.
+ */
+ public static final int NS_LATEST_VERSION = 6;
+
+ /**
* The default name looked for by {@link SdkSource} when trying to load an
* sdk-addon XML if the URL doesn't match an existing resource.
*/
@@ -42,12 +48,6 @@
*/
public static final String NS_PATTERN = NS_BASE + "([1-9][0-9]*)"; //$NON-NLS-1$
- /**
- * The latest version of the sdk-addon XML Schema.
- * Valid version numbers are between 1 and this number, included.
- */
- public static final int NS_LATEST_VERSION = 5;
-
/** The XML namespace of the latest sdk-addon XML. */
public static final String NS_URI = getSchemaUri(NS_LATEST_VERSION);
diff --git a/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-6.xsd b/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-6.xsd
new file mode 100755
index 0000000..3457aad
--- /dev/null
+++ b/sdklib/src/main/java/com/android/sdklib/repository/sdk-addon-6.xsd
@@ -0,0 +1,472 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+<xsd:schema
+ targetNamespace="http://schemas.android.com/sdk/android/addon/6"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:sdk="http://schemas.android.com/sdk/android/addon/6"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified"
+ version="1">
+
+ <!-- The repository contains a collection of downloadable items known as
+ "packages". Each package has a type and various attributes and contains
+ a list of file "archives" that can be downloaded for specific OSes.
+
+ An Android Addon repository is a web site that contains an "addon.xml"
+ file that conforms to this XML Schema.
+
+ History:
+ - v1 is used by the SDK Updater in Tools r8. It is split out of the
+ main SDK Repository XML Schema and can only contain <addon> and
+ <extra> packages.
+
+ - v2 is used by the SDK Updater in Tools r12.
+ - <extra> element now has a <project-files> element that contains 1 or
+ or more <path>, each indicating the relative path of a file that this package
+ can contribute to installed projects.
+ - <addon> element now has an optional <layoutlib> that indicates the API
+ and revision of the layout library for this particular add-on, if any.
+
+ - v3 is used by the SDK Manager in Tools r14:
+ - <extra> now has an <old-paths> element, a ;-separated list of old paths that
+ should be detected and migrated to the new <path> for that package.
+
+ - v4 is used by the SDK Manager in Tools r18:
+ - <extra> and <addon> are not in the Repository XSD v6 anymore.
+ - <extra> get a new field <name-display>, which is used by the SDK Manager to
+ customize the name of the extra in the list display. The single <vendor>
+ field becomes <vendor-id> and <vendor-display>, the id being used internally
+ and the display in the UI.
+ - <addon> does the same, where <name> is replaced by <name-id> and <name-display>
+ and <vendor> is replaced by <vendor-id> and <vendor-display>.
+
+ - v5 is used by the SDK Manager in Tools r20:
+ - The <beta-rc> element is no longer supported. It was never implemented anyway.
+ - For <tool> and <platform-tool> packages, the <revision> element becomes a
+ a "full revision" element with <major>, <minor>, <micro> and <preview> sub-elements.
+ - <min-tools-rev> for <extra> becomes a full revision element.
+
+ - v6 is used by the SDK Manager in Tools r22.3:
+ - <extra> revision is now a "full revision" element with <major>, <minor>, <micro>.
+ It does not support the <revision><preview> sub-element though.
+ -->
+
+ <xsd:element name="sdk-addon" type="sdk:repositoryType" />
+
+ <xsd:complexType name="repositoryType">
+ <xsd:annotation>
+ <xsd:documentation>
+ The repository contains a collection of downloadable packages.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="add-on" type="sdk:addonType" />
+ <xsd:element name="extra" type="sdk:extraType" />
+ <xsd:element name="license" type="sdk:licenseType" />
+ </xsd:choice>
+ </xsd:complexType>
+
+
+ <!-- The definition of an SDK Add-on package. -->
+
+ <xsd:complexType name="addonType">
+ <xsd:annotation>
+ <xsd:documentation>An SDK add-on package.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The internal name id of the add-on. Must be unique per vendor. -->
+ <xsd:element name="name-id" type="sdk:idType" />
+ <!-- The displayed name of the add-on. -->
+ <xsd:element name="name-display" type="xsd:normalizedString" />
+
+ <!-- The internal vendor id of the add-on. Must be unique amongst vendors. -->
+ <xsd:element name="vendor-id" type="sdk:idType" />
+ <!-- The displayed vendor name of the add-on. -->
+ <xsd:element name="vendor-display" type="xsd:normalizedString" />
+
+ <!-- The Android API Level for the add-on. An int > 0. -->
+ <xsd:element name="api-level" type="xsd:positiveInteger" />
+ <!-- Note: Add-ons do not support 'codenames' (a.k.a. API previews). -->
+ <!-- The revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="xsd:positiveInteger" />
+
+ <!-- An add-on can declare 0 or more libraries.
+ This element is mandatory but it can be empty.
+ -->
+
+ <xsd:element name="libs">
+ <xsd:complexType>
+ <xsd:sequence minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="lib">
+ <xsd:complexType>
+ <xsd:all>
+ <!-- The name of the library. -->
+ <xsd:element name="name" type="xsd:normalizedString" />
+ <!-- The optional description of this add-on library. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+
+ <!-- optional elements -->
+
+ <!-- The optional license of this package. If present, users will have
+ to agree to it before downloading. -->
+ <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+ <!-- The optional description of this package. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ <!-- The optional description URL of this package -->
+ <xsd:element name="desc-url" type="xsd:token" minOccurs="0" />
+ <!-- The optional release note for this package. -->
+ <xsd:element name="release-note" type="xsd:string" minOccurs="0" />
+ <!-- The optional release note URL of this package -->
+ <xsd:element name="release-url" type="xsd:token" minOccurs="0" />
+ <!-- A list of file archives for this package. -->
+ <xsd:element name="archives" type="sdk:archivesType" />
+
+ <!-- An optional element indicating the package is obsolete.
+ The string content is however currently not defined and ignored. -->
+ <xsd:element name="obsolete" type="xsd:string" minOccurs="0" />
+
+ <!-- Optional information on the layoutlib packaged in this platform. -->
+ <xsd:element name="layoutlib" type="sdk:layoutlibType" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <xsd:simpleType name="idType">
+ <xsd:annotation>
+ <xsd:documentation>
+ An ID string for an addon/extra name-id or vendor-id
+ can only be simple alphanumeric string.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[a-zA-Z0-9_-]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+
+ <!-- The definition of a layout library used by an addon. -->
+
+ <xsd:complexType name="layoutlibType" >
+ <xsd:annotation>
+ <xsd:documentation>
+ Version information for a layoutlib included in an addon.
+ .</xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The layoutlib API level, an int > 0,
+ incremented with each new incompatible lib. -->
+ <xsd:element name="api" type="xsd:positiveInteger" />
+ <!-- The incremental minor revision for that API, e.g. in case of bug fixes.
+ Optional. An int >= 0, assumed to be 0 if the element is missing. -->
+ <xsd:element name="revision" type="xsd:nonNegativeInteger" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of an SDK extra package. This kind of package is for
+ "free" content. Such packages are installed in SDK/extras/vendor/path.
+ -->
+
+ <xsd:complexType name="extraType" >
+ <xsd:annotation>
+ <xsd:documentation>
+ An SDK extra package. This kind of package is for "free" content.
+ Such packages are installed in SDK/vendor/path.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The displayed name of the extra. -->
+ <xsd:element name="name-display" type="xsd:normalizedString" />
+
+ <!-- The internal vendor id of the extra. Must be unique amongst vendors. -->
+ <xsd:element name="vendor-id" type="sdk:idType" />
+ <!-- The displayed vendor name of the extra. -->
+ <xsd:element name="vendor-display" type="xsd:normalizedString" />
+
+ <!-- The install path sub-folder name. It must not be empty. -->
+ <xsd:element name="path" type="sdk:segmentType" />
+
+ <!-- A semi-colon separated list of "obsolete" path names which are equivalent
+ to the current 'path' name. When a package is seen using an old-paths' name,
+ the package manager will try to upgrade it to the new path. -->
+ <xsd:element name="old-paths" type="sdk:segmentListType" minOccurs="0" />
+
+ <!-- The revision, in major.minor.micro format, incremented each time a new
+ package is generated. -->
+ <xsd:element name="revision" type="sdk:revisionNoPreviewType" minOccurs="0" />
+
+ <!-- A list of file archives for this package. -->
+ <xsd:element name="archives" type="sdk:archivesType" />
+
+ <!-- optional elements -->
+
+ <!-- The optional license of this package. If present, users will have
+ to agree to it before downloading. -->
+ <xsd:element name="uses-license" type="sdk:usesLicenseType" minOccurs="0" />
+ <!-- The optional description of this package. -->
+ <xsd:element name="description" type="xsd:string" minOccurs="0" />
+ <!-- The optional description URL of this package -->
+ <xsd:element name="desc-url" type="xsd:token" minOccurs="0" />
+ <!-- The optional release note for this package. -->
+ <xsd:element name="release-note" type="xsd:string" minOccurs="0" />
+ <!-- The optional release note URL of this package -->
+ <xsd:element name="release-url" type="xsd:token" minOccurs="0" />
+ <!-- The minimal revision of tools required by this package.
+ Optional. If present, must be a revision element. -->
+ <xsd:element name="min-tools-rev" type="sdk:revisionType" minOccurs="0" />
+ <!-- The minimal API level required by this package.
+ Optional. If present, must be an int > 0. -->
+ <xsd:element name="min-api-level" type="xsd:positiveInteger" minOccurs="0" />
+
+ <!-- An optional element indicating the package is obsolete.
+ The string content is however currently not defined and ignored. -->
+ <xsd:element name="obsolete" type="xsd:string" minOccurs="0" />
+
+ <!-- A list of project files contributed by this package. Optional. -->
+ <xsd:element name="project-files" type="sdk:projectFilesType" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- A full revision, with a major.minor.micro and an optional preview number.
+ The major number is mandatory, the other elements are optional.
+ -->
+
+ <xsd:complexType name="revisionType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A full revision, with a major.minor.micro and an
+ optional preview number. The major number is mandatory.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The major revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="major" type="xsd:positiveInteger" />
+ <!-- The minor revision, an int >= 0, incremented each time a new
+ minor package is generated. Assumed to be 0 if missing. -->
+ <xsd:element name="minor" type="xsd:nonNegativeInteger" minOccurs="0" />
+ <!-- The micro revision, an int >= 0, incremented each time a new
+ buf fix is generated. Assumed to be 0 if missing. -->
+ <xsd:element name="micro" type="xsd:nonNegativeInteger" minOccurs="0" />
+ <!-- The preview/release candidate revision, an int > 0,
+ incremented each time a new preview is generated.
+ Not present for final releases. -->
+ <xsd:element name="preview" type="xsd:positiveInteger" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- A full revision, with a major.minor.micro but that does not support the
+ optional preview number.
+ The major number is mandatory, the other elements are optional.
+ -->
+
+ <xsd:complexType name="revisionNoPreviewType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A full revision, with a major.minor.micro and no support for
+ the optional preview number. The major number is mandatory.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:all>
+ <!-- The major revision, an int > 0, incremented each time a new
+ package is generated. -->
+ <xsd:element name="major" type="xsd:positiveInteger" />
+ <!-- The minor revision, an int >= 0, incremented each time a new
+ minor package is generated. Assumed to be 0 if missing. -->
+ <xsd:element name="minor" type="xsd:nonNegativeInteger" minOccurs="0" />
+ <!-- The micro revision, an int >= 0, incremented each time a new
+ buf fix is generated. Assumed to be 0 if missing. -->
+ <xsd:element name="micro" type="xsd:nonNegativeInteger" minOccurs="0" />
+ </xsd:all>
+ </xsd:complexType>
+
+
+ <!-- The definition of a path segment used by the extra element. -->
+
+ <xsd:simpleType name="segmentType">
+ <xsd:annotation>
+ <xsd:documentation>
+ One path segment for the install path of an extra element.
+ It must be a single-segment path. It must not be empty.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[a-zA-Z0-9_]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="segmentListType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A semi-colon separated list of a segmentTypes.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="[a-zA-Z0-9_;]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+
+ <!-- The definition of a license to be referenced by the uses-license element. -->
+
+ <xsd:complexType name="licenseType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A license definition. Such a license must be used later as a reference
+ using a uses-license element in one of the package elements.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="id" type="xsd:ID" />
+ <xsd:attribute name="type" type="xsd:token" fixed="text" />
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+
+ <!-- Type describing the license used by a package.
+ The license MUST be defined using a license node and referenced
+ using the ref attribute of the license element inside a package.
+ -->
+
+ <xsd:complexType name="usesLicenseType">
+ <xsd:annotation>
+ <xsd:documentation>
+ Describes the license used by a package. The license MUST be defined
+ using a license node and referenced using the ref attribute of the
+ license element inside a package.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute name="ref" type="xsd:IDREF" />
+ </xsd:complexType>
+
+
+ <!-- A collection of files that can be downloaded for a given architecture.
+ The <archives> node is mandatory in the repository elements and the
+ collection must have at least one <archive> declared.
+ Each archive is a zip file that will be unzipped in a location that depends
+ on its package type.
+ -->
+
+ <xsd:complexType name="archivesType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A collection of files that can be downloaded for a given architecture.
+ The <archives> node is mandatory in the repository packages and the
+ collection must have at least one <archive> declared.
+ Each archive is a zip file that will be unzipped in a location that depends
+ on its package type.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="1" maxOccurs="unbounded">
+ <!-- One archive file -->
+ <xsd:element name="archive">
+ <xsd:complexType>
+ <!-- Properties of the archive file -->
+ <xsd:all>
+ <!-- The size in bytes of the archive to download. -->
+ <xsd:element name="size" type="xsd:positiveInteger" />
+ <!-- The checksum of the archive file. -->
+ <xsd:element name="checksum" type="sdk:checksumType" />
+ <!-- The URL is an absolute URL if it starts with http://, https://
+ or ftp://. Otherwise it is relative to the parent directory that
+ contains this repository.xml -->
+ <xsd:element name="url" type="xsd:token" />
+ </xsd:all>
+
+ <!-- Attributes that identify the OS and architecture -->
+ <xsd:attribute name="os" use="required">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="any" />
+ <xsd:enumeration value="linux" />
+ <xsd:enumeration value="macosx" />
+ <xsd:enumeration value="windows" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ <xsd:attribute name="arch" use="optional">
+ <xsd:simpleType>
+ <xsd:restriction base="xsd:token">
+ <xsd:enumeration value="any" />
+ <xsd:enumeration value="ppc" />
+ <xsd:enumeration value="x86" />
+ <xsd:enumeration value="x86_64" />
+ </xsd:restriction>
+ </xsd:simpleType>
+ </xsd:attribute>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:sequence>
+ </xsd:complexType>
+
+
+ <!-- A collection of file paths available in an <extra> package
+ that can be installed in an Android project.
+ If present, the <project-files> collection must contain at least one path.
+ Each path is relative to the root directory of the package.
+ -->
+
+ <xsd:complexType name="projectFilesType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A collection of file paths available in an <extra> package
+ that can be installed in an Android project.
+ If present, the <project-files> collection must contain at least one path.
+ Each path is relative to the root directory of the package.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:sequence minOccurs="1" maxOccurs="unbounded">
+ <!-- One JAR Path, relative to the root folder of the package. -->
+ <xsd:element name="path" type="xsd:string" />
+ </xsd:sequence>
+ </xsd:complexType>
+
+
+ <!-- The definition of a file checksum -->
+
+ <xsd:simpleType name="sha1Number">
+ <xsd:annotation>
+ <xsd:documentation>A SHA1 checksum.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="([0-9a-fA-F]){40}"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:complexType name="checksumType">
+ <xsd:annotation>
+ <xsd:documentation>A file checksum, currently only SHA1.</xsd:documentation>
+ </xsd:annotation>
+ <xsd:simpleContent>
+ <xsd:extension base="sdk:sha1Number">
+ <xsd:attribute name="type" type="xsd:token" fixed="sha1" />
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+</xsd:schema>
diff --git a/sdklib/src/test/java/com/android/sdklib/AndroidTargetHashTest.java b/sdklib/src/test/java/com/android/sdklib/AndroidTargetHashTest.java
index 4e36efd..457f7f6 100755
--- a/sdklib/src/test/java/com/android/sdklib/AndroidTargetHashTest.java
+++ b/sdklib/src/test/java/com/android/sdklib/AndroidTargetHashTest.java
@@ -46,4 +46,17 @@
assertEquals("vendor 10:my-addon:10", AndroidTargetHash.getTargetHashString(a));
}
+ public void testGetPlatformVersion() {
+ assertNull(AndroidTargetHash.getPlatformVersion("blah-5"));
+ assertNull(AndroidTargetHash.getPlatformVersion("android-"));
+
+ AndroidVersion version = AndroidTargetHash.getPlatformVersion("android-5");
+ assertNotNull(version);
+ assertEquals(5, version.getApiLevel());
+ assertNull(version.getCodename());
+
+ version = AndroidTargetHash.getPlatformVersion("android-next");
+ assertNotNull(version);
+ assertEquals("next", version.getCodename());
+ }
}
diff --git a/sdklib/src/test/java/com/android/sdklib/SdkManagerTest.java b/sdklib/src/test/java/com/android/sdklib/SdkManagerTest.java
index 4add17e..549f005 100755
--- a/sdklib/src/test/java/com/android/sdklib/SdkManagerTest.java
+++ b/sdklib/src/test/java/com/android/sdklib/SdkManagerTest.java
@@ -20,6 +20,7 @@
import com.android.SdkConstants;
import com.android.sdklib.ISystemImage.LocationType;
import com.android.sdklib.SdkManager.LayoutlibVersion;
+import com.android.sdklib.internal.androidTarget.PlatformTarget;
import com.android.sdklib.repository.FullRevision;
import com.google.common.collect.Sets;
@@ -56,9 +57,9 @@
}
assertEquals("[]", getLog().toString()); // no errors in the logger
- assertEquals("[3.0.0, 3.0.1, 12.3.4 rc5]", Arrays.toString(v.toArray()));
+ assertEquals("[3.0.0, 3.0.1, 18.3.4 rc5]", Arrays.toString(v.toArray()));
- assertEquals(new FullRevision(12, 3, 4, 5),
+ assertEquals(new FullRevision(18, 3, 4, 5),
sdkman.getLatestBuildTool().getRevision());
// Get infos, first one that doesn't exist returns null.
@@ -79,18 +80,23 @@
"ANDROID_RS_CLANG=$SDK/build-tools/3.0.0/renderscript/clang-include/}>",
cleanPath(sdkman, i.toString()));
- i = sdkman.getBuildTool(new FullRevision(12, 3, 4, 5));
+ i = sdkman.getBuildTool(new FullRevision(18, 3, 4, 5));
assertEquals(
- "<BuildToolInfo rev=12.3.4 rc5, " +
- "mPath=$SDK/build-tools/12.3.4 rc5, " +
+ "<BuildToolInfo rev=18.3.4 rc5, " +
+ "mPath=$SDK/build-tools/18.3.4 rc5, " +
"mPaths={" +
- "AAPT=$SDK/build-tools/12.3.4 rc5/aapt, " +
- "AIDL=$SDK/build-tools/12.3.4 rc5/aidl, " +
- "DX=$SDK/build-tools/12.3.4 rc5/dx, " +
- "DX_JAR=$SDK/build-tools/12.3.4 rc5/lib/dx.jar, " +
- "LLVM_RS_CC=$SDK/build-tools/12.3.4 rc5/llvm-rs-cc, " +
- "ANDROID_RS=$SDK/build-tools/12.3.4 rc5/renderscript/include/, " +
- "ANDROID_RS_CLANG=$SDK/build-tools/12.3.4 rc5/renderscript/clang-include/}>",
+ "AAPT=$SDK/build-tools/18.3.4 rc5/aapt, " +
+ "AIDL=$SDK/build-tools/18.3.4 rc5/aidl, " +
+ "DX=$SDK/build-tools/18.3.4 rc5/dx, " +
+ "DX_JAR=$SDK/build-tools/18.3.4 rc5/lib/dx.jar, " +
+ "LLVM_RS_CC=$SDK/build-tools/18.3.4 rc5/llvm-rs-cc, " +
+ "ANDROID_RS=$SDK/build-tools/18.3.4 rc5/renderscript/include/, " +
+ "ANDROID_RS_CLANG=$SDK/build-tools/18.3.4 rc5/renderscript/clang-include/, " +
+ "BCC_COMPAT=$SDK/build-tools/18.3.4 rc5/bcc_compat, " +
+ "LD_ARM=$SDK/build-tools/18.3.4 rc5/arm-linux-androideabi-ld, " +
+ "LD_X86=$SDK/build-tools/18.3.4 rc5/i686-linux-android-ld, " +
+ "LD_MIPS=$SDK/build-tools/18.3.4 rc5/mipsel-linux-android-ld" +
+ "}>",
cleanPath(sdkman, i.toString()));
}
diff --git a/sdklib/src/test/java/com/android/sdklib/SdkManagerTestCase.java b/sdklib/src/test/java/com/android/sdklib/SdkManagerTestCase.java
index 0c18948f..86945f3 100755
--- a/sdklib/src/test/java/com/android/sdklib/SdkManagerTestCase.java
+++ b/sdklib/src/test/java/com/android/sdklib/SdkManagerTestCase.java
@@ -18,21 +18,24 @@
import com.android.SdkConstants;
+import com.android.annotations.NonNull;
import com.android.prefs.AndroidLocation;
import com.android.prefs.AndroidLocation.AndroidLocationException;
import com.android.sdklib.internal.avd.AvdManager;
import com.android.sdklib.io.FileOp;
+import com.android.sdklib.local.LocalPlatformPkgInfo;
import com.android.sdklib.mock.MockLog;
+import com.android.sdklib.repository.FullRevision;
import com.android.sdklib.repository.PkgProps;
import com.android.sdklib.repository.SdkRepoConstants;
import com.android.utils.ILogger;
+import junit.framework.TestCase;
+
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
-import junit.framework.TestCase;
-
/**
* Test case that allocates a temporary SDK, a temporary AVD base folder
* with an SdkManager and an AvdManager that points to them.
@@ -226,13 +229,16 @@
new File(targetDir, SdkConstants.FN_FRAMEWORK_AIDL).createNewFile();
createSourceProps(targetDir,
+ PkgProps.PKG_REVISION, "1",
+ PkgProps.PLATFORM_VERSION, "0.0",
+ PkgProps.VERSION_API_LEVEL, "0",
PkgProps.LAYOUTLIB_API, "5",
PkgProps.LAYOUTLIB_REV, "2");
createFileProps(SdkConstants.FN_BUILD_PROP, targetDir,
- SdkManager.PROP_VERSION_RELEASE, "0.0",
- SdkManager.PROP_VERSION_SDK, "0",
- SdkManager.PROP_VERSION_CODENAME, "REL");
+ LocalPlatformPkgInfo.PROP_VERSION_RELEASE, "0.0",
+ LocalPlatformPkgInfo.PROP_VERSION_SDK, "0",
+ LocalPlatformPkgInfo.PROP_VERSION_CODENAME, "REL");
return targetDir;
}
@@ -243,6 +249,7 @@
new File(imagesDir, "userdata.img").createNewFile();
createSourceProps(imagesDir,
+ PkgProps.PKG_REVISION, "0",
PkgProps.VERSION_API_LEVEL, "0",
PkgProps.SYS_IMG_ABI, abiType);
}
@@ -278,25 +285,64 @@
private void makeBuildTools(File buildToolsTopDir) throws IOException {
buildToolsTopDir.mkdir();
- for (String revision : new String[] { "3.0.0", "3.0.1", "12.3.4 rc5" }) {
+ for (String revision : new String[] { "3.0.0", "3.0.1", "18.3.4 rc5" }) {
File buildToolsDir = new File(buildToolsTopDir, revision);
createSourceProps(buildToolsDir, PkgProps.PKG_REVISION, revision);
- createTextFile(buildToolsDir, SdkConstants.FN_AAPT);
- createTextFile(buildToolsDir, SdkConstants.FN_AIDL);
- createTextFile(buildToolsDir, SdkConstants.FN_DX);
- createTextFile(buildToolsDir, SdkConstants.FD_LIB + File.separator +
- SdkConstants.FN_DX_JAR);
- createTextFile(buildToolsDir, SdkConstants.FN_RENDERSCRIPT);
- createTextFile(buildToolsDir, SdkConstants.OS_FRAMEWORK_RS + File.separator +
- "placeholder.txt");
- createTextFile(buildToolsDir, SdkConstants.OS_FRAMEWORK_RS_CLANG + File.separator +
- "placeholder.txt");
- }
+ FullRevision fullRevision = FullRevision.parseRevision(revision);
+ createFakeBuildTools(
+ buildToolsDir, fullRevision,
+ BuildToolInfo.PathId.AAPT, SdkConstants.FN_AAPT);
+ createFakeBuildTools(
+ buildToolsDir, fullRevision,
+ BuildToolInfo.PathId.AIDL, SdkConstants.FN_AIDL);
+ createFakeBuildTools(
+ buildToolsDir, fullRevision,
+ BuildToolInfo.PathId.DX, SdkConstants.FN_DX);
+ createFakeBuildTools(
+ buildToolsDir, fullRevision,
+ BuildToolInfo.PathId.DX_JAR, SdkConstants.FD_LIB + File.separator +
+ SdkConstants.FN_DX_JAR);
+ createFakeBuildTools(
+ buildToolsDir, fullRevision,
+ BuildToolInfo.PathId.LLVM_RS_CC, SdkConstants.FN_RENDERSCRIPT);
+ createFakeBuildTools(
+ buildToolsDir, fullRevision,
+ BuildToolInfo.PathId.ANDROID_RS, SdkConstants.OS_FRAMEWORK_RS + File.separator +
+ "placeholder.txt");
+ createFakeBuildTools(
+ buildToolsDir, fullRevision,
+ BuildToolInfo.PathId.ANDROID_RS_CLANG, SdkConstants.OS_FRAMEWORK_RS_CLANG + File.separator +
+ "placeholder.txt");
+ createFakeBuildTools(
+ buildToolsDir, fullRevision,
+ BuildToolInfo.PathId.BCC_COMPAT, SdkConstants.FN_BCC_COMPAT);
+ createFakeBuildTools(
+ buildToolsDir, fullRevision,
+ BuildToolInfo.PathId.LD_ARM, SdkConstants.FN_LD_ARM);
+ createFakeBuildTools(
+ buildToolsDir, fullRevision,
+ BuildToolInfo.PathId.LD_MIPS, SdkConstants.FN_LD_MIPS);
+ createFakeBuildTools(
+ buildToolsDir, fullRevision,
+ BuildToolInfo.PathId.LD_X86, SdkConstants.FN_LD_X86);
+ }
}
+ private void createFakeBuildTools(@NonNull File dir,
+ @NonNull FullRevision buildToolsRevision,
+ @NonNull BuildToolInfo.PathId pathId,
+ @NonNull String filepath)
+ throws IOException {
+
+ if (pathId.isPresentIn(buildToolsRevision)) {
+ createTextFile(dir, filepath);
+ }
+ }
+
+
private void createSourceProps(File parentDir, String...paramValuePairs) throws IOException {
createFileProps(SdkConstants.FN_SOURCE_PROP, parentDir, paramValuePairs);
}
diff --git a/sdklib/src/test/java/com/android/sdklib/devices/DeviceParserTest.java b/sdklib/src/test/java/com/android/sdklib/devices/DeviceParserTest.java
index 611fc56..5b08d2c 100644
--- a/sdklib/src/test/java/com/android/sdklib/devices/DeviceParserTest.java
+++ b/sdklib/src/test/java/com/android/sdklib/devices/DeviceParserTest.java
@@ -48,7 +48,7 @@
devices.size());
Device device = devices.get(0);
- assertEquals("Galaxy Nexus", device.getName());
+ assertEquals("Galaxy Nexus", device.getDisplayName());
assertEquals("Samsung", device.getManufacturer());
// Test Meta information
@@ -190,7 +190,7 @@
devices.size());
Device device = devices.get(0);
- assertEquals("Galaxy Nexus", device.getName());
+ assertEquals("Galaxy Nexus", device.getDisplayName());
assertEquals(new Dimension(1280, 720), device.getScreenSize(ScreenOrientation.LANDSCAPE));
assertEquals(new Dimension(720, 1280), device.getScreenSize(ScreenOrientation.PORTRAIT));
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/LocalSdkParserTest.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/LocalSdkParserTest.java
index e61451f..8baaee4 100755
--- a/sdklib/src/test/java/com/android/sdklib/internal/repository/LocalSdkParserTest.java
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/LocalSdkParserTest.java
@@ -38,7 +38,7 @@
assertEquals(
"[Android SDK Tools, revision 1.0.1, " +
"Android SDK Platform-tools, revision 17.1.2, " +
- "Android SDK Build-tools, revision 12.3.4 rc5, " +
+ "Android SDK Build-tools, revision 18.3.4 rc5, " +
"Android SDK Build-tools, revision 3.0.1, " +
"Android SDK Build-tools, revision 3, " +
"SDK Platform Android 0.0, API 0, revision 1, " +
@@ -82,7 +82,7 @@
monitor)));
assertEquals(
- "[Android SDK Build-tools, revision 12.3.4 rc5, " +
+ "[Android SDK Build-tools, revision 18.3.4 rc5, " +
"Android SDK Build-tools, revision 3.0.1, " +
"Android SDK Build-tools, revision 3]",
Arrays.toString(parser.parseSdk(sdkman.getLocation(),
@@ -105,7 +105,7 @@
assertEquals(
"[Android SDK Tools, revision 1.0.1, " +
"Android SDK Platform-tools, revision 17.1.2, " +
- "Android SDK Build-tools, revision 12.3.4 rc5, " +
+ "Android SDK Build-tools, revision 18.3.4 rc5, " +
"Android SDK Build-tools, revision 3.0.1, " +
"Android SDK Build-tools, revision 3, " +
"SDK Platform Android 0.0, API 0, revision 1, " +
@@ -126,7 +126,7 @@
assertEquals(
"[Android SDK Tools, revision 1.0.1, " +
"Android SDK Platform-tools, revision 17.1.2, " +
- "Android SDK Build-tools, revision 12.3.4 rc5, " +
+ "Android SDK Build-tools, revision 18.3.4 rc5, " +
"Android SDK Build-tools, revision 3.0.1, " +
"Android SDK Build-tools, revision 3, " +
"SDK Platform Android 0.0, API 0, revision 1, " +
@@ -145,7 +145,7 @@
assertEquals(
"[Android SDK Tools, revision 1.0.1, " +
"Android SDK Platform-tools, revision 17.1.2, " +
- "Android SDK Build-tools, revision 12.3.4 rc5, " +
+ "Android SDK Build-tools, revision 18.3.4 rc5, " +
"Android SDK Build-tools, revision 3.0.1, " +
"Android SDK Build-tools, revision 3, " +
"SDK Platform Android 0.0, API 0, revision 1, " +
@@ -158,7 +158,7 @@
assertEquals(
"[Android SDK Tools, revision 1.0.1, " +
"Android SDK Platform-tools, revision 17.1.2, " +
- "Android SDK Build-tools, revision 12.3.4 rc5, " +
+ "Android SDK Build-tools, revision 18.3.4 rc5, " +
"Android SDK Build-tools, revision 3.0.1, " +
"Android SDK Build-tools, revision 3, " +
"SDK Platform Android 0.0, API 0, revision 1, " +
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/MockEmptySdkManager.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/MockEmptySdkManager.java
index be1fb29..4a45a38 100755
--- a/sdklib/src/test/java/com/android/sdklib/internal/repository/MockEmptySdkManager.java
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/MockEmptySdkManager.java
@@ -16,7 +16,6 @@
package com.android.sdklib.internal.repository;
-import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkManager;
/**
@@ -25,6 +24,5 @@
public class MockEmptySdkManager extends SdkManager {
public MockEmptySdkManager(String osSdkPath) {
super(osSdkPath);
- setTargets(new IAndroidTarget[0]);
}
}
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/archives/ArchiveInstallerTest.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/archives/ArchiveInstallerTest.java
index b0b3f03..e5a8041 100755
--- a/sdklib/src/test/java/com/android/sdklib/internal/repository/archives/ArchiveInstallerTest.java
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/archives/ArchiveInstallerTest.java
@@ -203,7 +203,7 @@
"Extra.NameDisplay=Vendor1 OldPath\n" +
"Archive.Os=ANY\n" +
"Pkg.SourceUrl=http\\://repo.example.com/url\n" +
- "Pkg.Revision=2\n" +
+ "Pkg.Revision=2.0.0\n" +
"Extra.VendorId=vendor1\n" +
"'>]"),
stripDate(Arrays.toString(mFile.getOutputStreams())));
@@ -280,7 +280,7 @@
"Extra.NameDisplay=Vendor1 NewPath\n" +
"Archive.Os=ANY\n" +
"Pkg.SourceUrl=http\\://repo.example.com/url\n" +
- "Pkg.Revision=2\n" +
+ "Pkg.Revision=2.0.0\n" +
"Extra.VendorId=vendor1\n" +
"'>]"),
stripDate(Arrays.toString(mFile.getOutputStreams())));
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/ExtraPackageTest_Base.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/ExtraPackageTest_Base.java
new file mode 100755
index 0000000..4cb0f99
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/ExtraPackageTest_Base.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.internal.repository.packages;
+
+import com.android.sdklib.internal.repository.archives.Archive.Arch;
+import com.android.sdklib.internal.repository.archives.Archive.Os;
+import com.android.sdklib.repository.PkgProps;
+
+import java.util.Properties;
+
+public class ExtraPackageTest_Base extends PackageTest {
+
+ @Override
+ public void testCreate() {
+ Properties props = createProps();
+
+ MockExtraPackage p = new MockExtraPackage(
+ null, //source
+ props,
+ "vendor",
+ "the_path",
+ -1, //revision
+ null, //license
+ null, //description
+ null, //descUrl
+ Os.ANY, //archiveOs
+ Arch.ANY, //archiveArch
+ LOCAL_ARCHIVE_PATH
+ );
+
+ testCreatedPackage(p);
+ }
+
+ @Override
+ public void testSaveProperties() {
+ Properties props = createProps();
+
+ MockExtraPackage p = new MockExtraPackage(
+ null, //source
+ props,
+ "vendor",
+ "the_path",
+ -1, //revision
+ null, //license
+ null, //description
+ null, //descUrl
+ Os.ANY, //archiveOs
+ Arch.ANY, //archiveArch
+ LOCAL_ARCHIVE_PATH
+ );
+
+ Properties props2 = new Properties();
+ p.saveProperties(props2);
+
+ assertEquals(props2, props);
+ }
+
+ @Override
+ protected Properties createProps() {
+ Properties props = super.createProps();
+
+ props.setProperty(PkgProps.EXTRA_VENDOR_ID, "vendor");
+ props.setProperty(PkgProps.EXTRA_VENDOR_DISPLAY, "vendor");
+ props.setProperty(PkgProps.EXTRA_PATH, "the_path");
+ props.setProperty(PkgProps.EXTRA_NAME_DISPLAY, "Vendor The Path");
+
+ // Extra revision is now a NoPreviewRevision and writes its full major.minor.micro
+ props.setProperty(PkgProps.PKG_REVISION, "42.0.0");
+
+ // MinToolsPackage properties
+ props.setProperty(PkgProps.MIN_TOOLS_REV, "3.0.1");
+
+ return props;
+ }
+
+ protected void testCreatedMinToolsPackage(MockExtraPackage p) {
+ super.testCreatedPackage(p);
+
+ // MinToolsPackage properties
+ assertEquals("3.0.1", p.getMinToolsRevision().toShortString());
+ }
+}
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/ExtraPackageTest_v3.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/ExtraPackageTest_v3.java
index 6102f13..802fde7 100755
--- a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/ExtraPackageTest_v3.java
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/ExtraPackageTest_v3.java
@@ -27,9 +27,9 @@
/**
* Tests {@link ExtraPackage} using anddon-3.xsd: it has a {@code <path>} and {@code <vendor>}.
- * (it lacks name-display, vendor-id and vendor-display with are in addon-4.xsd)
+ * (it lacks name-display, vendor-id and vendor-display which are in addon-4.xsd)
*/
-public class ExtraPackageTest_v3 extends MinToolsPackageTest {
+public class ExtraPackageTest_v3 extends ExtraPackageTest_Base {
private static final char PS = File.pathSeparatorChar;
@@ -72,8 +72,8 @@
// ExtraPackage properties
props.setProperty(PkgProps.EXTRA_VENDOR_ID, "vendor");
props.setProperty(PkgProps.EXTRA_VENDOR_DISPLAY, "vendor");
- props.setProperty(PkgProps.EXTRA_NAME_DISPLAY, "Vendor The Path");
props.setProperty(PkgProps.EXTRA_PATH, "the_path");
+ props.setProperty(PkgProps.EXTRA_NAME_DISPLAY, "Vendor The Path");
props.setProperty(PkgProps.EXTRA_OLD_PATHS, "old_path1;oldpath2");
props.setProperty(PkgProps.EXTRA_MIN_API_LEVEL, "11");
props.setProperty(PkgProps.EXTRA_PROJECT_FILES,
@@ -127,6 +127,9 @@
// different vendor, same path
Properties props2 = new Properties(props1);
+ props2.setProperty(PkgProps.EXTRA_VENDOR_ID, "");
+ props2.setProperty(PkgProps.EXTRA_VENDOR_DISPLAY, "");
+ props2.setProperty(PkgProps.EXTRA_NAME_DISPLAY, "");
props2.setProperty(PkgProps.EXTRA_VENDOR, "vendor2");
ExtraPackage p2 = createExtraPackage(props2);
assertFalse(p1.sameItemAs(p2));
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/ExtraPackageTest_v4.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/ExtraPackageTest_v4.java
index eff3020..2c0fe15 100755
--- a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/ExtraPackageTest_v4.java
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/ExtraPackageTest_v4.java
@@ -29,7 +29,7 @@
* Tests {@link ExtraPackage} using anddon-4.xsd:
* it has name-display, vendor-id and vendor-display.
*/
-public class ExtraPackageTest_v4 extends MinToolsPackageTest {
+public class ExtraPackageTest_v4 extends ExtraPackageTest_Base {
private static final char PS = File.pathSeparatorChar;
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockExtraPackage.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockExtraPackage.java
index 6d756c5..4a9f0a4 100755
--- a/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockExtraPackage.java
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/packages/MockExtraPackage.java
@@ -45,6 +45,32 @@
Properties props,
String vendor,
String path,
+ int revision,
+ String license,
+ String description,
+ String descUrl,
+ Os archiveOs,
+ Arch archiveArch,
+ String archiveOsPath) {
+ super(
+ source,
+ props,
+ vendor,
+ path,
+ revision,
+ license,
+ description,
+ descUrl,
+ archiveOs,
+ archiveArch,
+ archiveOsPath);
+ }
+
+ public MockExtraPackage(
+ SdkSource source,
+ Properties props,
+ String vendor,
+ String path,
int revision) {
super(
source,
diff --git a/sdklib/src/test/java/com/android/sdklib/internal/repository/sources/SdkAddonSourceTest.java b/sdklib/src/test/java/com/android/sdklib/internal/repository/sources/SdkAddonSourceTest.java
index 046cd1d..f281a98 100755
--- a/sdklib/src/test/java/com/android/sdklib/internal/repository/sources/SdkAddonSourceTest.java
+++ b/sdklib/src/test/java/com/android/sdklib/internal/repository/sources/SdkAddonSourceTest.java
@@ -713,6 +713,154 @@
}
/**
+ * Validate we can load a valid add-on schema version 6
+ */
+ public void testLoadAddonXml_6() throws Exception {
+ InputStream xmlStream = getTestResource("/com/android/sdklib/testdata/addon_sample_6.xml");
+
+ // guess the version from the XML document
+ int version = mSource._getXmlSchemaVersion(xmlStream);
+ assertEquals(6, version);
+
+ Boolean[] validatorFound = new Boolean[] { Boolean.FALSE };
+ String[] validationError = new String[] { null };
+ String url = "not-a-valid-url://" + SdkAddonConstants.URL_DEFAULT_FILENAME;
+
+ String uri = mSource._validateXml(xmlStream, url, version, validationError, validatorFound);
+ assertEquals(Boolean.TRUE, validatorFound[0]);
+ assertEquals(null, validationError[0]);
+ assertEquals(SdkAddonConstants.getSchemaUri(6), uri);
+
+ // Validation was successful, load the document
+ MockMonitor monitor = new MockMonitor();
+ Document doc = mSource._getDocument(xmlStream, monitor);
+ assertNotNull(doc);
+
+ // Get the packages
+ assertTrue(mSource._parsePackages(doc, uri, monitor));
+
+ assertEquals("Found My First add-on, Android API 1, revision 1\n" +
+ "Found My Second add-on, Android API 2, revision 42\n" +
+ "Found This add-on has no libraries, Android API 4, revision 3\n" +
+ "Found Random name, not an id!, revision 43.42.41 (Obsolete)\n" +
+ "Found Yet another extra, by Android, revision 2.0.1\n" +
+ "Found . -..- - .-. .-, revision 2 (Obsolete)\n",
+ monitor.getCapturedVerboseLog());
+ assertEquals("", monitor.getCapturedLog());
+ assertEquals("", monitor.getCapturedErrorLog());
+
+ // check the packages we found... we expected to find 6 packages with each at least
+ // one archive.
+ // Note the order doesn't necessary match the one from the
+ // assertEquald(getCapturedVerboseLog) because packages are sorted using the
+ // Packages' sorting order, e.g. all platforms are sorted by descending API level, etc.
+ Package[] pkgs = mSource.getPackages();
+
+ assertEquals(6, pkgs.length);
+ for (Package p : pkgs) {
+ assertTrue(p.getArchives().length >= 1);
+ }
+
+ // Check the addon packages: vendor/name id vs display
+ ArrayList<String> addonNames = new ArrayList<String>();
+ ArrayList<String> addonVendors = new ArrayList<String>();
+ for (Package p : pkgs) {
+ if (p instanceof AddonPackage) {
+ AddonPackage ap = (AddonPackage) p;
+ addonNames.add(ap.getNameId() + "/" + ap.getDisplayName());
+ addonVendors.add(ap.getVendorId() + "/" + ap.getDisplayVendor());
+ }
+ }
+ // Addons are sorted by addon/vendor id and thus their order differs from the
+ // XML or the parsed package list.
+ assertEquals(
+ "[no_libs/This add-on has no libraries, " +
+ "My_Second_add-on/My Second add-on, " +
+ "My_First_add-on/My First add-on]",
+ Arrays.toString(addonNames.toArray()));
+ assertEquals(
+ "[Joe_Bar/Joe Bar, " +
+ "John_Deer/John Deer, " +
+ "John_Doe/John Doe]",
+ Arrays.toString(addonVendors.toArray()));
+
+ // Check the layoutlib of the platform packages.
+ ArrayList<Pair<Integer, Integer>> layoutlibVers = new ArrayList<Pair<Integer,Integer>>();
+ for (Package p : pkgs) {
+ if (p instanceof AddonPackage) {
+ layoutlibVers.add(((AddonPackage) p).getLayoutlibVersion());
+ }
+ }
+ assertEquals(
+ "[Pair [first=3, second=42], " + // for #3 "This add-on has no libraries"
+ "Pair [first=0, second=0], " + // for #2 "My Second add-on"
+ "Pair [first=5, second=0]]", // for #1 "My First add-on"
+ Arrays.toString(layoutlibVers.toArray()));
+
+
+ // Check the extra packages: path, vendor, install folder, old-paths
+ final String osSdkPath = "SDK";
+ final SdkManager sdkManager = new MockEmptySdkManager(osSdkPath);
+
+ ArrayList<String> extraPaths = new ArrayList<String>();
+ ArrayList<String> extraVendors = new ArrayList<String>();
+ ArrayList<File> extraInstall = new ArrayList<File>();
+ ArrayList<ArrayList<String>> extraFilePaths = new ArrayList<ArrayList<String>>();
+ for (Package p : pkgs) {
+ if (p instanceof ExtraPackage) {
+ ExtraPackage ep = (ExtraPackage) p;
+ // combine path and old-paths in the form "path [old_path1, old_path2]"
+ extraPaths.add(ep.getPath() + " " + Arrays.toString(ep.getOldPaths()));
+ extraVendors.add(ep.getVendorId() + "/" + ep.getVendorDisplay());
+ extraInstall.add(ep.getInstallFolder(osSdkPath, sdkManager));
+
+ ArrayList<String> filePaths = new ArrayList<String>();
+ for (String filePath : ep.getProjectFiles()) {
+ filePaths.add(filePath);
+ }
+ extraFilePaths.add(filePaths);
+ }
+ }
+ // Extras are sorted by vendor-id/path and thus their order differs from the
+ // XML or the parsed package list.
+ assertEquals(
+ "[extra0000005f [], " + // for extra #3
+ "extra_api_dep [path1, old_path2, oldPath3], " + // for extra #2
+ "usb_driver []]", // for extra #1
+ Arrays.toString(extraPaths.toArray()));
+ assertEquals(
+ "[____/____, " +
+ "android_vendor/Android Vendor, " +
+ "cyclop/The big bus]",
+ Arrays.toString(extraVendors.toArray()));
+ assertEquals(
+ ("[SDK/extras/____/extra0000005f, " +
+ "SDK/extras/android_vendor/extra_api_dep, " +
+ "SDK/extras/cyclop/usb_driver]").replace('/', File.separatorChar),
+ Arrays.toString(extraInstall.toArray()));
+ assertEquals(
+ "[[], " +
+ "[v8/veggies_8.jar, root.jar, dir1/dir 2 with space/mylib.jar], " +
+ "[]]",
+ Arrays.toString(extraFilePaths.toArray()));
+
+
+ // Check the min-tools-rev
+ ArrayList<String> minToolsRevs = new ArrayList<String>();
+ for (Package p : pkgs) {
+ if (p instanceof IMinToolsDependency) {
+ minToolsRevs.add(p.getListDescription() + ": " +
+ ((IMinToolsDependency) p).getMinToolsRevision().toShortString());
+ }
+ }
+ assertEquals(
+ "[. -..- - .-. .- (Obsolete): 3.0.1, " +
+ "Yet another extra, by Android: 3, " +
+ "Random name, not an id! (Obsolete): 3.2.1 rc42]",
+ Arrays.toString(minToolsRevs.toArray()));
+ }
+
+ /**
* Returns an SdkLib file resource as a {@link ByteArrayInputStream},
* which has the advantage that we can use {@link InputStream#reset()} on it
* at any time to read it multiple times.
diff --git a/sdklib/src/test/java/com/android/sdklib/io/MockFileOp.java b/sdklib/src/test/java/com/android/sdklib/io/MockFileOp.java
index cbe5fdd..557a51f 100755
--- a/sdklib/src/test/java/com/android/sdklib/io/MockFileOp.java
+++ b/sdklib/src/test/java/com/android/sdklib/io/MockFileOp.java
@@ -18,17 +18,25 @@
import com.android.SdkConstants;
import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.google.common.base.Charsets;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
-import java.util.HashSet;
+import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
@@ -49,8 +57,8 @@
*/
public class MockFileOp implements IFileOp {
- private final Set<String> mExistinfFiles = new TreeSet<String>();
- private final Set<String> mExistinfFolders = new TreeSet<String>();
+ private final Map<String, FileInfo> mExistingFiles = Maps.newTreeMap();
+ private final Set<String> mExistingFolders = Sets.newTreeSet();
private final List<StringOutputStream> mOutputStreams = new ArrayList<StringOutputStream>();
public MockFileOp() {
@@ -58,15 +66,17 @@
/** Resets the internal state, as if the object had been newly created. */
public void reset() {
- mExistinfFiles.clear();
- mExistinfFolders.clear();
+ mExistingFiles.clear();
+ mExistingFolders.clear();
}
- public String getAgnosticAbsPath(File file) {
+ @NonNull
+ public String getAgnosticAbsPath(@NonNull File file) {
return getAgnosticAbsPath(file.getAbsolutePath());
}
- public String getAgnosticAbsPath(String path) {
+ @NonNull
+ public String getAgnosticAbsPath(@NonNull String path) {
if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
// Try to convert the windows-looking path to a unix-looking one
path = path.replace('\\', '/');
@@ -79,8 +89,8 @@
* Records a new absolute file path.
* Parent folders are not automatically created.
*/
- public void recordExistingFile(File file) {
- mExistinfFiles.add(getAgnosticAbsPath(file));
+ public void recordExistingFile(@NonNull File file) {
+ recordExistingFile(getAgnosticAbsPath(file), 0, null);
}
/**
@@ -91,8 +101,52 @@
* On Windows that means you'll want to use {@link #getAgnosticAbsPath(File)}.
* @param absFilePath A unix-like file path, e.g. "/dir/file"
*/
- public void recordExistingFile(String absFilePath) {
- mExistinfFiles.add(absFilePath);
+ public void recordExistingFile(@NonNull String absFilePath) {
+ recordExistingFile(absFilePath, 0, null);
+ }
+
+ /**
+ * Records a new absolute file path & its input stream content.
+ * Parent folders are not automatically created.
+ * <p/>
+ * The syntax should always look "unix-like", e.g. "/dir/file".
+ * On Windows that means you'll want to use {@link #getAgnosticAbsPath(File)}.
+ * @param absFilePath A unix-like file path, e.g. "/dir/file"
+ * @param inputStream A non-null byte array of content to return
+ * via {@link #newFileInputStream(File)}.
+ */
+ public void recordExistingFile(@NonNull String absFilePath, @Nullable byte[] inputStream) {
+ recordExistingFile(absFilePath, 0, inputStream);
+ }
+
+ /**
+ * Records a new absolute file path & its input stream content.
+ * Parent folders are not automatically created.
+ * <p/>
+ * The syntax should always look "unix-like", e.g. "/dir/file".
+ * On Windows that means you'll want to use {@link #getAgnosticAbsPath(File)}.
+ * @param absFilePath A unix-like file path, e.g. "/dir/file"
+ * @param content A non-null UTF-8 content string to return
+ * via {@link #newFileInputStream(File)}.
+ */
+ public void recordExistingFile(@NonNull String absFilePath, @NonNull String content) {
+ recordExistingFile(absFilePath, 0, content.getBytes(Charsets.UTF_8));
+ }
+
+ /**
+ * Records a new absolute file path & its input stream content.
+ * Parent folders are not automatically created.
+ * <p/>
+ * The syntax should always look "unix-like", e.g. "/dir/file".
+ * On Windows that means you'll want to use {@link #getAgnosticAbsPath(File)}.
+ * @param absFilePath A unix-like file path, e.g. "/dir/file"
+ * @param inputStream A non-null byte array of content to return
+ * via {@link #newFileInputStream(File)}.
+ */
+ public void recordExistingFile(@NonNull String absFilePath,
+ long lastModified,
+ @Nullable byte[] inputStream) {
+ mExistingFiles.put(absFilePath, new FileInfo(lastModified, inputStream));
}
/**
@@ -100,7 +154,7 @@
* Parent folders are not automatically created.
*/
public void recordExistingFolder(File folder) {
- mExistinfFolders.add(getAgnosticAbsPath(folder));
+ mExistingFolders.add(getAgnosticAbsPath(folder));
}
/**
@@ -112,7 +166,7 @@
* @param absFolderPath A unix-like folder path, e.g. "/dir/file"
*/
public void recordExistingFolder(String absFolderPath) {
- mExistinfFolders.add(absFolderPath);
+ mExistingFolders.add(absFolderPath);
}
/**
@@ -121,8 +175,10 @@
* <p/>
* The returned list is sorted by alphabetic absolute path string.
*/
+ @NonNull
public String[] getExistingFiles() {
- return mExistinfFiles.toArray(new String[mExistinfFiles.size()]);
+ Set<String> files = mExistingFiles.keySet();
+ return files.toArray(new String[files.size()]);
}
/**
@@ -131,14 +187,16 @@
* <p/>
* The returned list is sorted by alphabetic absolute path string.
*/
+ @NonNull
public String[] getExistingFolders() {
- return mExistinfFolders.toArray(new String[mExistinfFolders.size()]);
+ return mExistingFolders.toArray(new String[mExistingFolders.size()]);
}
/**
* Returns the {@link StringOutputStream#toString()} as an array, in creation order.
* Array can be empty but not null.
*/
+ @NonNull
public String[] getOutputStreams() {
int n = mOutputStreams.size();
String[] result = new String[n];
@@ -155,7 +213,7 @@
* The argument can be null.
*/
@Override
- public void deleteFileOrFolder(File fileOrFolder) {
+ public void deleteFileOrFolder(@NonNull File fileOrFolder) {
if (fileOrFolder != null) {
if (isDirectory(fileOrFolder)) {
// Must delete content recursively first
@@ -173,7 +231,7 @@
* <em>Note: this mock version does nothing.</em>
*/
@Override
- public void setExecutablePermission(File file) throws IOException {
+ public void setExecutablePermission(@NonNull File file) throws IOException {
// pass
}
@@ -183,7 +241,7 @@
* <em>Note: this mock version does nothing.</em>
*/
@Override
- public void setReadOnly(File file) {
+ public void setReadOnly(@NonNull File file) {
// pass
}
@@ -193,35 +251,63 @@
* <em>Note: this mock version does nothing.</em>
*/
@Override
- public void copyFile(File source, File dest) throws IOException {
+ public void copyFile(@NonNull File source, @NonNull File dest) throws IOException {
// pass
+ throw new UnsupportedOperationException("MockFileUtils.copyFile is not supported."); //$NON-NLS-1$
}
/**
* Checks whether 2 binary files are the same.
*
- * @param source the source file to copy
- * @param destination the destination file to write
+ * @param file1 the source file to copy
+ * @param file2 the destination file to write
* @throws FileNotFoundException if the source files don't exist.
* @throws IOException if there's a problem reading the files.
*/
@Override
- public boolean isSameFile(File source, File destination) throws IOException {
- throw new UnsupportedOperationException("MockFileUtils.isSameFile is not supported."); //$NON-NLS-1$
+ public boolean isSameFile(@NonNull File file1, @NonNull File file2) throws IOException {
+ String path1 = getAgnosticAbsPath(file1);
+ String path2 = getAgnosticAbsPath(file2);
+ FileInfo fi1 = mExistingFiles.get(path1);
+ FileInfo fi2 = mExistingFiles.get(path2);
+
+ if (fi1 == null) {
+ throw new FileNotFoundException("[isSameFile] Mock file not defined: " + path1);
+ }
+
+ if (fi1 == fi2) {
+ return true;
+ }
+
+ if (fi2 == null) {
+ throw new FileNotFoundException("[isSameFile] Mock file not defined: " + path2);
+ }
+
+ byte[] content1 = fi1.getContent();
+ byte[] content2 = fi2.getContent();
+
+ if (content1 == null) {
+ throw new IOException("[isSameFile] Mock file has no content: " + path1);
+ }
+ if (content2 == null) {
+ throw new IOException("[isSameFile] Mock file has no content: " + path2);
+ }
+
+ return Arrays.equals(content1, content2);
}
/** Invokes {@link File#isFile()} on the given {@code file}. */
@Override
- public boolean isFile(File file) {
+ public boolean isFile(@NonNull File file) {
String path = getAgnosticAbsPath(file);
- return mExistinfFiles.contains(path);
+ return mExistingFiles.containsKey(path);
}
/** Invokes {@link File#isDirectory()} on the given {@code file}. */
@Override
- public boolean isDirectory(File file) {
+ public boolean isDirectory(@NonNull File file) {
String path = getAgnosticAbsPath(file);
- if (mExistinfFolders.contains(path)) {
+ if (mExistingFolders.contains(path)) {
return true;
}
@@ -231,12 +317,12 @@
Pattern.quote(path + (path.endsWith("/") ? "" : '/')) + //$NON-NLS-1$ //$NON-NLS-2$
".*"); //$NON-NLS-1$
- for (String folder : mExistinfFolders) {
+ for (String folder : mExistingFolders) {
if (pathRE.matcher(folder).matches()) {
return true;
}
}
- for (String filePath : mExistinfFiles) {
+ for (String filePath : mExistingFiles.keySet()) {
if (pathRE.matcher(filePath).matches()) {
return true;
}
@@ -247,26 +333,26 @@
/** Invokes {@link File#exists()} on the given {@code file}. */
@Override
- public boolean exists(File file) {
+ public boolean exists(@NonNull File file) {
return isFile(file) || isDirectory(file);
}
/** Invokes {@link File#length()} on the given {@code file}. */
@Override
- public long length(File file) {
+ public long length(@NonNull File file) {
throw new UnsupportedOperationException("MockFileUtils.length is not supported."); //$NON-NLS-1$
}
@Override
- public boolean delete(File file) {
+ public boolean delete(@NonNull File file) {
String path = getAgnosticAbsPath(file);
- if (mExistinfFiles.remove(path)) {
+ if (mExistingFiles.remove(path) != null) {
return true;
}
boolean hasSubfiles = false;
- for (String folder : mExistinfFolders) {
+ for (String folder : mExistingFolders) {
if (folder.startsWith(path) && !folder.equals(path)) {
// the File.delete operation is not recursive and would fail to remove
// a root dir that is not empty.
@@ -274,7 +360,7 @@
}
}
if (!hasSubfiles) {
- for (String filePath : mExistinfFiles) {
+ for (String filePath : mExistingFiles.keySet()) {
if (filePath.startsWith(path) && !filePath.equals(path)) {
// the File.delete operation is not recursive and would fail to remove
// a root dir that is not empty.
@@ -283,15 +369,15 @@
}
}
- return mExistinfFolders.remove(path);
+ return mExistingFolders.remove(path);
}
/** Invokes {@link File#mkdirs()} on the given {@code file}. */
@Override
- public boolean mkdirs(File file) {
+ public boolean mkdirs(@NonNull File file) {
for (; file != null; file = file.getParentFile()) {
String path = getAgnosticAbsPath(file);
- mExistinfFolders.add(path);
+ mExistingFolders.add(path);
}
return true;
}
@@ -299,9 +385,11 @@
/**
* Invokes {@link File#listFiles()} on the given {@code file}.
* The returned list is sorted by alphabetic absolute path string.
+ * Might return an empty array but never null.
*/
+ @NonNull
@Override
- public File[] listFiles(File file) {
+ public File[] listFiles(@NonNull File file) {
TreeSet<File> files = new TreeSet<File>();
String path = getAgnosticAbsPath(file);
@@ -309,12 +397,12 @@
Pattern.quote(path + (path.endsWith("/") ? "" : '/')) + //$NON-NLS-1$ //$NON-NLS-2$
".*"); //$NON-NLS-1$
- for (String folder : mExistinfFolders) {
+ for (String folder : mExistingFolders) {
if (pathRE.matcher(folder).matches()) {
files.add(new File(folder));
}
}
- for (String filePath : mExistinfFiles) {
+ for (String filePath : mExistingFiles.keySet()) {
if (pathRE.matcher(filePath).matches()) {
files.add(new File(filePath));
}
@@ -324,7 +412,7 @@
/** Invokes {@link File#renameTo(File)} on the given files. */
@Override
- public boolean renameTo(File oldFile, File newFile) {
+ public boolean renameTo(@NonNull File oldFile, @NonNull File newFile) {
boolean renamed = false;
String oldPath = getAgnosticAbsPath(oldFile);
@@ -333,31 +421,34 @@
"^(" + Pattern.quote(oldPath) + //$NON-NLS-1$
")($|/.*)"); //$NON-NLS-1$
- Set<String> newStrings = new HashSet<String>();
- for (Iterator<String> it = mExistinfFolders.iterator(); it.hasNext(); ) {
+ Set<String> newFolders = Sets.newTreeSet();
+ for (Iterator<String> it = mExistingFolders.iterator(); it.hasNext(); ) {
String folder = it.next();
Matcher m = pathRE.matcher(folder);
if (m.matches()) {
it.remove();
String newFolder = newPath + m.group(2);
- newStrings.add(newFolder);
+ newFolders.add(newFolder);
renamed = true;
}
}
- mExistinfFolders.addAll(newStrings);
- newStrings.clear();
+ mExistingFolders.addAll(newFolders);
+ newFolders.clear();
- for (Iterator<String> it = mExistinfFiles.iterator(); it.hasNext(); ) {
- String filePath = it.next();
+ Map<String, FileInfo> newFiles = Maps.newTreeMap();
+ for (Iterator<Entry<String, FileInfo>> it = mExistingFiles.entrySet().iterator();
+ it.hasNext(); ) {
+ Entry<String, FileInfo> entry = it.next();
+ String filePath = entry.getKey();
Matcher m = pathRE.matcher(filePath);
if (m.matches()) {
it.remove();
String newFilePath = newPath + m.group(2);
- newStrings.add(newFilePath);
+ newFiles.put(newFilePath, entry.getValue());
renamed = true;
}
}
- mExistinfFiles.addAll(newStrings);
+ mExistingFiles.putAll(newFiles);
return renamed;
}
@@ -367,8 +458,9 @@
* <p/>
* <em>TODO: we might want to overload this to read mock properties instead of a real file.</em>
*/
+ @NonNull
@Override
- public @NonNull Properties loadProperties(@NonNull File file) {
+ public Properties loadProperties(@NonNull File file) {
Properties props = new Properties();
FileInputStream fis = null;
try {
@@ -417,8 +509,9 @@
* Returns an OutputStream that will capture the bytes written and associate
* them with the given file.
*/
+ @NonNull
@Override
- public OutputStream newFileOutputStream(File file) throws FileNotFoundException {
+ public OutputStream newFileOutputStream(@NonNull File file) throws FileNotFoundException {
StringOutputStream os = new StringOutputStream(file);
mOutputStreams.add(os);
return os;
@@ -466,4 +559,48 @@
return sb.toString();
}
}
+
+ @NonNull
+ @Override
+ public InputStream newFileInputStream(@NonNull File file) throws FileNotFoundException {
+ FileInfo fi = mExistingFiles.get(getAgnosticAbsPath(file));
+ if (fi != null) {
+ byte[] content = fi.getContent();
+ if (content != null) {
+ return new ByteArrayInputStream(content);
+ }
+ }
+ throw new FileNotFoundException("Mock file has no content: " + getAgnosticAbsPath(file));
+ }
+
+ @Override
+ public long lastModified(@NonNull File file) {
+ FileInfo fi = mExistingFiles.get(getAgnosticAbsPath(file));
+ if (fi != null) {
+ return fi.getLastModified();
+ }
+ return 0;
+ }
+
+ // -----
+
+ private static class FileInfo {
+ private long mLastModified;
+ private byte[] mContent;
+
+ public FileInfo(long lastModified, @Nullable byte[] content) {
+ mLastModified = lastModified;
+ mContent = content;
+ }
+
+ public long getLastModified() {
+ return mLastModified;
+ }
+
+ @Nullable
+ public byte[] getContent() {
+ return mContent;
+ }
+
+ }
}
diff --git a/sdklib/src/test/java/com/android/sdklib/local/LocalSdkTest.java b/sdklib/src/test/java/com/android/sdklib/local/LocalSdkTest.java
new file mode 100755
index 0000000..b183abb
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/local/LocalSdkTest.java
@@ -0,0 +1,662 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.local;
+
+import com.android.SdkConstants;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.BuildToolInfo;
+import com.android.sdklib.BuildToolInfo.PathId;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.internal.repository.archives.Archive;
+import com.android.sdklib.internal.repository.packages.Package;
+import com.android.sdklib.io.MockFileOp;
+import com.android.sdklib.repository.FullRevision;
+import com.android.sdklib.repository.MajorRevision;
+
+import java.io.File;
+import java.util.Arrays;
+
+import junit.framework.TestCase;
+
+@SuppressWarnings("MethodMayBeStatic")
+public class LocalSdkTest extends TestCase {
+
+ private MockFileOp mFOp;
+ private LocalSdk mLS;
+
+ @Override
+ protected void setUp() {
+ mFOp = new MockFileOp();
+ mLS = new LocalSdk(mFOp);
+ mLS.setLocation(new File("/sdk"));
+ }
+
+ public final void testLocalSdkTest_getLocation() {
+ MockFileOp fop = new MockFileOp();
+ LocalSdk ls = new LocalSdk(fop);
+ assertNull(ls.getLocation());
+ ls.setLocation(new File("/sdk"));
+ assertEquals(new File("/sdk"), ls.getLocation());
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_Tools() {
+ // check empty
+ assertNull(mLS.getPkgInfo(LocalSdk.PKG_TOOLS));
+
+ // setup fake files
+ mLS.clearLocalPkg(LocalSdk.PKG_ALL);
+ mFOp.recordExistingFolder("/sdk/tools");
+ mFOp.recordExistingFile("/sdk/tools/source.properties",
+ "Pkg.License=Terms and Conditions\n" +
+ "Archive.Os=WINDOWS\n" +
+ "Pkg.Revision=22.3.4\n" +
+ "Platform.MinPlatformToolsRev=18\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n" +
+ "Pkg.SourceUrl=https\\://dl-ssl.google.com/android/repository/repository-8.xml");
+ mFOp.recordExistingFile("/sdk/tools/" + SdkConstants.androidCmdName(), "placeholder");
+ mFOp.recordExistingFile("/sdk/tools/" + SdkConstants.FN_EMULATOR, "placeholder");
+
+ LocalPkgInfo pi = mLS.getPkgInfo(LocalSdk.PKG_TOOLS);
+ assertNotNull(pi);
+ assertTrue(pi instanceof LocalToolPkgInfo);
+ assertEquals(new File("/sdk/tools"), pi.getLocalDir());
+ assertSame(mLS, pi.getLocalSdk());
+ assertEquals(null, pi.getLoadError());
+ assertEquals(new FullRevision(22, 3, 4), pi.getFullRevision());
+ assertEquals("<LocalToolPkgInfo FullRev=22.3.4>", pi.toString());
+
+ Package pkg = pi.getPackage();
+ assertNotNull(pkg);
+ assertEquals(new FullRevision(22, 3, 4), pkg.getRevision());
+ assertEquals("Android SDK Tools, revision 22.3.4", pkg.getShortDescription());
+ assertTrue(pkg.isLocal());
+ Archive a = pkg.getArchives()[0];
+ assertTrue(a.isLocal());
+ assertEquals("/sdk/tools", mFOp.getAgnosticAbsPath(a.getLocalOsPath()));
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_PlatformTools() {
+ // check empty
+ assertNull(mLS.getPkgInfo(LocalSdk.PKG_PLATFORM_TOOLS));
+
+ // setup fake files
+ mLS.clearLocalPkg(LocalSdk.PKG_ALL);
+ mFOp.recordExistingFolder("/sdk/platform-tools");
+ mFOp.recordExistingFile("/sdk/platform-tools/source.properties",
+ "Pkg.License=Terms and Conditions\n" +
+ "Archive.Os=WINDOWS\n" +
+ "Pkg.Revision=18.19.20\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n" +
+ "Pkg.SourceUrl=https\\://dl-ssl.google.com/android/repository/repository-8.xml");
+
+ LocalPkgInfo pi = mLS.getPkgInfo(LocalSdk.PKG_PLATFORM_TOOLS);
+ assertNotNull(pi);
+ assertTrue(pi instanceof LocalPlatformToolPkgInfo);
+ assertEquals(new File("/sdk/platform-tools"), pi.getLocalDir());
+ assertSame(mLS, pi.getLocalSdk());
+ assertEquals(null, pi.getLoadError());
+ assertEquals(new FullRevision(18, 19, 20), pi.getFullRevision());
+ assertEquals("<LocalPlatformToolPkgInfo FullRev=18.19.20>", pi.toString());
+
+ Package pkg = pi.getPackage();
+ assertNotNull(pkg);
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_Docs() {
+ // check empty
+ assertNull(mLS.getPkgInfo(LocalSdk.PKG_DOCS));
+
+ // setup fake files
+ mLS.clearLocalPkg(LocalSdk.PKG_ALL);
+ mFOp.recordExistingFolder("/sdk/docs");
+ mFOp.recordExistingFile("/sdk/docs/source.properties",
+ "Pkg.License=Terms and Conditions\n" +
+ "Archive.Os=ANY\n" +
+ "AndroidVersion.ApiLevel=18\n" +
+ "Pkg.Revision=2\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n" +
+ "Pkg.SourceUrl=https\\://dl-ssl.google.com/android/repository/repository-8.xml");
+ mFOp.recordExistingFile("/sdk/docs/index.html", "placeholder");
+
+ LocalPkgInfo pi = mLS.getPkgInfo(LocalSdk.PKG_DOCS);
+ assertNotNull(pi);
+ assertTrue(pi instanceof LocalDocPkgInfo);
+ assertEquals(new File("/sdk/docs"), pi.getLocalDir());
+ assertSame(mLS, pi.getLocalSdk());
+ assertEquals(null, pi.getLoadError());
+ assertEquals(new MajorRevision(2), pi.getMajorRevision());
+ assertEquals("<LocalDocPkgInfo MajorRev=2>", pi.toString());
+
+ Package pkg = pi.getPackage();
+ assertNotNull(pkg);
+ assertEquals(new MajorRevision(2), pkg.getRevision());
+ assertEquals("Documentation for Android SDK, API 18, revision 2", pkg.getShortDescription());
+ assertTrue(pkg.isLocal());
+ Archive a = pkg.getArchives()[0];
+ assertTrue(a.isLocal());
+ assertEquals("/sdk/docs", mFOp.getAgnosticAbsPath(a.getLocalOsPath()));
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_BuildTools() {
+ // check empty
+ assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(LocalSdk.PKG_BUILD_TOOLS)));
+
+ // We haven't defined any mock build-tools so the API will return
+ // a legacy build-tools based on top of platform tools if there's one with
+ // a revision < 17.
+ mFOp.recordExistingFolder("/sdk/platform-tools");
+ mFOp.recordExistingFile("/sdk/platform-tools/source.properties",
+ "Pkg.License=Terms and Conditions\n" +
+ "Archive.Os=WINDOWS\n" +
+ "Pkg.Revision=16\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n" +
+ "Pkg.SourceUrl=https\\://dl-ssl.google.com/android/repository/repository-8.xml");
+
+ // -- get latest build tool in legacy/compatibility mode
+
+ BuildToolInfo bt = mLS.getLatestBuildTool();
+ assertNotNull(bt);
+ assertEquals(new FullRevision(16), bt.getRevision());
+ assertEquals(new File("/sdk/platform-tools"), bt.getLocation());
+ assertEquals("/sdk/platform-tools/" + SdkConstants.FN_AAPT,
+ mFOp.getAgnosticAbsPath(bt.getPath(PathId.AAPT)));
+
+ // clearing local packages also clears the legacy build-tools
+ mLS.clearLocalPkg(LocalSdk.PKG_ALL);
+
+ // setup fake files
+ mFOp.recordExistingFolder("/sdk/build-tools");
+ mFOp.recordExistingFolder("/sdk/build-tools/17");
+ mFOp.recordExistingFolder("/sdk/build-tools/18.1.2");
+ mFOp.recordExistingFolder("/sdk/build-tools/12.2.3");
+ mFOp.recordExistingFile("/sdk/build-tools/17/source.properties",
+ "Pkg.License=Terms and Conditions\n" +
+ "Archive.Os=WINDOWS\n" +
+ "Pkg.Revision=17\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n" +
+ "Pkg.SourceUrl=https\\://dl-ssl.google.com/android/repository/repository-8.xml");
+ mFOp.recordExistingFile("/sdk/build-tools/18.1.2/source.properties",
+ "Pkg.License=Terms and Conditions\n" +
+ "Archive.Os=WINDOWS\n" +
+ "Pkg.Revision=18.1.2\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n" +
+ "Pkg.SourceUrl=https\\://dl-ssl.google.com/android/repository/repository-8.xml");
+ mFOp.recordExistingFile("/sdk/build-tools/12.2.3/source.properties",
+ "Pkg.License=Terms and Conditions\n" +
+ "Archive.Os=WINDOWS\n" +
+ "Pkg.Revision=12.2.3\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n" +
+ "Pkg.SourceUrl=https\\://dl-ssl.google.com/android/repository/repository-8.xml");
+
+ // -- get latest build tool 18.1.2
+
+ BuildToolInfo bt18a = mLS.getLatestBuildTool();
+ assertNotNull(bt18a);
+ assertEquals(new FullRevision(18, 1, 2), bt18a.getRevision());
+ assertEquals(new File("/sdk/build-tools/18.1.2"), bt18a.getLocation());
+ assertEquals("/sdk/build-tools/18.1.2/" + SdkConstants.FN_AAPT,
+ mFOp.getAgnosticAbsPath(bt18a.getPath(PathId.AAPT)));
+
+ // -- get specific build tools by version
+
+ BuildToolInfo bt18b = mLS.getBuildTool(new FullRevision(18, 1, 2));
+ assertSame(bt18a, bt18b);
+
+ BuildToolInfo bt17 = mLS.getBuildTool(new FullRevision(17));
+ assertNotNull(bt17);
+ assertEquals(new FullRevision(17), bt17.getRevision());
+ assertEquals(new File("/sdk/build-tools/17"), bt17.getLocation());
+ assertEquals("/sdk/build-tools/17/" + SdkConstants.FN_AAPT,
+ mFOp.getAgnosticAbsPath(bt17.getPath(PathId.AAPT)));
+
+ assertNull(mLS.getBuildTool(new FullRevision(0)));
+ assertNull(mLS.getBuildTool(new FullRevision(16, 17, 18)));
+
+ LocalPkgInfo pi = mLS.getPkgInfo(LocalSdk.PKG_BUILD_TOOLS, new FullRevision(18, 1, 2));
+ assertNotNull(pi);
+ assertTrue(pi instanceof LocalBuildToolPkgInfo);
+ assertSame(bt18a, ((LocalBuildToolPkgInfo)pi).getBuildToolInfo());
+ assertEquals(new File("/sdk/build-tools/18.1.2"), pi.getLocalDir());
+ assertSame(mLS, pi.getLocalSdk());
+ assertEquals(null, pi.getLoadError());
+ assertEquals(new FullRevision(18, 1, 2), pi.getFullRevision());
+
+ Package pkg = pi.getPackage();
+ assertNotNull(pkg);
+
+ // -- get all build-tools and iterate, sorted by revision.
+
+ assertEquals("[<LocalBuildToolPkgInfo FullRev=12.2.3>, " +
+ "<LocalBuildToolPkgInfo FullRev=17.0.0>, " +
+ "<LocalBuildToolPkgInfo FullRev=18.1.2>]",
+ Arrays.toString(mLS.getPkgsInfos(LocalSdk.PKG_BUILD_TOOLS)));
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_Extra() {
+ // check empty
+ assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(LocalSdk.PKG_EXTRAS)));
+ assertNull(mLS.getPkgInfo(LocalSdk.PKG_EXTRAS, "vendor1/path1"));
+ assertNull(mLS.getExtra("vendor1/path1"));
+
+ // setup fake files
+ mLS.clearLocalPkg(LocalSdk.PKG_ALL);
+ mFOp.recordExistingFolder("/sdk/extras");
+ mFOp.recordExistingFolder("/sdk/extras/vendor1");
+ mFOp.recordExistingFolder("/sdk/extras/vendor1/path1");
+ mFOp.recordExistingFolder("/sdk/extras/vendor1/path2");
+ mFOp.recordExistingFolder("/sdk/extras/vendor2");
+ mFOp.recordExistingFolder("/sdk/extras/vendor2/path1");
+ mFOp.recordExistingFolder("/sdk/extras/vendor2/path2");
+ mFOp.recordExistingFolder("/sdk/extras/vendor3");
+ mFOp.recordExistingFolder("/sdk/extras/vendor3/path3");
+ mFOp.recordExistingFile("/sdk/extras/vendor1/path1/source.properties",
+ "Extra.NameDisplay=Android Support Library\n" +
+ "Extra.VendorDisplay=Vendor\n" +
+ "Extra.VendorId=vendor1\n" +
+ "Extra.Path=path1\n" +
+ "Extra.OldPaths=compatibility\n" +
+ "Archive.Os=WINDOWS\n" +
+ "Pkg.Revision=11\n" +
+ "Archive.Arch=ANY\n");
+ mFOp.recordExistingFile("/sdk/extras/vendor1/path2/source.properties",
+ "Extra.NameDisplay=Some Extra\n" +
+ "Extra.VendorDisplay=Some Vendor\n" +
+ "Extra.VendorId=vendor1\n" +
+ "Extra.Path=path2\n" +
+ "Archive.Os=ANY\n" +
+ "Pkg.Revision=21\n" +
+ "Archive.Arch=ANY\n");
+ mFOp.recordExistingFile("/sdk/extras/vendor2/path1/source.properties",
+ "Extra.NameDisplay=Another Extra\n" +
+ "Extra.VendorDisplay=Another Vendor\n" +
+ "Extra.VendorId=vendor2\n" +
+ "Extra.Path=path1\n" +
+ "Extra.OldPaths=compatibility\n" +
+ "Archive.Os=WINDOWS\n" +
+ "Pkg.Revision=21\n" +
+ "Archive.Arch=ANY\n");
+
+ LocalPkgInfo pi1 = mLS.getPkgInfo(LocalSdk.PKG_EXTRAS, "vendor1/path1");
+ assertNotNull(pi1);
+ assertTrue(pi1 instanceof LocalExtraPkgInfo);
+ assertEquals("vendor1/path1", ((LocalExtraPkgInfo)pi1).getPath());
+ assertEquals("path1", ((LocalExtraPkgInfo)pi1).getExtraPath());
+ assertEquals("vendor1", ((LocalExtraPkgInfo)pi1).getVendorId());
+ assertEquals(new File("/sdk/extras/vendor1/path1"), pi1.getLocalDir());
+ assertSame(mLS, pi1.getLocalSdk());
+ assertEquals(null, pi1.getLoadError());
+ assertEquals(new FullRevision(11), pi1.getFullRevision());
+
+ Package pkg = pi1.getPackage();
+ assertNotNull(pkg);
+
+ LocalExtraPkgInfo pi2 = mLS.getExtra("vendor1/path1");
+ assertSame(pi1, pi2);
+
+ // -- get all extras and iterate, sorted by revision.
+
+ assertEquals("[<LocalExtraPkgInfo Path=vendor1/path1 FullRev=11.0.0>, " +
+ "<LocalExtraPkgInfo Path=vendor1/path2 FullRev=21.0.0>, " +
+ "<LocalExtraPkgInfo Path=vendor2/path1 FullRev=21.0.0>]",
+ Arrays.toString(mLS.getPkgsInfos(LocalSdk.PKG_EXTRAS)));
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_Sources() {
+ // check empty
+ assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(LocalSdk.PKG_SOURCES)));
+ assertNull(mLS.getPkgInfo(LocalSdk.PKG_SOURCES, new AndroidVersion(18, null)));
+
+ // setup fake files
+ mLS.clearLocalPkg(LocalSdk.PKG_ALL);
+ mFOp.recordExistingFolder("/sdk/sources");
+ mFOp.recordExistingFolder("/sdk/sources/android-CUPCAKE");
+ mFOp.recordExistingFolder("/sdk/sources/android-18");
+ mFOp.recordExistingFolder("/sdk/sources/android-42");
+ mFOp.recordExistingFile("/sdk/sources/android-CUPCAKE/source.properties",
+ "Archive.Os=ANY\n" +
+ "AndroidVersion.ApiLevel=3\n" +
+ "AndroidVersion.CodeName=CUPCAKE\n" +
+ "Pkg.Revision=1\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n");
+ mFOp.recordExistingFile("/sdk/sources/android-18/source.properties",
+ "Archive.Os=ANY\n" +
+ "AndroidVersion.ApiLevel=18\n" +
+ "Pkg.Revision=2\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n");
+ mFOp.recordExistingFile("/sdk/sources/android-42/source.properties",
+ "Archive.Os=ANY\n" +
+ "AndroidVersion.ApiLevel=42\n" +
+ "Pkg.Revision=3\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n");
+
+ LocalPkgInfo pi18 = mLS.getPkgInfo(LocalSdk.PKG_SOURCES, new AndroidVersion(18, null));
+ assertNotNull(pi18);
+ assertTrue(pi18 instanceof LocalSourcePkgInfo);
+ assertSame(mLS, pi18.getLocalSdk());
+ assertEquals(null, pi18.getLoadError());
+ assertEquals(new AndroidVersion(18, null), pi18.getAndroidVersion());
+ assertEquals(new MajorRevision(2), pi18.getMajorRevision());
+
+ Package pkg = pi18.getPackage();
+ assertNotNull(pkg);
+
+ LocalPkgInfo pi1 = mLS.getPkgInfo(LocalSdk.PKG_SOURCES, new AndroidVersion(3, "CUPCAKE"));
+ assertNotNull(pi1);
+ assertEquals(new AndroidVersion(3, "CUPCAKE"), pi1.getAndroidVersion());
+ assertEquals(new MajorRevision(1), pi1.getMajorRevision());
+
+ // -- get all extras and iterate, sorted by revision.
+
+ assertEquals("[<LocalSourcePkgInfo Android=API 3, CUPCAKE preview MajorRev=1>, " +
+ "<LocalSourcePkgInfo Android=API 18 MajorRev=2>, " +
+ "<LocalSourcePkgInfo Android=API 42 MajorRev=3>]",
+ Arrays.toString(mLS.getPkgsInfos(LocalSdk.PKG_SOURCES)));
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_Samples() {
+ // check empty
+ assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(LocalSdk.PKG_SAMPLES)));
+ assertNull(mLS.getPkgInfo(LocalSdk.PKG_SAMPLES, new AndroidVersion(18, null)));
+
+ // setup fake files
+ mLS.clearLocalPkg(LocalSdk.PKG_ALL);
+ mFOp.recordExistingFolder("/sdk/samples");
+ mFOp.recordExistingFolder("/sdk/samples/android-18");
+ mFOp.recordExistingFolder("/sdk/samples/android-42");
+ mFOp.recordExistingFile("/sdk/samples/android-18/source.properties",
+ "Archive.Os=ANY\n" +
+ "AndroidVersion.ApiLevel=18\n" +
+ "Pkg.Revision=2\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n");
+ mFOp.recordExistingFile("/sdk/samples/android-42/source.properties",
+ "Archive.Os=ANY\n" +
+ "AndroidVersion.ApiLevel=42\n" +
+ "Pkg.Revision=3\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n");
+
+ LocalPkgInfo pi18 = mLS.getPkgInfo(LocalSdk.PKG_SAMPLES, new AndroidVersion(18, null));
+ assertNotNull(pi18);
+ assertTrue(pi18 instanceof LocalSamplePkgInfo);
+ assertSame(mLS, pi18.getLocalSdk());
+ assertEquals(null, pi18.getLoadError());
+ assertEquals(new AndroidVersion(18, null), pi18.getAndroidVersion());
+ assertEquals(new MajorRevision(2), pi18.getMajorRevision());
+
+ Package pkg = pi18.getPackage();
+ assertNotNull(pkg);
+
+ // -- get all extras and iterate, sorted by revision.
+
+ assertEquals("[<LocalSamplePkgInfo Android=API 18 MajorRev=2>, " +
+ "<LocalSamplePkgInfo Android=API 42 MajorRev=3>]",
+ Arrays.toString(mLS.getPkgsInfos(LocalSdk.PKG_SAMPLES)));
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_SysImages() {
+ // check empty
+ assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(LocalSdk.PKG_SYS_IMAGES)));
+
+ // setup fake files
+ mLS.clearLocalPkg(LocalSdk.PKG_ALL);
+ mFOp.recordExistingFolder("/sdk/system-images");
+ mFOp.recordExistingFolder("/sdk/system-images/android-18");
+ mFOp.recordExistingFolder("/sdk/system-images/android-18/armeabi-v7a");
+ mFOp.recordExistingFolder("/sdk/system-images/android-18/x86");
+ mFOp.recordExistingFolder("/sdk/system-images/android-42");
+ mFOp.recordExistingFolder("/sdk/system-images/android-42/x86");
+ mFOp.recordExistingFolder("/sdk/system-images/android-42/mips");
+ mFOp.recordExistingFile("/sdk/system-images/android-18/armeabi-v7a/source.properties",
+ "Pkg.Revision=1\n" +
+ "SystemImage.Abi=armeabi-v7a\n" +
+ "AndroidVersion.ApiLevel=18\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Os=ANY\n" +
+ "Archive.Arch=ANY\n");
+ mFOp.recordExistingFile("/sdk/system-images/android-18/x86/source.properties",
+ "Pkg.Revision=2\n" +
+ "SystemImage.Abi=x86\n" +
+ "AndroidVersion.ApiLevel=18\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Os=ANY\n" +
+ "Archive.Arch=ANY\n");
+ mFOp.recordExistingFile("/sdk/system-images/android-42/x86/source.properties",
+ "Pkg.Revision=3\n" +
+ "SystemImage.Abi=x86\n" +
+ "AndroidVersion.ApiLevel=42\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Os=ANY\n" +
+ "Archive.Arch=ANY\n");
+ mFOp.recordExistingFile("/sdk/system-images/android-42/mips/source.properties",
+ "Pkg.Revision=4\n" +
+ "SystemImage.Abi=mips\n" +
+ "AndroidVersion.ApiLevel=42\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Os=ANY\n" +
+ "Archive.Arch=ANY\n");
+
+ assertEquals("[<LocalSysImgPkgInfo Android=API 18 Path=armeabi-v7a MajorRev=1>, " +
+ "<LocalSysImgPkgInfo Android=API 18 Path=x86 MajorRev=2>, " +
+ "<LocalSysImgPkgInfo Android=API 42 Path=mips MajorRev=4>, " +
+ "<LocalSysImgPkgInfo Android=API 42 Path=x86 MajorRev=3>]",
+ Arrays.toString(mLS.getPkgsInfos(LocalSdk.PKG_SYS_IMAGES)));
+
+ LocalPkgInfo pi = mLS.getPkgsInfos(LocalSdk.PKG_SYS_IMAGES)[0];
+ assertNotNull(pi);
+ assertTrue(pi instanceof LocalSysImgPkgInfo);
+ assertSame(mLS, pi.getLocalSdk());
+ assertEquals(null, pi.getLoadError());
+ assertEquals(new MajorRevision(1), pi.getMajorRevision());
+ assertEquals("armeabi-v7a", pi.getPath());
+
+ Package pkg = pi.getPackage();
+ assertNull(pkg);
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_Platforms() {
+ // check empty
+ assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(LocalSdk.PKG_PLATFORMS)));
+
+ // setup fake files
+ mLS.clearLocalPkg(LocalSdk.PKG_ALL);
+ recordPlatform18(mFOp);
+
+ assertEquals("[<LocalPlatformPkgInfo Android=API 18 Path=android-18 MajorRev=1>]",
+ Arrays.toString(mLS.getPkgsInfos(LocalSdk.PKG_PLATFORMS)));
+
+ LocalPkgInfo pi = mLS.getPkgInfo(LocalSdk.PKG_PLATFORMS, new AndroidVersion(18, null));
+ assertNotNull(pi);
+ assertTrue(pi instanceof LocalPlatformPkgInfo);
+ assertSame(mLS, pi.getLocalSdk());
+ assertEquals(null, pi.getLoadError());
+ assertEquals(new AndroidVersion(18, null), pi.getAndroidVersion());
+ assertEquals(new MajorRevision(1), pi.getMajorRevision());
+
+ Package pkg = pi.getPackage();
+ assertNotNull(pkg);
+
+ IAndroidTarget t1 = ((LocalPlatformPkgInfo)pi).getAndroidTarget();
+ assertNotNull(t1);
+
+ LocalPkgInfo pi2 = mLS.getPkgInfo(LocalSdk.PKG_PLATFORMS, "android-18");
+ assertSame(pi, pi2);
+
+ IAndroidTarget t2 = mLS.getTargetFromHashString("android-18");
+ assertSame(t1, t2);
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_Platforms_Sources() {
+ // setup fake files
+ mLS.clearLocalPkg(LocalSdk.PKG_ALL);
+ recordPlatform18(mFOp);
+ assertEquals("[<LocalPlatformPkgInfo Android=API 18 Path=android-18 MajorRev=1>]",
+ Arrays.toString(mLS.getPkgsInfos(LocalSdk.PKG_PLATFORMS | LocalSdk.PKG_SOURCES)));
+
+ // By default, IAndroidTarget returns the legacy path to a platform source,
+ // whether that directory exist or not.
+ LocalPkgInfo pi1 = mLS.getPkgInfo(LocalSdk.PKG_PLATFORMS, new AndroidVersion(18, null));
+ IAndroidTarget t1 = ((LocalPlatformPkgInfo)pi1).getAndroidTarget();
+ assertEquals("/sdk/platforms/android-18/sources",
+ mFOp.getAgnosticAbsPath(t1.getPath(IAndroidTarget.SOURCES)));
+
+ // However if a separate sources package folder is installed, it is returned instead.
+ mLS.clearLocalPkg(LocalSdk.PKG_ALL);
+ mFOp.recordExistingFolder("/sdk/sources");
+ mFOp.recordExistingFolder("/sdk/sources/android-18");
+ mFOp.recordExistingFile("/sdk/sources/android-18/source.properties",
+ "Archive.Os=ANY\n" +
+ "AndroidVersion.ApiLevel=18\n" +
+ "Pkg.Revision=2\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Arch=ANY\n");
+
+ LocalPkgInfo pi2 = mLS.getPkgInfo(LocalSdk.PKG_PLATFORMS, new AndroidVersion(18, null));
+ IAndroidTarget t2 = ((LocalPlatformPkgInfo)pi2).getAndroidTarget();
+ assertEquals("[<LocalPlatformPkgInfo Android=API 18 Path=android-18 MajorRev=1>, " +
+ "<LocalSourcePkgInfo Android=API 18 MajorRev=2>]",
+ Arrays.toString(mLS.getPkgsInfos(LocalSdk.PKG_PLATFORMS | LocalSdk.PKG_SOURCES)));
+
+ assertEquals("/sdk/sources/android-18",
+ mFOp.getAgnosticAbsPath(t2.getPath(IAndroidTarget.SOURCES)));
+ }
+
+ public final void testLocalSdkTest_getPkgInfo_Addons() {
+ // check empty
+ assertEquals("[]", Arrays.toString(mLS.getPkgsInfos(LocalSdk.PKG_ADDONS)));
+
+ // setup fake files
+ mLS.clearLocalPkg(LocalSdk.PKG_ALL);
+ recordPlatform18(mFOp);
+ mFOp.recordExistingFolder("/sdk/add-ons");
+ mFOp.recordExistingFolder("/sdk/add-ons/addon-vendor_name-2");
+ mFOp.recordExistingFile("/sdk/add-ons/addon-vendor_name-2/source.properties",
+ "Pkg.Revision=2\n" +
+ "Addon.VendorId=vendor\n" +
+ "Addon.VendorDisplay=Some Vendor\n" +
+ "Addon.NameId=name\n" +
+ "Addon.NameDisplay=Some Name\n" +
+ "AndroidVersion.ApiLevel=18\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Os=ANY\n" +
+ "Archive.Arch=ANY\n");
+ mFOp.recordExistingFile("/sdk/add-ons/addon-vendor_name-2/manifest.ini",
+ "revision=2\n" +
+ "name=Some Name\n" +
+ "name-id=name\n" +
+ "vendor=Some Vendor\n" +
+ "vendor-id=vendor\n" +
+ "api=18\n" +
+ "libraries=com.foo.lib1;com.blah.lib2\n" +
+ "com.foo.lib1=foo.jar;API for Foo\n" +
+ "com.blah.lib2=blah.jar;API for Blah\n");
+
+ assertEquals("[<LocalAddonPkgInfo Android=API 18 Path=Some Vendor:Some Name:18 MajorRev=2>]",
+ Arrays.toString(mLS.getPkgsInfos(LocalSdk.PKG_ADDONS)));
+ assertEquals("[<LocalPlatformPkgInfo Android=API 18 Path=android-18 MajorRev=1>, " +
+ "<LocalAddonPkgInfo Android=API 18 Path=Some Vendor:Some Name:18 MajorRev=2>]",
+ Arrays.toString(mLS.getPkgsInfos(LocalSdk.PKG_ALL)));
+
+ LocalPkgInfo pi = mLS.getPkgInfo(LocalSdk.PKG_ADDONS, "Some Vendor:Some Name:18");
+ assertNotNull(pi);
+ assertTrue(pi instanceof LocalAddonPkgInfo);
+ assertSame(mLS, pi.getLocalSdk());
+ assertEquals(null, pi.getLoadError());
+ assertEquals(new AndroidVersion(18, null), pi.getAndroidVersion());
+ assertEquals(new MajorRevision(2), pi.getMajorRevision());
+ assertEquals("Some Vendor:Some Name:18", pi.getPath());
+
+ Package pkg = pi.getPackage();
+ assertNotNull(pkg);
+
+ IAndroidTarget t = mLS.getTargetFromHashString("Some Vendor:Some Name:18");
+ assertSame(t, ((LocalAddonPkgInfo) pi).getAndroidTarget());
+ assertNotNull(t);
+
+ }
+
+ //-----
+
+ private void recordPlatform18(MockFileOp fop) {
+ fop.recordExistingFolder("/sdk/platforms");
+ fop.recordExistingFolder("/sdk/platforms/android-18");
+ fop.recordExistingFile("/sdk/platforms/android-18/android.jar");
+ fop.recordExistingFile("/sdk/platforms/android-18/framework.aidl");
+ fop.recordExistingFile("/sdk/platforms/android-18/source.properties",
+ "Pkg.Revision=1\n" +
+ "Platform.Version=4.3\n" +
+ "AndroidVersion.ApiLevel=18\n" +
+ "Layoutlib.Api=10\n" +
+ "Layoutlib.Revision=1\n" +
+ "Platform.MinToolsRev=21\n" +
+ "Pkg.LicenseRef=android-sdk-license\n" +
+ "Archive.Os=ANY\n" +
+ "Archive.Arch=ANY\n");
+ fop.recordExistingFile("/sdk/platforms/android-18/sdk.properties",
+ "sdk.ant.templates.revision=1\n" +
+ "sdk.skin.default=WVGA800\n");
+ fop.recordExistingFile("/sdk/platforms/android-18/build.prop",
+ "ro.build.id=JB_MR2\n" +
+ "ro.build.display.id=sdk-eng 4.3 JB_MR2 819563 test-keys\n" +
+ "ro.build.version.incremental=819563\n" +
+ "ro.build.version.sdk=18\n" +
+ "ro.build.version.codename=REL\n" +
+ "ro.build.version.release=4.3\n" +
+ "ro.build.date=Tue Sep 10 18:43:31 UTC 2013\n" +
+ "ro.build.date.utc=1378838611\n" +
+ "ro.build.type=eng\n" +
+ "ro.build.tags=test-keys\n" +
+ "ro.product.model=sdk\n" +
+ "ro.product.name=sdk\n" +
+ "ro.product.board=\n" +
+ "ro.product.cpu.abi=armeabi-v7a\n" +
+ "ro.product.cpu.abi2=armeabi\n" +
+ "ro.product.locale.language=en\n" +
+ "ro.product.locale.region=US\n" +
+ "ro.wifi.channels=\n" +
+ "ro.board.platform=\n" +
+ "# ro.build.product is obsolete; use ro.product.device\n" +
+ "# Do not try to parse ro.build.description or .fingerprint\n" +
+ "ro.build.description=sdk-eng 4.3 JB_MR2 819563 test-keys\n" +
+ "ro.build.fingerprint=generic/sdk/generic:4.3/JB_MR2/819563:eng/test-keys\n" +
+ "ro.build.characteristics=default\n" +
+ "rild.libpath=/system/lib/libreference-ril.so\n" +
+ "rild.libargs=-d /dev/ttyS0\n" +
+ "ro.config.notification_sound=OnTheHunt.ogg\n" +
+ "ro.config.alarm_alert=Alarm_Classic.ogg\n" +
+ "ro.kernel.android.checkjni=1\n" +
+ "xmpp.auto-presence=true\n" +
+ "ro.config.nocheckin=yes\n" +
+ "net.bt.name=Android\n" +
+ "dalvik.vm.stack-trace-file=/data/anr/traces.txt\n" +
+ "ro.build.user=generic\n" +
+ "ro.build.host=generic\n" +
+ "ro.product.brand=generic\n" +
+ "ro.product.manufacturer=generic\n" +
+ "ro.product.device=generic\n" +
+ "ro.build.product=generic\n");
+ }}
diff --git a/sdklib/src/test/java/com/android/sdklib/repository/ValidateAddonXmlTest.java b/sdklib/src/test/java/com/android/sdklib/repository/ValidateAddonXmlTest.java
index ea56e0b..88fec01 100755
--- a/sdklib/src/test/java/com/android/sdklib/repository/ValidateAddonXmlTest.java
+++ b/sdklib/src/test/java/com/android/sdklib/repository/ValidateAddonXmlTest.java
@@ -144,6 +144,30 @@
handler.verify();
}
+ /** Validate a valid sample using namespace version 5 using an InputStream */
+ public void testValidateLocalAddonFile5() throws Exception {
+ InputStream xmlStream = this.getClass().getResourceAsStream(
+ "/com/android/sdklib/testdata/addon_sample_5.xml");
+ Source source = new StreamSource(xmlStream);
+
+ CaptureErrorHandler handler = new CaptureErrorHandler();
+ Validator validator = getAddonValidator(5, handler);
+ validator.validate(source);
+ handler.verify();
+ }
+
+ /** Validate a valid sample using namespace version 6 using an InputStream */
+ public void testValidateLocalAddonFile6() throws Exception {
+ InputStream xmlStream = this.getClass().getResourceAsStream(
+ "/com/android/sdklib/testdata/addon_sample_6.xml");
+ Source source = new StreamSource(xmlStream);
+
+ CaptureErrorHandler handler = new CaptureErrorHandler();
+ Validator validator = getAddonValidator(6, handler);
+ validator.validate(source);
+ handler.verify();
+ }
+
// IMPORTANT: each time you add a test here, you should add a corresponding
// test in SdkAddonSourceTest to validate the XML content is parsed correctly.
@@ -189,7 +213,7 @@
public void testExtraPathWithSlash() throws Exception {
String document = "<?xml version=\"1.0\"?>" +
OPEN_TAG_ADDON +
- "<r:extra> <r:revision>1</r:revision> <r:path>path/cannot\\contain\\segments</r:path> " +
+ "<r:extra> <r:revision><r:major>1</r:major></r:revision> <r:path>path/cannot\\contain\\segments</r:path> " +
"<r:archives> <r:archive os=\"any\"> <r:size>1</r:size> <r:checksum>2822ae37115ebf13412bbef91339ee0d9454525e</r:checksum> " +
"<r:url>url</r:url> </r:archive> </r:archives> </r:extra>" +
CLOSE_TAG_ADDON;
diff --git a/sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_6.xml b/sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_6.xml
new file mode 100755
index 0000000..821f7ed
--- /dev/null
+++ b/sdklib/src/test/java/com/android/sdklib/testdata/addon_sample_6.xml
@@ -0,0 +1,223 @@
+<?xml version="1.0"?>
+<!--
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+<sdk:sdk-addon
+ xmlns:sdk="http://schemas.android.com/sdk/android/addon/6">
+
+ <!-- Define a couple of licenses. These will be referenced by uses-license later. -->
+
+ <sdk:license type="text" id="license1">
+ This is the license
+ for this platform.
+ </sdk:license>
+
+ <sdk:license id="license2">
+ Licenses are only of type 'text' right now, so this is implied.
+ </sdk:license>
+
+ <!-- Inner elements must be either platform, add-on, doc or tool.
+ There can be 0 or more of each, in any order. -->
+
+ <sdk:add-on>
+ <sdk:name-id>My_First_add-on</sdk:name-id>
+ <sdk:name-display>My First add-on</sdk:name-display>
+
+ <sdk:vendor-id>John_Doe</sdk:vendor-id>
+ <sdk:vendor-display>John Doe</sdk:vendor-display>
+
+ <sdk:api-level>1</sdk:api-level>
+ <sdk:revision>1</sdk:revision>
+ <sdk:uses-license ref="license2" />
+ <sdk:description>Some optional description</sdk:description>
+ <sdk:desc-url>http://www.example.com/myfirstaddon</sdk:desc-url>
+ <sdk:archives>
+ <sdk:archive os="any">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>http://www.example.com/add-ons/first.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ <!-- The libs node is mandatory, however it can be empty. -->
+ <sdk:libs>
+ <sdk:lib>
+ <sdk:name>android.blah.somelib</sdk:name>
+ <sdk:description>The description for this library.</sdk:description>
+ </sdk:lib>
+ <sdk:lib>
+ <!-- sdk:description is optional, name is not -->
+ <sdk:name>com.android.mymaps</sdk:name>
+ </sdk:lib>
+ </sdk:libs>
+ <sdk:layoutlib>
+ <sdk:api>5</sdk:api>
+ <sdk:revision>0</sdk:revision>
+ </sdk:layoutlib>
+ </sdk:add-on>
+
+ <sdk:add-on>
+ <sdk:name-id>My_Second_add-on</sdk:name-id>
+ <sdk:name-display>My Second add-on</sdk:name-display>
+
+ <sdk:vendor-id>John_Deer</sdk:vendor-id>
+ <sdk:vendor-display>John Deer</sdk:vendor-display>
+
+ <sdk:api-level>2</sdk:api-level>
+ <sdk:revision>42</sdk:revision>
+ <sdk:archives>
+ <sdk:archive os="windows">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/second-42-win.zip</sdk:url>
+ </sdk:archive>
+ <sdk:archive os="linux">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/second-42-linux.tar.bz2</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ <sdk:libs>
+ <sdk:lib>
+ <sdk:name>android.blah.somelib</sdk:name>
+ <sdk:description>The description for this library.</sdk:description>
+ </sdk:lib>
+ <sdk:lib>
+ <sdk:name>com.android.mymaps</sdk:name>
+ </sdk:lib>
+ </sdk:libs>
+ <sdk:uses-license ref="license2" />
+ <!-- No layoutlib element in this package. It's optional. -->
+ </sdk:add-on>
+
+ <sdk:add-on>
+ <sdk:name-id>no_libs</sdk:name-id>
+ <sdk:name-display>This add-on has no libraries</sdk:name-display>
+
+ <sdk:vendor-id>Joe_Bar</sdk:vendor-id>
+ <sdk:vendor-display>Joe Bar</sdk:vendor-display>
+
+ <sdk:uses-license ref="license2" />
+ <sdk:api-level>4</sdk:api-level>
+ <sdk:revision>3</sdk:revision>
+ <sdk:archives>
+ <sdk:archive os="any" arch="any">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/imnotanarchiveimadoctorjim.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ <!-- The libs node is mandatory, however it can be empty. -->
+ <sdk:libs />
+ <sdk:layoutlib>
+ <sdk:api>3</sdk:api>
+ <sdk:revision>42</sdk:revision>
+ </sdk:layoutlib>
+ </sdk:add-on>
+
+ <sdk:extra>
+ <sdk:name-display>Random name, not an id!</sdk:name-display>
+
+ <sdk:vendor-id>cyclop</sdk:vendor-id>
+ <sdk:vendor-display>The big bus</sdk:vendor-display>
+
+ <sdk:path>usb_driver</sdk:path>
+ <sdk:uses-license ref="license2" />
+ <sdk:revision>
+ <sdk:major>43</sdk:major>
+ <sdk:minor>42</sdk:minor>
+ <sdk:micro>41</sdk:micro>
+ </sdk:revision>
+ <sdk:archives>
+ <sdk:archive os="any" arch="any">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/extraduff.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ <sdk:description>An Extra package for the USB driver, it will install in $SDK/usb_driver</sdk:description>
+ <sdk:desc-url>http://www.example.com/extra.html</sdk:desc-url>
+ <sdk:min-tools-rev>
+ <sdk:major>3</sdk:major>
+ <sdk:minor>2</sdk:minor>
+ <sdk:micro>1</sdk:micro>
+ <sdk:preview>42</sdk:preview>
+ </sdk:min-tools-rev>
+ <sdk:obsolete/>
+ </sdk:extra>
+
+ <sdk:extra>
+ <sdk:name-display>Yet another extra, by Android</sdk:name-display>
+
+ <sdk:vendor-id>android_vendor</sdk:vendor-id>
+ <sdk:vendor-display>Android Vendor</sdk:vendor-display>
+
+ <sdk:path>extra_api_dep</sdk:path>
+ <sdk:uses-license ref="license2" />
+ <sdk:revision>
+ <sdk:major>2</sdk:major>
+ <sdk:micro>1</sdk:micro>
+ </sdk:revision>
+ <sdk:archives>
+ <sdk:archive os="any" arch="any">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/extra_mega_duff.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ <sdk:description>Some extra package that has a min-api-level of 42</sdk:description>
+ <sdk:desc-url>http://www.example.com/extra.html</sdk:desc-url>
+ <sdk:min-tools-rev>
+ <sdk:major>3</sdk:major>
+ </sdk:min-tools-rev>
+ <sdk:min-api-level>42</sdk:min-api-level>
+ <sdk:project-files>
+ <sdk:path>v8/veggies_8.jar</sdk:path>
+ <sdk:path>root.jar</sdk:path>
+ <sdk:path>dir1/dir 2 with space/mylib.jar</sdk:path>
+ </sdk:project-files>
+ <sdk:old-paths>path1;old_path2;oldPath3</sdk:old-paths>
+ </sdk:extra>
+
+ <sdk:extra>
+ <sdk:name-display>. -..- - .-. .-</sdk:name-display>
+
+ <sdk:vendor-id>____</sdk:vendor-id>
+ <sdk:vendor-display>____</sdk:vendor-display>
+
+ <sdk:path>____</sdk:path>
+ <sdk:uses-license ref="license2" />
+ <sdk:revision>
+ <sdk:major>2</sdk:major>
+ </sdk:revision>
+ <sdk:archives>
+ <sdk:archive os="any" arch="any">
+ <sdk:size>65536</sdk:size>
+ <sdk:checksum type="sha1">2822ae37115ebf13412bbef91339ee0d9454525e</sdk:checksum>
+ <sdk:url>distrib/extra_mega_duff.zip</sdk:url>
+ </sdk:archive>
+ </sdk:archives>
+ <sdk:description>Some extra package that has a min-api-level of 42</sdk:description>
+ <sdk:desc-url>http://www.example.com/extra.html</sdk:desc-url>
+ <sdk:min-tools-rev>
+ <sdk:major>3</sdk:major>
+ <!-- no minor, assumed to be 0 -->
+ <sdk:micro>1</sdk:micro>
+ </sdk:min-tools-rev>
+ <sdk:min-api-level>42</sdk:min-api-level>
+ <sdk:obsolete></sdk:obsolete>
+ <!-- No project-files element in this package. -->
+ </sdk:extra>
+
+</sdk:sdk-addon>
diff --git a/sdklib/test.gradle b/sdklib/test.gradle
index 6870aa3..de568fa 100755
--- a/sdklib/test.gradle
+++ b/sdklib/test.gradle
@@ -1,3 +1,9 @@
+apply plugin: 'java'
+apply plugin: 'distrib'
+apply plugin: 'maven'
+
+evaluationDependsOn(':dvlib')
+
group = 'com.android.tools'
archivesBaseName = 'sdklib-test'
@@ -17,3 +23,14 @@
}
shipping.isShipping = false
+
+apply from: '../baseVersion.gradle'
+
+task publishLocal(type: Upload) {
+ configuration = configurations.archives
+ repositories {
+ mavenDeployer {
+ repository(url: uri("$rootProject.ext.androidHostOut/repo"))
+ }
+ }
+}
diff --git a/settings.gradle b/settings.gradle
index 2e09641..e75c7ad 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -20,14 +20,27 @@
include 'sdklib-test'
include 'testutils'
-project(':ant-tasks' ).projectDir = new File(rootDir, 'legacy/ant-tasks')
-project(':archquery' ).projectDir = new File(rootDir, 'legacy/archquery')
-project(':dvlib' ).projectDir = new File(rootDir, 'device_validator/dvlib')
-project(':fat32lib' ).projectDir = new File(rootDir, '../external/fat32lib')
-project(':lint' ).projectDir = new File(rootDir, 'lint/cli')
-project(':lint-api' ).projectDir = new File(rootDir, 'lint/libs/lint-api')
-project(':lint-checks' ).projectDir = new File(rootDir, 'lint/libs/lint-checks')
-project(':screenshot2' ).projectDir = new File(rootDir, 'misc/screenshot2')
-project(':sdklib-test' ).projectDir = new File(rootDir, 'sdklib')
-project(':sdklib-test' ).buildFileName = 'test.gradle'
+include 'builder-model'
+include 'builder-test-api'
+include 'builder'
+include 'gradle-model'
+include 'gradle'
+
+project(':ant-tasks' ).projectDir = new File(rootDir, 'legacy/ant-tasks')
+project(':archquery' ).projectDir = new File(rootDir, 'legacy/archquery')
+project(':dvlib' ).projectDir = new File(rootDir, 'device_validator/dvlib')
+project(':fat32lib' ).projectDir = new File(rootDir, '../external/fat32lib')
+project(':lint' ).projectDir = new File(rootDir, 'lint/cli')
+project(':lint-api' ).projectDir = new File(rootDir, 'lint/libs/lint-api')
+project(':lint-checks' ).projectDir = new File(rootDir, 'lint/libs/lint-checks')
+project(':screenshot2' ).projectDir = new File(rootDir, 'misc/screenshot2')
+project(':sdklib-test' ).projectDir = new File(rootDir, 'sdklib')
+project(':sdklib-test' ).buildFileName = 'test.gradle'
+
+project(':builder-model' ).projectDir = new File(rootDir, 'build-system/builder-model')
+project(':builder-test-api').projectDir = new File(rootDir, 'build-system/builder-test-api')
+project(':builder' ).projectDir = new File(rootDir, 'build-system/builder')
+project(':gradle-model' ).projectDir = new File(rootDir, 'build-system/gradle-model')
+project(':gradle' ).projectDir = new File(rootDir, 'build-system/gradle')
+project(':manifest-merger' ).projectDir = new File(rootDir, 'build-system/manifest-merger')
diff --git a/templates/activities/BlankActivity/globals.xml.ftl b/templates/activities/BlankActivity/globals.xml.ftl
index 11aabd7..0dca7d3 100644
--- a/templates/activities/BlankActivity/globals.xml.ftl
+++ b/templates/activities/BlankActivity/globals.xml.ftl
@@ -1,8 +1,13 @@
<?xml version="1.0"?>
<globals>
<global id="projectOut" value="." />
- <global id="manifestOut" value="." />
- <global id="srcOut" value="src/${slashedPackageName(packageName)}" />
- <global id="resOut" value="res" />
+ <global id="manifestOut" value="${manifestDir}" />
+ <global id="appCompat" value="${(minApiLevel lt 14)?string('1','')}" />
+ <!-- e.g. getSupportActionBar vs. getActionBar -->
+ <global id="Support" value="${(minApiLevel lt 14)?string('Support','')}" />
+ <global id="hasViewPager" value="${(navType == 'pager' || navType == 'tabs')?string('1','')}" />
+ <global id="hasSections" value="${(navType == 'pager' || navType == 'tabs' || navType == 'spinner' || navType == 'drawer')?string('1','')}" />
+ <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
+ <global id="resOut" value="${resDir}" />
<global id="menuName" value="${classToResource(activityClass)}" />
</globals>
diff --git a/templates/activities/BlankActivity/recipe.xml.ftl b/templates/activities/BlankActivity/recipe.xml.ftl
index 1889cdc..e44e643 100644
--- a/templates/activities/BlankActivity/recipe.xml.ftl
+++ b/templates/activities/BlankActivity/recipe.xml.ftl
@@ -1,5 +1,10 @@
<?xml version="1.0"?>
<recipe>
+
+ <#if appCompat?has_content><dependency mavenUrl="com.android.support:appcompat-v7:+"/></#if>
+ <#if !appCompat?has_content && hasViewPager?has_content><dependency mavenUrl="com.android.support:support-v13:+"/></#if>
+ <#if !appCompat?has_content && navType == 'drawer'><dependency mavenUrl="com.android.support:support-v4:+"/></#if>
+
<merge from="AndroidManifest.xml.ftl"
to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
@@ -9,49 +14,69 @@
<merge from="res/values/strings.xml.ftl"
to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
- <merge from="res/values/dimens.xml"
+ <merge from="res/values/dimens.xml.ftl"
to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
- <merge from="res/values-sw600dp/dimens.xml"
- to="${escapeXmlAttribute(resOut)}/values-sw600dp/dimens.xml" />
- <merge from="res/values-sw720dp-land/dimens.xml"
- to="${escapeXmlAttribute(resOut)}/values-sw720dp-land/dimens.xml" />
+ <merge from="res/values-w820dp/dimens.xml"
+ to="${escapeXmlAttribute(resOut)}/values-w820dp/dimens.xml" />
- <!-- Decide what kind of layout to add (viewpager or not) -->
- <#if navType?contains("pager")>
+ <!-- TODO: switch on Holo Dark v. Holo Light -->
+ <#if navType == 'drawer'>
+ <copy from="res/drawable-hdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-hdpi" />
+ <copy from="res/drawable-mdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-mdpi" />
+ <copy from="res/drawable-xhdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-xhdpi" />
+ <copy from="res/drawable-xxhdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-xxhdpi" />
+
+ <instantiate from="res/menu/global.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/menu/global.xml" />
+
+ </#if>
+
+ <!-- Decide what kind of layout(s) to add -->
+ <#if hasViewPager?has_content>
<instantiate from="res/layout/activity_pager.xml.ftl"
to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
- <instantiate from="res/layout/fragment_dummy.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/fragment_${classToResource(activityClass)}_dummy.xml" />
- <#elseif navType == "tabs" || navType == "dropdown">
- <instantiate from="res/layout/activity_fragment_container.xml"
+ <#elseif navType == 'drawer'>
+ <instantiate from="res/layout/activity_drawer.xml.ftl"
to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
- <instantiate from="res/layout/fragment_dummy.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/layout/fragment_${classToResource(activityClass)}_dummy.xml" />
+ <instantiate from="res/layout/fragment_navigation_drawer.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/fragment_navigation_drawer.xml" />
<#else>
- <instantiate from="res/layout/activity_simple.xml.ftl"
+ <instantiate from="res/layout/activity_fragment_container.xml.ftl"
to="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+
</#if>
+ <!-- Always add the simple/placeholder fragment -->
+ <instantiate from="res/layout/fragment_simple.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
+
<!-- Decide which activity code to add -->
<#if navType == "none">
<instantiate from="src/app_package/SimpleActivity.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
- <#elseif navType == "tabs_pager" || navType == "pager_strip">
+ <#elseif navType == "tabs" || navType == "pager">
<instantiate from="src/app_package/TabsAndPagerActivity.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
- <#elseif navType == "tabs">
- <instantiate from="src/app_package/TabsActivity.java.ftl"
+ <#elseif navType == "drawer">
+ <instantiate from="src/app_package/DrawerActivity.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+ <instantiate from="src/app_package/NavigationDrawerFragment.java.ftl"
+ to="${escapeXmlAttribute(srcOut)}/NavigationDrawerFragment.java" />
- <#elseif navType == "dropdown">
+ <#elseif navType == "spinner">
<instantiate from="src/app_package/DropdownActivity.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
</#if>
- <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" />
+ <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.java" />
+ <open file="${escapeXmlAttribute(resOut)}/layout/${fragmentLayoutName}.xml" />
</recipe>
diff --git a/templates/activities/BlankActivity/root/AndroidManifest.xml.ftl b/templates/activities/BlankActivity/root/AndroidManifest.xml.ftl
index b8ae72e..98d8d7b 100644
--- a/templates/activities/BlankActivity/root/AndroidManifest.xml.ftl
+++ b/templates/activities/BlankActivity/root/AndroidManifest.xml.ftl
@@ -1,7 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
<application>
- <activity android:name=".${activityClass}"
+ <activity android:name="${packageName}.${activityClass}"
<#if isNewProject>
android:label="@string/app_name"
<#else>
diff --git a/templates/activities/BlankActivity/root/build.gradle.ftl b/templates/activities/BlankActivity/root/build.gradle.ftl
new file mode 100644
index 0000000..caf4f20
--- /dev/null
+++ b/templates/activities/BlankActivity/root/build.gradle.ftl
@@ -0,0 +1,7 @@
+dependencies {
+ <#if dependencyList?? >
+ <#list dependencyList as dependency>
+ compile '${dependency}'
+ </#list>
+ </#if>
+}
diff --git a/templates/activities/BlankActivity/root/res/drawable-hdpi/drawer_shadow.9.png b/templates/activities/BlankActivity/root/res/drawable-hdpi/drawer_shadow.9.png
new file mode 100644
index 0000000..236bff5
--- /dev/null
+++ b/templates/activities/BlankActivity/root/res/drawable-hdpi/drawer_shadow.9.png
Binary files differ
diff --git a/templates/activities/BlankActivity/root/res/drawable-hdpi/ic_drawer.png b/templates/activities/BlankActivity/root/res/drawable-hdpi/ic_drawer.png
new file mode 100644
index 0000000..c59f601
--- /dev/null
+++ b/templates/activities/BlankActivity/root/res/drawable-hdpi/ic_drawer.png
Binary files differ
diff --git a/templates/activities/BlankActivity/root/res/drawable-mdpi/drawer_shadow.9.png b/templates/activities/BlankActivity/root/res/drawable-mdpi/drawer_shadow.9.png
new file mode 100644
index 0000000..ffe3a28
--- /dev/null
+++ b/templates/activities/BlankActivity/root/res/drawable-mdpi/drawer_shadow.9.png
Binary files differ
diff --git a/templates/activities/BlankActivity/root/res/drawable-mdpi/ic_drawer.png b/templates/activities/BlankActivity/root/res/drawable-mdpi/ic_drawer.png
new file mode 100644
index 0000000..1ed2c56
--- /dev/null
+++ b/templates/activities/BlankActivity/root/res/drawable-mdpi/ic_drawer.png
Binary files differ
diff --git a/templates/activities/BlankActivity/root/res/drawable-xhdpi/drawer_shadow.9.png b/templates/activities/BlankActivity/root/res/drawable-xhdpi/drawer_shadow.9.png
new file mode 100644
index 0000000..fabe9d9
--- /dev/null
+++ b/templates/activities/BlankActivity/root/res/drawable-xhdpi/drawer_shadow.9.png
Binary files differ
diff --git a/templates/activities/BlankActivity/root/res/drawable-xhdpi/ic_drawer.png b/templates/activities/BlankActivity/root/res/drawable-xhdpi/ic_drawer.png
new file mode 100644
index 0000000..a5fa74d
--- /dev/null
+++ b/templates/activities/BlankActivity/root/res/drawable-xhdpi/ic_drawer.png
Binary files differ
diff --git a/templates/activities/BlankActivity/root/res/drawable-xxhdpi/drawer_shadow.9.png b/templates/activities/BlankActivity/root/res/drawable-xxhdpi/drawer_shadow.9.png
new file mode 100644
index 0000000..b91e9d7
--- /dev/null
+++ b/templates/activities/BlankActivity/root/res/drawable-xxhdpi/drawer_shadow.9.png
Binary files differ
diff --git a/templates/activities/BlankActivity/root/res/drawable-xxhdpi/ic_drawer.png b/templates/activities/BlankActivity/root/res/drawable-xxhdpi/ic_drawer.png
new file mode 100644
index 0000000..9c4685d
--- /dev/null
+++ b/templates/activities/BlankActivity/root/res/drawable-xxhdpi/ic_drawer.png
Binary files differ
diff --git a/templates/activities/BlankActivity/root/res/layout/activity_drawer.xml.ftl b/templates/activities/BlankActivity/root/res/layout/activity_drawer.xml.ftl
new file mode 100644
index 0000000..c23b77d
--- /dev/null
+++ b/templates/activities/BlankActivity/root/res/layout/activity_drawer.xml.ftl
@@ -0,0 +1,30 @@
+<!-- A DrawerLayout is intended to be used as the top-level content view using match_parent for both width and height to consume the full space available. -->
+<android.support.v4.widget.DrawerLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/drawer_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context="${packageName}.${activityClass}">
+
+ <!-- As the main content view, the view below consumes the entire
+ space available using match_parent in both dimensions. -->
+ <FrameLayout
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <!-- android:layout_gravity="start" tells DrawerLayout to treat
+ this as a sliding drawer on the left side for left-to-right
+ languages and on the right side for right-to-left languages.
+ If you're not building against API 17 or higher, use
+ android:layout_gravity="left" instead. -->
+ <!-- The drawer is given a fixed width in dp and extends the full height of
+ the container. -->
+ <fragment android:id="@+id/navigation_drawer"
+ android:layout_width="@dimen/navigation_drawer_width"
+ android:layout_height="match_parent"
+ android:layout_gravity="<#if buildApi gte 17>start<#else>left</#if>"
+ android:name="${packageName}.NavigationDrawerFragment" />
+
+</android.support.v4.widget.DrawerLayout>
diff --git a/templates/activities/BlankActivity/root/res/layout/activity_fragment_container.xml b/templates/activities/BlankActivity/root/res/layout/activity_fragment_container.xml
deleted file mode 100644
index 935d379..0000000
--- a/templates/activities/BlankActivity/root/res/layout/activity_fragment_container.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:id="@+id/container"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context=".${activityClass}"
- tools:ignore="MergeRootFrame" />
diff --git a/templates/activities/BlankActivity/root/res/layout/activity_fragment_container.xml.ftl b/templates/activities/BlankActivity/root/res/layout/activity_fragment_container.xml.ftl
new file mode 100644
index 0000000..da3a7e4
--- /dev/null
+++ b/templates/activities/BlankActivity/root/res/layout/activity_fragment_container.xml.ftl
@@ -0,0 +1,7 @@
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context="${packageName}.${activityClass}"
+ tools:ignore="MergeRootFrame" />
diff --git a/templates/activities/BlankActivity/root/res/layout/activity_pager.xml.ftl b/templates/activities/BlankActivity/root/res/layout/activity_pager.xml.ftl
index ab57463..bdaf98c 100644
--- a/templates/activities/BlankActivity/root/res/layout/activity_pager.xml.ftl
+++ b/templates/activities/BlankActivity/root/res/layout/activity_pager.xml.ftl
@@ -3,22 +3,4 @@
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
- tools:context=".${activityClass}"<#if navType != "pager_strip"> /><#else>>
-
- <!--
- This title strip will display the currently visible page title, as well as the page
- titles for adjacent pages.
- -->
-
- <android.support.v4.view.PagerTitleStrip
- android:id="@+id/pager_title_strip"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="top"
- android:background="#33b5e5"
- android:paddingBottom="4dp"
- android:paddingTop="4dp"
- android:textColor="#fff" />
-
-</android.support.v4.view.ViewPager>
-</#if>
+ tools:context="${packageName}.${activityClass}" />
diff --git a/templates/activities/BlankActivity/root/res/layout/activity_simple.xml.ftl b/templates/activities/BlankActivity/root/res/layout/activity_simple.xml.ftl
deleted file mode 100644
index 9ddd213..0000000
--- a/templates/activities/BlankActivity/root/res/layout/activity_simple.xml.ftl
+++ /dev/null
@@ -1,16 +0,0 @@
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width=<#if buildApi lt 8 >"fill_parent"<#else>"match_parent"</#if>
- android:layout_height=<#if buildApi lt 8 >"fill_parent"<#else>"match_parent"</#if>
- android:paddingLeft="@dimen/activity_horizontal_margin"
- android:paddingRight="@dimen/activity_horizontal_margin"
- android:paddingTop="@dimen/activity_vertical_margin"
- android:paddingBottom="@dimen/activity_vertical_margin"
- tools:context=".${activityClass}">
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/hello_world" />
-
-</RelativeLayout>
diff --git a/templates/activities/BlankActivity/root/res/layout/fragment_dummy.xml.ftl b/templates/activities/BlankActivity/root/res/layout/fragment_dummy.xml.ftl
deleted file mode 100644
index 1f21998..0000000
--- a/templates/activities/BlankActivity/root/res/layout/fragment_dummy.xml.ftl
+++ /dev/null
@@ -1,16 +0,0 @@
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width=<#if buildApi lt 8 >"fill_parent"<#else>"match_parent"</#if>
- android:layout_height=<#if buildApi lt 8 >"fill_parent"<#else>"match_parent"</#if>
- android:paddingLeft="@dimen/activity_horizontal_margin"
- android:paddingRight="@dimen/activity_horizontal_margin"
- android:paddingTop="@dimen/activity_vertical_margin"
- android:paddingBottom="@dimen/activity_vertical_margin"
- tools:context=".${activityClass}$DummySectionFragment">
-
- <TextView
- android:id="@+id/section_label"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
-
-</RelativeLayout>
diff --git a/templates/activities/BlankActivity/root/res/layout/fragment_navigation_drawer.xml.ftl b/templates/activities/BlankActivity/root/res/layout/fragment_navigation_drawer.xml.ftl
new file mode 100644
index 0000000..62c1224
--- /dev/null
+++ b/templates/activities/BlankActivity/root/res/layout/fragment_navigation_drawer.xml.ftl
@@ -0,0 +1,9 @@
+<ListView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:choiceMode="singleChoice"
+ android:divider="@android:color/transparent"
+ android:dividerHeight="0dp"
+ android:background="#cccc"
+ tools:context="${packageName}.NavigationDrawerFragment" />
diff --git a/templates/activities/BlankActivity/root/res/layout/fragment_simple.xml.ftl b/templates/activities/BlankActivity/root/res/layout/fragment_simple.xml.ftl
new file mode 100644
index 0000000..db8cc12
--- /dev/null
+++ b/templates/activities/BlankActivity/root/res/layout/fragment_simple.xml.ftl
@@ -0,0 +1,16 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ tools:context="${packageName}.${activityClass}$PlaceholderFragment">
+
+ <TextView
+ <#if hasSections?has_content>android:id="@+id/section_label"<#else>android:text="@string/hello_world"</#if>
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</RelativeLayout>
diff --git a/templates/activities/BlankActivity/root/res/menu/global.xml.ftl b/templates/activities/BlankActivity/root/res/menu/global.xml.ftl
new file mode 100644
index 0000000..d9d95cd
--- /dev/null
+++ b/templates/activities/BlankActivity/root/res/menu/global.xml.ftl
@@ -0,0 +1,7 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android"<#if appCompat?has_content>
+ xmlns:app="http://schemas.android.com/apk/res-auto"</#if>>
+ <item android:id="@+id/action_settings"
+ android:title="@string/action_settings"
+ android:orderInCategory="100"
+ ${(appCompat?has_content)?string('app','android')}:showAsAction="never" />
+</menu>
diff --git a/templates/activities/BlankActivity/root/res/menu/main.xml.ftl b/templates/activities/BlankActivity/root/res/menu/main.xml.ftl
index e35aa1b..054a3dc 100644
--- a/templates/activities/BlankActivity/root/res/menu/main.xml.ftl
+++ b/templates/activities/BlankActivity/root/res/menu/main.xml.ftl
@@ -1,6 +1,12 @@
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
+<menu xmlns:android="http://schemas.android.com/apk/res/android"<#if appCompat?has_content>
+ xmlns:app="http://schemas.android.com/apk/res-auto"</#if>
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:context="${packageName}.${activityClass}" >
+ <#if navType == 'drawer'><item android:id="@+id/action_example"
+ android:title="@string/action_example"
+ ${(appCompat?has_content)?string('app','android')}:showAsAction="withText|ifRoom" /></#if>
<item android:id="@+id/action_settings"
android:title="@string/action_settings"
- android:orderInCategory="100"<#if buildApi gte 11>
- android:showAsAction="never"</#if> />
+ android:orderInCategory="100"
+ ${(appCompat?has_content)?string('app','android')}:showAsAction="never" />
</menu>
diff --git a/templates/activities/BlankActivity/root/res/values-sw600dp/dimens.xml b/templates/activities/BlankActivity/root/res/values-sw600dp/dimens.xml
deleted file mode 100644
index 886b05f..0000000
--- a/templates/activities/BlankActivity/root/res/values-sw600dp/dimens.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<resources>
- <!-- Customize dimensions originally defined in res/values/dimens.xml (such as
- screen margins) for sw600dp devices (e.g. 7" tablets) here. -->
-</resources>
diff --git a/templates/activities/BlankActivity/root/res/values-sw720dp-land/dimens.xml b/templates/activities/BlankActivity/root/res/values-sw720dp-land/dimens.xml
deleted file mode 100644
index 00059fc..0000000
--- a/templates/activities/BlankActivity/root/res/values-sw720dp-land/dimens.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<resources>
- <!-- Customize dimensions originally defined in res/values/dimens.xml (such as
- screen margins) for sw720dp devices (e.g. 10" tablets) in landscape here. -->
- <dimen name="activity_horizontal_margin">128dp</dimen>
-</resources>
diff --git a/templates/activities/BlankActivity/root/res/values-w820dp/dimens.xml b/templates/activities/BlankActivity/root/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/templates/activities/BlankActivity/root/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+<resources>
+ <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+ (such as screen margins) for screens with more than 820dp of available width. This
+ would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+ <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>
diff --git a/templates/activities/BlankActivity/root/res/values/dimens.xml b/templates/activities/BlankActivity/root/res/values/dimens.xml
deleted file mode 100644
index 47c8224..0000000
--- a/templates/activities/BlankActivity/root/res/values/dimens.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<resources>
- <!-- Default screen margins, per the Android Design guidelines. -->
- <dimen name="activity_horizontal_margin">16dp</dimen>
- <dimen name="activity_vertical_margin">16dp</dimen>
-</resources>
diff --git a/templates/activities/BlankActivity/root/res/values/dimens.xml.ftl b/templates/activities/BlankActivity/root/res/values/dimens.xml.ftl
new file mode 100644
index 0000000..74c7299
--- /dev/null
+++ b/templates/activities/BlankActivity/root/res/values/dimens.xml.ftl
@@ -0,0 +1,11 @@
+<resources>
+ <!-- Default screen margins, per the Android Design guidelines. -->
+ <dimen name="activity_horizontal_margin">16dp</dimen>
+ <dimen name="activity_vertical_margin">16dp</dimen>
+
+ <#if navType == 'drawer'>
+ <!-- Per the design guidelines, navigation drawers should be between 240dp and 320dp:
+ https://developer.android.com/design/patterns/navigation-drawer.html -->
+ <dimen name="navigation_drawer_width">240dp</dimen>
+ </#if>
+</resources>
diff --git a/templates/activities/BlankActivity/root/res/values/strings.xml.ftl b/templates/activities/BlankActivity/root/res/values/strings.xml.ftl
index 1c9bc8b..bcb8586 100644
--- a/templates/activities/BlankActivity/root/res/values/strings.xml.ftl
+++ b/templates/activities/BlankActivity/root/res/values/strings.xml.ftl
@@ -3,13 +3,19 @@
<string name="title_${activityToLayout(activityClass)}">${escapeXmlString(activityTitle)}</string>
</#if>
- <string name="action_settings">Settings</string>
-
- <#if navType != "none">
+ <#if hasSections?has_content>
<string name="title_section1">Section 1</string>
<string name="title_section2">Section 2</string>
<string name="title_section3">Section 3</string>
<#else>
<string name="hello_world">Hello world!</string>
</#if>
+ <#if navType == 'drawer'>
+ <string name="navigation_drawer_open">Open navigation drawer</string>
+ <string name="navigation_drawer_close">Close navigation drawer</string>
+
+ <string name="action_example">Example action</string>
+ </#if>
+ <string name="action_settings">Settings</string>
+
</resources>
diff --git a/templates/activities/BlankActivity/root/src/app_package/DrawerActivity.java.ftl b/templates/activities/BlankActivity/root/src/app_package/DrawerActivity.java.ftl
new file mode 100644
index 0000000..bc28bb9
--- /dev/null
+++ b/templates/activities/BlankActivity/root/src/app_package/DrawerActivity.java.ftl
@@ -0,0 +1,83 @@
+package ${packageName};
+
+import android.app.Activity;
+<#if appCompat?has_content>import android.support.v7.app.ActionBarActivity</#if>;
+import android.<#if appCompat?has_content>support.v7.</#if>app.ActionBar;
+import android.<#if appCompat?has_content>support.v4.</#if>app.Fragment;
+import android.<#if appCompat?has_content>support.v4.</#if>app.FragmentManager;
+import android.content.Context;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.support.v4.widget.DrawerLayout;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+
+public class ${activityClass} extends ${(appCompat?has_content)?string('ActionBar','')}Activity
+ implements NavigationDrawerFragment.NavigationDrawerCallbacks {
+
+ /**
+ * Fragment managing the behaviors, interactions and presentation of the navigation drawer.
+ */
+ private NavigationDrawerFragment mNavigationDrawerFragment;
+
+ /**
+ * Used to store the last screen title. For use in {@link #restoreActionBar()}.
+ */
+ private CharSequence mTitle;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.${layoutName});
+
+ mNavigationDrawerFragment = (NavigationDrawerFragment)
+ get${Support}FragmentManager().findFragmentById(R.id.navigation_drawer);
+ mTitle = getTitle();
+
+ // Set up the drawer.
+ mNavigationDrawerFragment.setUp(
+ R.id.navigation_drawer,
+ (DrawerLayout) findViewById(R.id.drawer_layout));
+ }
+
+ @Override
+ public void onNavigationDrawerItemSelected(int position) {
+ // update the main content by replacing fragments
+ FragmentManager fragmentManager = get${Support}FragmentManager();
+ fragmentManager.beginTransaction()
+ .replace(R.id.container, PlaceholderFragment.newInstance(position + 1))
+ .commit();
+ }
+
+ public void onSectionAttached(int number) {
+ switch (number) {
+ case 1:
+ mTitle = getString(R.string.title_section1);
+ break;
+ case 2:
+ mTitle = getString(R.string.title_section2);
+ break;
+ case 3:
+ mTitle = getString(R.string.title_section3);
+ break;
+ }
+ }
+
+ public void restoreActionBar() {
+ ActionBar actionBar = get${Support}ActionBar();
+ actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
+ actionBar.setDisplayShowTitleEnabled(true);
+ actionBar.setTitle(mTitle);
+ }
+
+ <#include "include_options_menu.java.ftl">
+
+ <#include "include_fragment.java.ftl">
+
+}
diff --git a/templates/activities/BlankActivity/root/src/app_package/DropdownActivity.java.ftl b/templates/activities/BlankActivity/root/src/app_package/DropdownActivity.java.ftl
index cb0665e..eb2d740 100644
--- a/templates/activities/BlankActivity/root/src/app_package/DropdownActivity.java.ftl
+++ b/templates/activities/BlankActivity/root/src/app_package/DropdownActivity.java.ftl
@@ -1,13 +1,11 @@
package ${packageName};
-<#if minApiLevel < 14>import android.annotation.TargetApi;</#if>
-import android.app.ActionBar;
+import <#if appCompat?has_content>android.support.v7.app.ActionBarActivity<#else>android.app.Activity</#if>;
+import android.<#if appCompat?has_content>support.v7.</#if>app.ActionBar;
+import android.<#if appCompat?has_content>support.v4.</#if>app.Fragment;
+import android.content.Context;
+import android.os.Build;
import android.os.Bundle;
-<#if minApiLevel < 14>import android.content.Context;
-import android.os.Build;</#if>
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.NavUtils;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -17,7 +15,7 @@
import android.widget.ArrayAdapter;
import android.widget.TextView;
-public class ${activityClass} extends FragmentActivity implements ActionBar.OnNavigationListener {
+public class ${activityClass} extends ${(appCompat?has_content)?string('ActionBar','')}Activity implements ActionBar.OnNavigationListener {
/**
* The serialization (saved instance state) Bundle key representing the
@@ -31,7 +29,7 @@
setContentView(R.layout.${layoutName});
// Set up the action bar to show a dropdown list.
- final ActionBar actionBar = getActionBar();
+ final ActionBar actionBar = get${Support}ActionBar();
actionBar.setDisplayShowTitleEnabled(false);
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
<#if parentActivityClass != "">
@@ -43,7 +41,7 @@
actionBar.setListNavigationCallbacks(
// Specify a SpinnerAdapter to populate the dropdown list.
new ArrayAdapter<String>(
- <#if minApiLevel gte 14>actionBar.getThemedContext()<#else>getActionBarThemedContextCompat()</#if>,
+ actionBar.getThemedContext(),
android.R.layout.simple_list_item_1,
android.R.id.text1,
new String[] {
@@ -54,27 +52,11 @@
this);
}
- <#if minApiLevel < 14>
- /**
- * Backward-compatible version of {@link ActionBar#getThemedContext()} that
- * simply returns the {@link android.app.Activity} if
- * <code>getThemedContext</code> is unavailable.
- */
- @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
- private Context getActionBarThemedContextCompat() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
- return getActionBar().getThemedContext();
- } else {
- return this;
- }
- }
- </#if>
-
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
// Restore the previously serialized current dropdown position.
if (savedInstanceState.containsKey(STATE_SELECTED_NAVIGATION_ITEM)) {
- getActionBar().setSelectedNavigationItem(
+ get${Support}ActionBar().setSelectedNavigationItem(
savedInstanceState.getInt(STATE_SELECTED_NAVIGATION_ITEM));
}
}
@@ -83,26 +65,21 @@
public void onSaveInstanceState(Bundle outState) {
// Serialize the current dropdown position.
outState.putInt(STATE_SELECTED_NAVIGATION_ITEM,
- getActionBar().getSelectedNavigationIndex());
+ get${Support}ActionBar().getSelectedNavigationIndex());
}
- <#include "include_onCreateOptionsMenu.java.ftl">
- <#include "include_onOptionsItemSelected.java.ftl">
+ <#include "include_options_menu.java.ftl">
@Override
public boolean onNavigationItemSelected(int position, long id) {
// When the given dropdown item is selected, show its contents in the
// container view.
- Fragment fragment = new DummySectionFragment();
- Bundle args = new Bundle();
- args.putInt(DummySectionFragment.ARG_SECTION_NUMBER, position + 1);
- fragment.setArguments(args);
- getSupportFragmentManager().beginTransaction()
- .replace(R.id.container, fragment)
+ get${Support}FragmentManager().beginTransaction()
+ .replace(R.id.container, PlaceholderFragment.newInstance(position + 1))
.commit();
return true;
}
- <#include "include_DummySectionFragment.java.ftl">
+ <#include "include_fragment.java.ftl">
}
diff --git a/templates/activities/BlankActivity/root/src/app_package/NavigationDrawerFragment.java.ftl b/templates/activities/BlankActivity/root/src/app_package/NavigationDrawerFragment.java.ftl
new file mode 100644
index 0000000..3880362
--- /dev/null
+++ b/templates/activities/BlankActivity/root/src/app_package/NavigationDrawerFragment.java.ftl
@@ -0,0 +1,282 @@
+package ${packageName};
+
+<#if appCompat?has_content>import android.support.v7.app.ActionBarActivity;</#if>;
+import android.app.Activity;
+import android.<#if appCompat?has_content>support.v7.</#if>app.ActionBar;
+import android.<#if appCompat?has_content>support.v4.</#if>app.Fragment;
+import android.support.v4.app.ActionBarDrawerToggle;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.widget.DrawerLayout;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.Toast;
+
+/**
+ * Fragment used for managing interactions for and presentation of a navigation drawer.
+ * See the <a href="https://developer.android.com/design/patterns/navigation-drawer.html#Interaction">
+ * design guidelines</a> for a complete explanation of the behaviors implemented here.
+ */
+public class NavigationDrawerFragment extends Fragment {
+
+ /**
+ * Remember the position of the selected item.
+ */
+ private static final String STATE_SELECTED_POSITION = "selected_navigation_drawer_position";
+
+ /**
+ * Per the design guidelines, you should show the drawer on launch until the user manually
+ * expands it. This shared preference tracks this.
+ */
+ private static final String PREF_USER_LEARNED_DRAWER = "navigation_drawer_learned";
+
+ /**
+ * A pointer to the current callbacks instance (the Activity).
+ */
+ private NavigationDrawerCallbacks mCallbacks;
+
+ /**
+ * Helper component that ties the action bar to the navigation drawer.
+ */
+ private ActionBarDrawerToggle mDrawerToggle;
+
+ private DrawerLayout mDrawerLayout;
+ private ListView mDrawerListView;
+ private View mFragmentContainerView;
+
+ private int mCurrentSelectedPosition = 0;
+ private boolean mFromSavedInstanceState;
+ private boolean mUserLearnedDrawer;
+
+ public NavigationDrawerFragment() {
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Read in the flag indicating whether or not the user has demonstrated awareness of the
+ // drawer. See PREF_USER_LEARNED_DRAWER for details.
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
+ mUserLearnedDrawer = sp.getBoolean(PREF_USER_LEARNED_DRAWER, false);
+
+ if (savedInstanceState != null) {
+ mCurrentSelectedPosition = savedInstanceState.getInt(STATE_SELECTED_POSITION);
+ mFromSavedInstanceState = true;
+ }
+
+ // Select either the default item (0) or the last selected item.
+ selectItem(mCurrentSelectedPosition);
+ }
+
+ @Override
+ public void onActivityCreated (Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ // Indicate that this fragment would like to influence the set of actions in the action bar.
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ mDrawerListView = (ListView) inflater.inflate(
+ R.layout.fragment_navigation_drawer, container, false);
+ mDrawerListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ selectItem(position);
+ }
+ });
+ mDrawerListView.setAdapter(new ArrayAdapter<String>(
+ getActionBar().getThemedContext(),
+ android.R.layout.simple_list_item_<#if minApiLevel gte 11>activated_</#if>1,
+ android.R.id.text1,
+ new String[]{
+ getString(R.string.title_section1),
+ getString(R.string.title_section2),
+ getString(R.string.title_section3),
+ }));
+ mDrawerListView.setItemChecked(mCurrentSelectedPosition, true);
+ return mDrawerListView;
+ }
+
+ public boolean isDrawerOpen() {
+ return mDrawerLayout != null && mDrawerLayout.isDrawerOpen(mFragmentContainerView);
+ }
+
+ /**
+ * Users of this fragment must call this method to set up the navigation drawer interactions.
+ *
+ * @param fragmentId The android:id of this fragment in its activity's layout.
+ * @param drawerLayout The DrawerLayout containing this fragment's UI.
+ */
+ public void setUp(int fragmentId, DrawerLayout drawerLayout) {
+ mFragmentContainerView = getActivity().findViewById(fragmentId);
+ mDrawerLayout = drawerLayout;
+
+ // set a custom shadow that overlays the main content when the drawer opens
+ mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
+ // set up the drawer's list view with items and click listener
+
+ ActionBar actionBar = getActionBar();
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setHomeButtonEnabled(true);
+
+ // ActionBarDrawerToggle ties together the the proper interactions
+ // between the navigation drawer and the action bar app icon.
+ mDrawerToggle = new ActionBarDrawerToggle(
+ getActivity(), /* host Activity */
+ mDrawerLayout, /* DrawerLayout object */
+ R.drawable.ic_drawer, /* nav drawer image to replace 'Up' caret */
+ R.string.navigation_drawer_open, /* "open drawer" description for accessibility */
+ R.string.navigation_drawer_close /* "close drawer" description for accessibility */
+ ) {
+ @Override
+ public void onDrawerClosed(View drawerView) {
+ super.onDrawerClosed(drawerView);
+ if (!isAdded()) {
+ return;
+ }
+
+ getActivity().${(appCompat?has_content)?string('supportInvalidate','invalidate')}OptionsMenu(); // calls onPrepareOptionsMenu()
+ }
+
+ @Override
+ public void onDrawerOpened(View drawerView) {
+ super.onDrawerOpened(drawerView);
+ if (!isAdded()) {
+ return;
+ }
+
+ if (!mUserLearnedDrawer) {
+ // The user manually opened the drawer; store this flag to prevent auto-showing
+ // the navigation drawer automatically in the future.
+ mUserLearnedDrawer = true;
+ SharedPreferences sp = PreferenceManager
+ .getDefaultSharedPreferences(getActivity());
+ sp.edit().putBoolean(PREF_USER_LEARNED_DRAWER, true).${(minApiLevel gte 9)?string('apply','commit')}();
+ }
+
+ getActivity().${(appCompat?has_content)?string('supportInvalidate','invalidate')}OptionsMenu(); // calls onPrepareOptionsMenu()
+ }
+ };
+
+ // If the user hasn't 'learned' about the drawer, open it to introduce them to the drawer,
+ // per the navigation drawer design guidelines.
+ if (!mUserLearnedDrawer && !mFromSavedInstanceState) {
+ mDrawerLayout.openDrawer(mFragmentContainerView);
+ }
+
+ // Defer code dependent on restoration of previous instance state.
+ mDrawerLayout.post(new Runnable() {
+ @Override
+ public void run() {
+ mDrawerToggle.syncState();
+ }
+ });
+
+ mDrawerLayout.setDrawerListener(mDrawerToggle);
+ }
+
+ private void selectItem(int position) {
+ mCurrentSelectedPosition = position;
+ if (mDrawerListView != null) {
+ mDrawerListView.setItemChecked(position, true);
+ }
+ if (mDrawerLayout != null) {
+ mDrawerLayout.closeDrawer(mFragmentContainerView);
+ }
+ if (mCallbacks != null) {
+ mCallbacks.onNavigationDrawerItemSelected(position);
+ }
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ try {
+ mCallbacks = (NavigationDrawerCallbacks) activity;
+ } catch (ClassCastException e) {
+ throw new ClassCastException("Activity must implement NavigationDrawerCallbacks.");
+ }
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ mCallbacks = null;
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(STATE_SELECTED_POSITION, mCurrentSelectedPosition);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ // Forward the new configuration the drawer toggle component.
+ mDrawerToggle.onConfigurationChanged(newConfig);
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ // If the drawer is open, show the global app actions in the action bar. See also
+ // showGlobalContextActionBar, which controls the top-left area of the action bar.
+ if (mDrawerLayout != null && isDrawerOpen()) {
+ inflater.inflate(R.menu.global, menu);
+ showGlobalContextActionBar();
+ }
+ super.onCreateOptionsMenu(menu, inflater);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (mDrawerToggle.onOptionsItemSelected(item)) {
+ return true;
+ }
+
+ if (item.getItemId() == R.id.action_example) {
+ Toast.makeText(getActivity(), "Example action.", Toast.LENGTH_SHORT).show();
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ /**
+ * Per the navigation drawer design guidelines, updates the action bar to show the global app
+ * 'context', rather than just what's in the current screen.
+ */
+ private void showGlobalContextActionBar() {
+ ActionBar actionBar = getActionBar();
+ actionBar.setDisplayShowTitleEnabled(true);
+ actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
+ actionBar.setTitle(R.string.app_name);
+ }
+
+ private ActionBar getActionBar() {
+ return <#if appCompat?has_content>((ActionBarActivity) getActivity()).getSupportActionBar();<#else>getActivity().getActionBar();</#if>
+ }
+
+ /**
+ * Callbacks interface that all activities using this fragment must implement.
+ */
+ public static interface NavigationDrawerCallbacks {
+ /**
+ * Called when an item in the navigation drawer is selected.
+ */
+ void onNavigationDrawerItemSelected(int position);
+ }
+}
diff --git a/templates/activities/BlankActivity/root/src/app_package/SimpleActivity.java.ftl b/templates/activities/BlankActivity/root/src/app_package/SimpleActivity.java.ftl
index 7edd647..417a5ed 100644
--- a/templates/activities/BlankActivity/root/src/app_package/SimpleActivity.java.ftl
+++ b/templates/activities/BlankActivity/root/src/app_package/SimpleActivity.java.ftl
@@ -1,42 +1,32 @@
package ${packageName};
+import <#if appCompat?has_content>android.support.v7.app.ActionBarActivity<#else>android.app.Activity</#if>;
+import android.<#if appCompat?has_content>support.v7.</#if>app.ActionBar;
+import android.<#if appCompat?has_content>support.v4.</#if>app.Fragment;
import android.os.Bundle;
-import android.app.Activity;
+import android.view.LayoutInflater;
import android.view.Menu;
-<#if parentActivityClass != "">
import android.view.MenuItem;
-import android.support.v4.app.NavUtils;
-<#if minApiLevel < 11>
-import android.annotation.TargetApi;
+import android.view.View;
+import android.view.ViewGroup;
import android.os.Build;
-</#if>
-</#if>
-public class ${activityClass} extends Activity {
+public class ${activityClass} extends ${(appCompat?has_content)?string('ActionBar','')}Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.${layoutName});
- <#if parentActivityClass != "">
- // Show the Up button in the action bar.
- setupActionBar();
- </#if>
+
+ if (savedInstanceState == null) {
+ get${Support}FragmentManager().beginTransaction()
+ .add(R.id.container, new PlaceholderFragment())
+ .commit();
+ }
}
- <#if parentActivityClass != "">
- /**
- * Set up the {@link android.app.ActionBar}<#if minApiLevel < 11>, if the API is available</#if>.
- */
- <#if minApiLevel < 11>@TargetApi(Build.VERSION_CODES.HONEYCOMB)
- </#if>private void setupActionBar() {
- <#if minApiLevel < 11>if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {</#if>
- getActionBar().setDisplayHomeAsUpEnabled(true);
- <#if minApiLevel < 11>}</#if>
- }
- </#if>
+ <#include "include_options_menu.java.ftl">
- <#include "include_onCreateOptionsMenu.java.ftl">
- <#include "include_onOptionsItemSelected.java.ftl">
+ <#include "include_fragment.java.ftl">
}
diff --git a/templates/activities/BlankActivity/root/src/app_package/TabsActivity.java.ftl b/templates/activities/BlankActivity/root/src/app_package/TabsActivity.java.ftl
deleted file mode 100644
index 0bf975e..0000000
--- a/templates/activities/BlankActivity/root/src/app_package/TabsActivity.java.ftl
+++ /dev/null
@@ -1,86 +0,0 @@
-package ${packageName};
-
-import android.app.ActionBar;
-import android.app.FragmentTransaction;
-import android.os.Bundle;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.NavUtils;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-public class ${activityClass} extends FragmentActivity implements ActionBar.TabListener {
-
- /**
- * The serialization (saved instance state) Bundle key representing the
- * current tab position.
- */
- private static final String STATE_SELECTED_NAVIGATION_ITEM = "selected_navigation_item";
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.${layoutName});
-
- // Set up the action bar to show tabs.
- final ActionBar actionBar = getActionBar();
- actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
- <#if parentActivityClass != "">
- // Show the Up button in the action bar.
- actionBar.setDisplayHomeAsUpEnabled(true);
- </#if>
-
- // For each of the sections in the app, add a tab to the action bar.
- actionBar.addTab(actionBar.newTab().setText(R.string.title_section1).setTabListener(this));
- actionBar.addTab(actionBar.newTab().setText(R.string.title_section2).setTabListener(this));
- actionBar.addTab(actionBar.newTab().setText(R.string.title_section3).setTabListener(this));
- }
-
- @Override
- public void onRestoreInstanceState(Bundle savedInstanceState) {
- // Restore the previously serialized current tab position.
- if (savedInstanceState.containsKey(STATE_SELECTED_NAVIGATION_ITEM)) {
- getActionBar().setSelectedNavigationItem(
- savedInstanceState.getInt(STATE_SELECTED_NAVIGATION_ITEM));
- }
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- // Serialize the current tab position.
- outState.putInt(STATE_SELECTED_NAVIGATION_ITEM,
- getActionBar().getSelectedNavigationIndex());
- }
-
- <#include "include_onCreateOptionsMenu.java.ftl">
- <#include "include_onOptionsItemSelected.java.ftl">
-
- @Override
- public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
- // When the given tab is selected, show the tab contents in the
- // container view.
- Fragment fragment = new DummySectionFragment();
- Bundle args = new Bundle();
- args.putInt(DummySectionFragment.ARG_SECTION_NUMBER, tab.getPosition() + 1);
- fragment.setArguments(args);
- getSupportFragmentManager().beginTransaction()
- .replace(R.id.container, fragment)
- .commit();
- }
-
- @Override
- public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
- }
-
- @Override
- public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
- }
-
- <#include "include_DummySectionFragment.java.ftl">
-
-}
diff --git a/templates/activities/BlankActivity/root/src/app_package/TabsAndPagerActivity.java.ftl b/templates/activities/BlankActivity/root/src/app_package/TabsAndPagerActivity.java.ftl
index 30c4f4a..d89e4ac 100644
--- a/templates/activities/BlankActivity/root/src/app_package/TabsAndPagerActivity.java.ftl
+++ b/templates/activities/BlankActivity/root/src/app_package/TabsAndPagerActivity.java.ftl
@@ -2,14 +2,13 @@
import java.util.Locale;
-<#if navType?contains("tabs")>import android.app.ActionBar;
-import android.app.FragmentTransaction;</#if>
+import <#if appCompat?has_content>android.support.v7.app.ActionBarActivity<#else>android.app.Activity</#if>;
+import android.<#if appCompat?has_content>support.v7.</#if>app.ActionBar;
+import android.<#if appCompat?has_content>support.v4.</#if>app.Fragment;
+import android.<#if appCompat?has_content>support.v4.</#if>app.FragmentManager;
+import android.<#if appCompat?has_content>support.v4.</#if>app.FragmentTransaction;
+import android.support.${(appCompat?has_content)?string('v4','v13')}.app.FragmentPagerAdapter;
import android.os.Bundle;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.app.FragmentPagerAdapter;
-import android.support.v4.app.NavUtils;
import android.support.v4.view.ViewPager;
import android.view.Gravity;
import android.view.LayoutInflater;
@@ -19,15 +18,15 @@
import android.view.ViewGroup;
import android.widget.TextView;
-public class ${activityClass} extends FragmentActivity<#if navType?contains("tabs")> implements ActionBar.TabListener</#if> {
+public class ${activityClass} extends ${(appCompat?has_content)?string('ActionBar','')}Activity<#if navType == 'tabs'> implements ActionBar.TabListener</#if> {
/**
* The {@link android.support.v4.view.PagerAdapter} that will provide
* fragments for each of the sections. We use a
- * {@link android.support.v4.app.FragmentPagerAdapter} derivative, which
- * will keep every loaded fragment in memory. If this becomes too memory
- * intensive, it may be best to switch to a
- * {@link android.support.v4.app.FragmentStatePagerAdapter}.
+ * {@link FragmentPagerAdapter} derivative, which will keep every
+ * loaded fragment in memory. If this becomes too memory intensive, it
+ * may be best to switch to a
+ * {@link android.support.${(appCompat?has_content)?string('v4','v13')}.app.FragmentStatePagerAdapter}.
*/
SectionsPagerAdapter mSectionsPagerAdapter;
@@ -41,28 +40,20 @@
super.onCreate(savedInstanceState);
setContentView(R.layout.${layoutName});
- <#if navType?contains("tabs")>
+ <#if navType == 'tabs'>
// Set up the action bar.
- final ActionBar actionBar = getActionBar();
- actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
- <#if parentActivityClass != "">
- // Show the Up button in the action bar.
- actionBar.setDisplayHomeAsUpEnabled(true);
- </#if>
- <#elseif parentActivityClass != "">
- // Show the Up button in the action bar.
- getActionBar().setDisplayHomeAsUpEnabled(true);
- </#if>
+ final ActionBar actionBar = get${Support}ActionBar();
+ actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);</#if>
// Create the adapter that will return a fragment for each of the three
- // primary sections of the app.
- mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
+ // primary sections of the activity.
+ mSectionsPagerAdapter = new SectionsPagerAdapter(get${Support}FragmentManager());
// Set up the ViewPager with the sections adapter.
mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(mSectionsPagerAdapter);
- <#if navType?contains("tabs")>
+ <#if navType == 'tabs'>
// When swiping between different sections, select the corresponding
// tab. We can also use ActionBar.Tab#select() to do this if we have
// a reference to the Tab.
@@ -87,10 +78,9 @@
</#if>
}
- <#include "include_onCreateOptionsMenu.java.ftl">
- <#include "include_onOptionsItemSelected.java.ftl">
+ <#include "include_options_menu.java.ftl">
- <#if navType?contains("tabs")>@Override
+ <#if navType == 'tabs'>@Override
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
// When the given tab is selected, switch to the corresponding page in
// the ViewPager.
@@ -118,13 +108,8 @@
@Override
public Fragment getItem(int position) {
// getItem is called to instantiate the fragment for the given page.
- // Return a DummySectionFragment (defined as a static inner class
- // below) with the page number as its lone argument.
- Fragment fragment = new DummySectionFragment();
- Bundle args = new Bundle();
- args.putInt(DummySectionFragment.ARG_SECTION_NUMBER, position + 1);
- fragment.setArguments(args);
- return fragment;
+ // Return a PlaceholderFragment (defined as a static inner class below).
+ return PlaceholderFragment.newInstance(position + 1);
}
@Override
@@ -148,6 +133,6 @@
}
}
- <#include "include_DummySectionFragment.java.ftl">
+ <#include "include_fragment.java.ftl">
}
diff --git a/templates/activities/BlankActivity/root/src/app_package/include_DummySectionFragment.java.ftl b/templates/activities/BlankActivity/root/src/app_package/include_DummySectionFragment.java.ftl
deleted file mode 100644
index 8eb1399..0000000
--- a/templates/activities/BlankActivity/root/src/app_package/include_DummySectionFragment.java.ftl
+++ /dev/null
@@ -1,23 +0,0 @@
- /**
- * A dummy fragment representing a section of the app, but that simply
- * displays dummy text.
- */
- public static class DummySectionFragment extends Fragment {
- /**
- * The fragment argument representing the section number for this
- * fragment.
- */
- public static final String ARG_SECTION_NUMBER = "section_number";
-
- public DummySectionFragment() {
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- View rootView = inflater.inflate(R.layout.fragment_${classToResource(activityClass)}_dummy, container, false);
- TextView dummyTextView = (TextView) rootView.findViewById(R.id.section_label);
- dummyTextView.setText(Integer.toString(getArguments().getInt(ARG_SECTION_NUMBER)));
- return rootView;
- }
- }
diff --git a/templates/activities/BlankActivity/root/src/app_package/include_fragment.java.ftl b/templates/activities/BlankActivity/root/src/app_package/include_fragment.java.ftl
new file mode 100644
index 0000000..c271435
--- /dev/null
+++ b/templates/activities/BlankActivity/root/src/app_package/include_fragment.java.ftl
@@ -0,0 +1,47 @@
+ /**
+ * A placeholder fragment containing a simple view.
+ */
+ public static class PlaceholderFragment extends Fragment {
+<#if hasSections?has_content>
+ /**
+ * The fragment argument representing the section number for this
+ * fragment.
+ */
+ private static final String ARG_SECTION_NUMBER = "section_number";
+
+ /**
+ * Returns a new instance of this fragment for the given section
+ * number.
+ */
+ public static PlaceholderFragment newInstance(int sectionNumber) {
+ PlaceholderFragment fragment = new PlaceholderFragment();
+ Bundle args = new Bundle();
+ args.putInt(ARG_SECTION_NUMBER, sectionNumber);
+ fragment.setArguments(args);
+ return fragment;
+ }
+ </#if>
+
+ public PlaceholderFragment() {
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View rootView = inflater.inflate(R.layout.${fragmentLayoutName}, container, false);
+ <#if hasSections?has_content>
+ TextView textView = (TextView) rootView.findViewById(R.id.section_label);
+ textView.setText(Integer.toString(getArguments().getInt(ARG_SECTION_NUMBER)));
+ </#if>
+ return rootView;
+ }
+<#if navType == 'drawer'>
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ ((${activityClass}) activity).onSectionAttached(
+ getArguments().getInt(ARG_SECTION_NUMBER));
+ }
+</#if>
+ }
diff --git a/templates/activities/BlankActivity/root/src/app_package/include_onCreateOptionsMenu.java.ftl b/templates/activities/BlankActivity/root/src/app_package/include_onCreateOptionsMenu.java.ftl
deleted file mode 100644
index 005d629..0000000
--- a/templates/activities/BlankActivity/root/src/app_package/include_onCreateOptionsMenu.java.ftl
+++ /dev/null
@@ -1,6 +0,0 @@
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- // Inflate the menu; this adds items to the action bar if it is present.
- getMenuInflater().inflate(R.menu.${menuName}, menu);
- return true;
- }
diff --git a/templates/activities/BlankActivity/root/src/app_package/include_onOptionsItemSelected.java.ftl b/templates/activities/BlankActivity/root/src/app_package/include_onOptionsItemSelected.java.ftl
deleted file mode 100644
index e1dc462..0000000
--- a/templates/activities/BlankActivity/root/src/app_package/include_onOptionsItemSelected.java.ftl
+++ /dev/null
@@ -1,19 +0,0 @@
- <#if parentActivityClass != "">
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- // This ID represents the Home or Up button. In the case of this
- // activity, the Up button is shown. Use NavUtils to allow users
- // to navigate up one level in the application structure. For
- // more details, see the Navigation pattern on Android Design:
- //
- // http://developer.android.com/design/patterns/navigation.html#up-vs-back
- //
- NavUtils.navigateUpFromSameTask(this);
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
- </#if>
diff --git a/templates/activities/BlankActivity/root/src/app_package/include_options_menu.java.ftl b/templates/activities/BlankActivity/root/src/app_package/include_options_menu.java.ftl
new file mode 100644
index 0000000..d99a3d2
--- /dev/null
+++ b/templates/activities/BlankActivity/root/src/app_package/include_options_menu.java.ftl
@@ -0,0 +1,28 @@
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ <#if navType == 'drawer'>if (!mNavigationDrawerFragment.isDrawerOpen()) {
+ // Only show items in the action bar relevant to this screen
+ // if the drawer is not showing. Otherwise, let the drawer
+ // decide what to show in the action bar.
+ getMenuInflater().inflate(R.menu.${menuName}, menu);
+ restoreActionBar();
+ return true;
+ }
+ return super.onCreateOptionsMenu(menu);<#else>
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.${menuName}, menu);
+ return true;</#if>
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ int id = item.getItemId();
+ if (id == R.id.action_settings) {
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
diff --git a/templates/activities/BlankActivity/template.xml b/templates/activities/BlankActivity/template.xml
index 8b02ba9..a72292f 100644
--- a/templates/activities/BlankActivity/template.xml
+++ b/templates/activities/BlankActivity/template.xml
@@ -1,10 +1,11 @@
<?xml version="1.0"?>
<template
format="3"
- revision="3"
+ revision="4"
name="Blank Activity"
+ minApi="7"
+ minBuildApi="14"
description="Creates a new blank activity, with an action bar and optional navigational elements such as tabs or horizontal swipe.">
- <dependency name="android-support-v4" revision="8" />
<category value="Activities" />
@@ -27,6 +28,15 @@
help="The name of the layout to create for the activity" />
<parameter
+ id="fragmentLayoutName"
+ name="Fragment Layout Name"
+ type="string"
+ constraints="layout|unique|nonempty"
+ suggest="fragment_${classToResource(activityClass)}"
+ default="fragment_main"
+ help="The name of the layout to create for the activity's content fragment" />
+
+ <parameter
id="activityTitle"
name="Title"
type="string"
@@ -57,10 +67,10 @@
default="none"
help="The type of navigation to use for the activity" >
<option id="none" default="true">None</option>
- <!--<option id="tabs" minApi="11">Fixed Tabs</option>-->
- <option id="tabs_pager" minApi="11">Fixed Tabs + Swipe</option>
- <option id="pager_strip" minApi="11">Scrollable Tabs + Swipe</option>
- <option id="dropdown" minApi="11">Dropdown</option>
+ <option id="pager">Swipe Views (ViewPager)</option>
+ <option id="tabs">Action Bar Tabs (with ViewPager)</option>
+ <option id="spinner">Action Bar Spinner</option>
+ <option id="drawer">Navigation Drawer</option>
</parameter>
<parameter
@@ -77,9 +87,9 @@
<!-- attributes act as selectors based on chosen parameters -->
<thumb navType="none">template_blank_activity.png</thumb>
<thumb navType="tabs">template_blank_activity_tabs.png</thumb>
- <thumb navType="tabs_pager">template_blank_activity_tabs_pager.png</thumb>
- <thumb navType="pager_strip">template_blank_activity_pager.png</thumb>
- <thumb navType="dropdown">template_blank_activity_dropdown.png</thumb>
+ <thumb navType="pager">template_blank_activity_pager.png</thumb>
+ <thumb navType="spinner">template_blank_activity_dropdown.png</thumb>
+ <thumb navType="drawer">template_blank_activity_drawer.png</thumb>
</thumbs>
<globals file="globals.xml.ftl" />
diff --git a/templates/activities/BlankActivity/template_blank_activity.png b/templates/activities/BlankActivity/template_blank_activity.png
index 729dd1c..d6ace2c 100644
--- a/templates/activities/BlankActivity/template_blank_activity.png
+++ b/templates/activities/BlankActivity/template_blank_activity.png
Binary files differ
diff --git a/templates/activities/BlankActivity/template_blank_activity_drawer.png b/templates/activities/BlankActivity/template_blank_activity_drawer.png
new file mode 100644
index 0000000..25ab6bc
--- /dev/null
+++ b/templates/activities/BlankActivity/template_blank_activity_drawer.png
Binary files differ
diff --git a/templates/activities/BlankActivity/template_blank_activity_dropdown.png b/templates/activities/BlankActivity/template_blank_activity_dropdown.png
index 09fa2cf..6204340 100644
--- a/templates/activities/BlankActivity/template_blank_activity_dropdown.png
+++ b/templates/activities/BlankActivity/template_blank_activity_dropdown.png
Binary files differ
diff --git a/templates/activities/BlankActivity/template_blank_activity_pager.png b/templates/activities/BlankActivity/template_blank_activity_pager.png
index 7cd8e0e..f0792e6 100644
--- a/templates/activities/BlankActivity/template_blank_activity_pager.png
+++ b/templates/activities/BlankActivity/template_blank_activity_pager.png
Binary files differ
diff --git a/templates/activities/BlankActivity/template_blank_activity_tabs.png b/templates/activities/BlankActivity/template_blank_activity_tabs.png
index 86a09d6..e57460b 100644
--- a/templates/activities/BlankActivity/template_blank_activity_tabs.png
+++ b/templates/activities/BlankActivity/template_blank_activity_tabs.png
Binary files differ
diff --git a/templates/activities/BlankActivity/template_blank_activity_tabs_pager.png b/templates/activities/BlankActivity/template_blank_activity_tabs_pager.png
deleted file mode 100644
index 0697a56..0000000
--- a/templates/activities/BlankActivity/template_blank_activity_tabs_pager.png
+++ /dev/null
Binary files differ
diff --git a/templates/activities/FullscreenActivity/globals.xml.ftl b/templates/activities/FullscreenActivity/globals.xml.ftl
index 6d73e17..d566fee 100644
--- a/templates/activities/FullscreenActivity/globals.xml.ftl
+++ b/templates/activities/FullscreenActivity/globals.xml.ftl
@@ -1,8 +1,8 @@
<?xml version="1.0"?>
<globals>
<global id="projectOut" value="." />
- <global id="manifestOut" value="." />
- <global id="srcOut" value="src/${slashedPackageName(packageName)}" />
- <global id="resOut" value="res" />
+ <global id="manifestOut" value="${manifestDir}" />
+ <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
+ <global id="resOut" value="${resDir}" />
<global id="simpleName" value="${activityToLayout(activityClass)}" />
</globals>
diff --git a/templates/activities/FullscreenActivity/recipe.xml.ftl b/templates/activities/FullscreenActivity/recipe.xml.ftl
index 6f121a8..834a207 100644
--- a/templates/activities/FullscreenActivity/recipe.xml.ftl
+++ b/templates/activities/FullscreenActivity/recipe.xml.ftl
@@ -1,5 +1,7 @@
<?xml version="1.0"?>
<recipe>
+ <dependency mavenUrl="com.android.support:support-v4:18.0.0" />
+
<merge from="AndroidManifest.xml.ftl"
to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
diff --git a/templates/activities/FullscreenActivity/root/AndroidManifest.xml.ftl b/templates/activities/FullscreenActivity/root/AndroidManifest.xml.ftl
index b909732..266df2f 100644
--- a/templates/activities/FullscreenActivity/root/AndroidManifest.xml.ftl
+++ b/templates/activities/FullscreenActivity/root/AndroidManifest.xml.ftl
@@ -1,7 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
<application>
- <activity android:name=".${activityClass}"
+ <activity android:name="${packageName}.${activityClass}"
<#if isNewProject>
android:label="@string/app_name"
<#else>
diff --git a/templates/activities/FullscreenActivity/root/res/layout/activity_fullscreen.xml.ftl b/templates/activities/FullscreenActivity/root/res/layout/activity_fullscreen.xml.ftl
index 39f801a..000b639 100644
--- a/templates/activities/FullscreenActivity/root/res/layout/activity_fullscreen.xml.ftl
+++ b/templates/activities/FullscreenActivity/root/res/layout/activity_fullscreen.xml.ftl
@@ -3,7 +3,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0099cc"
- tools:context=".${activityClass}">
+ tools:context="${packageName}.${activityClass}">
<!-- The primary full-screen view. This can be replaced with whatever view
is needed to present your content, e.g. VideoView, SurfaceView,
@@ -25,7 +25,7 @@
android:fitsSystemWindows="true">
<LinearLayout android:id="@+id/fullscreen_content_controls"
- style="?buttonBarStyle"
+ style="?metaButtonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
@@ -34,7 +34,7 @@
tools:ignore="UselessParent">
<Button android:id="@+id/dummy_button"
- style="?buttonBarButtonStyle"
+ style="?metaButtonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
diff --git a/templates/activities/FullscreenActivity/root/res/values-v11/styles.xml b/templates/activities/FullscreenActivity/root/res/values-v11/styles.xml
index feaeb70..f72515d 100644
--- a/templates/activities/FullscreenActivity/root/res/values-v11/styles.xml
+++ b/templates/activities/FullscreenActivity/root/res/values-v11/styles.xml
@@ -4,8 +4,8 @@
<item name="android:actionBarStyle">@style/FullscreenActionBarStyle</item>
<item name="android:windowActionBarOverlay">true</item>
<item name="android:windowBackground">@null</item>
- <item name="buttonBarStyle">?android:attr/buttonBarStyle</item>
- <item name="buttonBarButtonStyle">?android:attr/buttonBarButtonStyle</item>
+ <item name="metaButtonBarStyle">?android:attr/buttonBarStyle</item>
+ <item name="metaButtonBarButtonStyle">?android:attr/buttonBarButtonStyle</item>
</style>
<style name="FullscreenActionBarStyle" parent="android:Widget.Holo.ActionBar">
diff --git a/templates/activities/FullscreenActivity/root/res/values/attrs.xml b/templates/activities/FullscreenActivity/root/res/values/attrs.xml
index 2cf1a1a..7ce840e 100644
--- a/templates/activities/FullscreenActivity/root/res/values/attrs.xml
+++ b/templates/activities/FullscreenActivity/root/res/values/attrs.xml
@@ -5,8 +5,8 @@
?android:attr/buttonBarStyle is new as of API 11 so this is
necessary to support previous API levels. -->
<declare-styleable name="ButtonBarContainerTheme">
- <attr name="buttonBarStyle" format="reference" />
- <attr name="buttonBarButtonStyle" format="reference" />
+ <attr name="metaButtonBarStyle" format="reference" />
+ <attr name="metaButtonBarButtonStyle" format="reference" />
</declare-styleable>
</resources>
diff --git a/templates/activities/FullscreenActivity/root/res/values/styles.xml b/templates/activities/FullscreenActivity/root/res/values/styles.xml
index 48bb968..e95ba03 100644
--- a/templates/activities/FullscreenActivity/root/res/values/styles.xml
+++ b/templates/activities/FullscreenActivity/root/res/values/styles.xml
@@ -3,8 +3,8 @@
<style name="FullscreenTheme" parent="android:Theme.NoTitleBar">
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowBackground">@null</item>
- <item name="buttonBarStyle">@style/ButtonBar</item>
- <item name="buttonBarButtonStyle">@style/ButtonBarButton</item>
+ <item name="metaButtonBarStyle">@style/ButtonBar</item>
+ <item name="metaButtonBarButtonStyle">@style/ButtonBarButton</item>
</style>
<!-- Backward-compatible version of ?android:attr/buttonBarStyle -->
diff --git a/templates/activities/FullscreenActivity/root/src/app_package/FullscreenActivity.java.ftl b/templates/activities/FullscreenActivity/root/src/app_package/FullscreenActivity.java.ftl
index 4714244..9d6109b 100644
--- a/templates/activities/FullscreenActivity/root/src/app_package/FullscreenActivity.java.ftl
+++ b/templates/activities/FullscreenActivity/root/src/app_package/FullscreenActivity.java.ftl
@@ -145,19 +145,19 @@
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- // This ID represents the Home or Up button. In the case of this
- // activity, the Up button is shown. Use NavUtils to allow users
- // to navigate up one level in the application structure. For
- // more details, see the Navigation pattern on Android Design:
- //
- // http://developer.android.com/design/patterns/navigation.html#up-vs-back
- //
- // TODO: If Settings has multiple levels, Up should navigate up
- // that hierarchy.
- NavUtils.navigateUpFromSameTask(this);
- return true;
+ int id = item.getItemId();
+ if (id == android.R.id.home) {
+ // This ID represents the Home or Up button. In the case of this
+ // activity, the Up button is shown. Use NavUtils to allow users
+ // to navigate up one level in the application structure. For
+ // more details, see the Navigation pattern on Android Design:
+ //
+ // http://developer.android.com/design/patterns/navigation.html#up-vs-back
+ //
+ // TODO: If Settings has multiple levels, Up should navigate up
+ // that hierarchy.
+ NavUtils.navigateUpFromSameTask(this);
+ return true;
}
return super.onOptionsItemSelected(item);
}
diff --git a/templates/activities/FullscreenActivity/template.xml b/templates/activities/FullscreenActivity/template.xml
index d2617fb..cf568ea 100644
--- a/templates/activities/FullscreenActivity/template.xml
+++ b/templates/activities/FullscreenActivity/template.xml
@@ -1,7 +1,7 @@
<?xml version="1.0"?>
<template
- format="3"
- revision="3"
+ format="4"
+ revision="4"
name="Fullscreen Activity"
description="Creates a new activity that toggles the visibility of the system UI (status and navigation bars) and action bar upon user interaction."
minApi="4"
diff --git a/templates/activities/LoginActivity/globals.xml.ftl b/templates/activities/LoginActivity/globals.xml.ftl
index fbe8985..05c9aad 100644
--- a/templates/activities/LoginActivity/globals.xml.ftl
+++ b/templates/activities/LoginActivity/globals.xml.ftl
@@ -1,9 +1,9 @@
<?xml version="1.0"?>
<globals>
<global id="projectOut" value="." />
- <global id="manifestOut" value="." />
- <global id="srcOut" value="src/${slashedPackageName(packageName)}" />
- <global id="resOut" value="res" />
+ <global id="manifestOut" value="${manifestDir}" />
+ <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
+ <global id="resOut" value="${resDir}" />
<global id="menuName" value="${classToResource(activityClass)}" />
<global id="simpleName" value="${activityToLayout(activityClass)}" />
</globals>
diff --git a/templates/activities/LoginActivity/recipe.xml.ftl b/templates/activities/LoginActivity/recipe.xml.ftl
index 94f93d6..bb232af 100644
--- a/templates/activities/LoginActivity/recipe.xml.ftl
+++ b/templates/activities/LoginActivity/recipe.xml.ftl
@@ -1,5 +1,7 @@
<?xml version="1.0"?>
<recipe>
+ <dependency mavenUrl="com.android.support:support-v4:18.0.0" />
+
<merge from="AndroidManifest.xml.ftl"
to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
diff --git a/templates/activities/LoginActivity/root/AndroidManifest.xml.ftl b/templates/activities/LoginActivity/root/AndroidManifest.xml.ftl
index c5f02d2..4f35c79 100644
--- a/templates/activities/LoginActivity/root/AndroidManifest.xml.ftl
+++ b/templates/activities/LoginActivity/root/AndroidManifest.xml.ftl
@@ -1,7 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
<application>
- <activity android:name=".${activityClass}"
+ <activity android:name="${packageName}.${activityClass}"
<#if isNewProject>
android:label="@string/app_name"
<#else>
diff --git a/templates/activities/LoginActivity/root/res/layout/activity_login.xml.ftl b/templates/activities/LoginActivity/root/res/layout/activity_login.xml.ftl
index 9434e5b..9f0fb73 100644
--- a/templates/activities/LoginActivity/root/res/layout/activity_login.xml.ftl
+++ b/templates/activities/LoginActivity/root/res/layout/activity_login.xml.ftl
@@ -1,6 +1,6 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
- tools:context=".${activityClass}">
+ tools:context="${packageName}.${activityClass}">
<!-- Login progress -->
<LinearLayout android:id="@+id/login_status"
diff --git a/templates/activities/LoginActivity/root/res/menu/activity_login.xml b/templates/activities/LoginActivity/root/res/menu/activity_login.xml
index 2965794..f39c9a3 100644
--- a/templates/activities/LoginActivity/root/res/menu/activity_login.xml
+++ b/templates/activities/LoginActivity/root/res/menu/activity_login.xml
@@ -1,4 +1,6 @@
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:context="${packageName}.${activityClass}">
<item android:id="@+id/action_forgot_password"
android:title="@string/action_forgot_password"
android:showAsAction="never" />
diff --git a/templates/activities/LoginActivity/root/src/app_package/LoginActivity.java.ftl b/templates/activities/LoginActivity/root/src/app_package/LoginActivity.java.ftl
index 8defdc7..4f28c21 100644
--- a/templates/activities/LoginActivity/root/src/app_package/LoginActivity.java.ftl
+++ b/templates/activities/LoginActivity/root/src/app_package/LoginActivity.java.ftl
@@ -106,19 +106,19 @@
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- // This ID represents the Home or Up button. In the case of this
- // activity, the Up button is shown. Use NavUtils to allow users
- // to navigate up one level in the application structure. For
- // more details, see the Navigation pattern on Android Design:
- //
- // http://developer.android.com/design/patterns/navigation.html#up-vs-back
- //
- // TODO: If Settings has multiple levels, Up should navigate up
- // that hierarchy.
- NavUtils.navigateUpFromSameTask(this);
- return true;
+ int id = item.getItemId();
+ if (id == android.R.id.home) {
+ // This ID represents the Home or Up button. In the case of this
+ // activity, the Up button is shown. Use NavUtils to allow users
+ // to navigate up one level in the application structure. For
+ // more details, see the Navigation pattern on Android Design:
+ //
+ // http://developer.android.com/design/patterns/navigation.html#up-vs-back
+ //
+ // TODO: If Settings has multiple levels, Up should navigate up
+ // that hierarchy.
+ NavUtils.navigateUpFromSameTask(this);
+ return true;
}
return super.onOptionsItemSelected(item);
}
diff --git a/templates/activities/LoginActivity/template.xml b/templates/activities/LoginActivity/template.xml
index ccfc7ad..576c633 100644
--- a/templates/activities/LoginActivity/template.xml
+++ b/templates/activities/LoginActivity/template.xml
@@ -1,11 +1,12 @@
<?xml version="1.0"?>
<template
- format="3"
- revision="3"
+ format="4"
+ revision="4"
name="Login Activity"
description="Creates a new login activity, allowing users to enter an email address and password to log in to or register with your application."
minApi="3"
minBuildApi="13">
+
<dependency name="android-support-v4" revision="8" />
<category value="Activities" />
diff --git a/templates/activities/MasterDetailFlow/globals.xml.ftl b/templates/activities/MasterDetailFlow/globals.xml.ftl
index 415d60e..0d23f55 100644
--- a/templates/activities/MasterDetailFlow/globals.xml.ftl
+++ b/templates/activities/MasterDetailFlow/globals.xml.ftl
@@ -1,9 +1,9 @@
<?xml version="1.0"?>
<globals>
<global id="projectOut" value="." />
- <global id="manifestOut" value="." />
- <global id="srcOut" value="src/${slashedPackageName(packageName)}" />
- <global id="resOut" value="res" />
+ <global id="manifestOut" value="${manifestDir}" />
+ <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
+ <global id="resOut" value="${resDir}" />
<global id="CollectionName" value="${extractLetters(objectKind)}List" />
<global id="collection_name" value="${extractLetters(objectKind?lower_case)}_list" />
<global id="DetailName" value="${extractLetters(objectKind)}Detail" />
diff --git a/templates/activities/MasterDetailFlow/recipe.xml.ftl b/templates/activities/MasterDetailFlow/recipe.xml.ftl
index 4b39f74..0c68f35 100644
--- a/templates/activities/MasterDetailFlow/recipe.xml.ftl
+++ b/templates/activities/MasterDetailFlow/recipe.xml.ftl
@@ -1,5 +1,7 @@
<?xml version="1.0"?>
<recipe>
+ <dependency mavenUrl="com.android.support:support-v4:18.0.0" />
+
<merge from="AndroidManifest.xml.ftl"
to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
@@ -29,4 +31,7 @@
to="${escapeXmlAttribute(srcOut)}/${CollectionName}Fragment.java" />
<instantiate from="src/app_package/dummy/DummyContent.java.ftl"
to="${escapeXmlAttribute(srcOut)}/dummy/DummyContent.java" />
+
+ <open file="${escapeXmlAttribute(srcOut)}/${DetailName}Fragment.java" />
+ <open file="${escapeXmlAttribute(resOut)}/layout/fragment_${detail_name}.xml" />
</recipe>
diff --git a/templates/activities/MasterDetailFlow/root/AndroidManifest.xml.ftl b/templates/activities/MasterDetailFlow/root/AndroidManifest.xml.ftl
index 4707bd6..34d0402 100644
--- a/templates/activities/MasterDetailFlow/root/AndroidManifest.xml.ftl
+++ b/templates/activities/MasterDetailFlow/root/AndroidManifest.xml.ftl
@@ -1,7 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
- <activity android:name=".${CollectionName}Activity"
+ <activity android:name="${packageName}.${CollectionName}Activity"
<#if isNewProject>
android:label="@string/app_name"
<#else>
@@ -20,11 +20,11 @@
</#if>
</activity>
- <activity android:name=".${DetailName}Activity"
+ <activity android:name="${packageName}.${DetailName}Activity"
android:label="@string/title_${detail_name}"
<#if buildApi gte 16>android:parentActivityName=".${CollectionName}Activity"</#if>>
<meta-data android:name="android.support.PARENT_ACTIVITY"
- android:value=".${CollectionName}Activity" />
+ android:value="${packageName}.${CollectionName}Activity" />
</activity>
</application>
diff --git a/templates/activities/MasterDetailFlow/root/res/layout/activity_content_detail.xml.ftl b/templates/activities/MasterDetailFlow/root/res/layout/activity_content_detail.xml.ftl
index ddc1ecc..02bf4f6 100644
--- a/templates/activities/MasterDetailFlow/root/res/layout/activity_content_detail.xml.ftl
+++ b/templates/activities/MasterDetailFlow/root/res/layout/activity_content_detail.xml.ftl
@@ -3,5 +3,5 @@
android:id="@+id/${detail_name}_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
- tools:context=".${DetailName}Activity"
+ tools:context="${packageName}.${DetailName}Activity"
tools:ignore="MergeRootFrame" />
diff --git a/templates/activities/MasterDetailFlow/root/res/layout/activity_content_list.xml.ftl b/templates/activities/MasterDetailFlow/root/res/layout/activity_content_list.xml.ftl
index 065cd42..e51a98e 100644
--- a/templates/activities/MasterDetailFlow/root/res/layout/activity_content_list.xml.ftl
+++ b/templates/activities/MasterDetailFlow/root/res/layout/activity_content_list.xml.ftl
@@ -6,5 +6,5 @@
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
- tools:context=".${CollectionName}Activity"
+ tools:context="${packageName}.${CollectionName}Activity"
tools:layout="@android:layout/list_content" />
diff --git a/templates/activities/MasterDetailFlow/root/res/layout/activity_content_twopane.xml.ftl b/templates/activities/MasterDetailFlow/root/res/layout/activity_content_twopane.xml.ftl
index 575e9e6..1f2bd19 100644
--- a/templates/activities/MasterDetailFlow/root/res/layout/activity_content_twopane.xml.ftl
+++ b/templates/activities/MasterDetailFlow/root/res/layout/activity_content_twopane.xml.ftl
@@ -8,7 +8,7 @@
android:divider="?android:attr/dividerHorizontal"
android:orientation="horizontal"
android:showDividers="middle"
- tools:context=".${CollectionName}Activity">
+ tools:context="${packageName}.${CollectionName}Activity">
<!--
This layout is a two-pane layout for the ${objectKindPlural}
diff --git a/templates/activities/MasterDetailFlow/root/res/layout/fragment_content_detail.xml.ftl b/templates/activities/MasterDetailFlow/root/res/layout/fragment_content_detail.xml.ftl
index 808fc31..f685145 100644
--- a/templates/activities/MasterDetailFlow/root/res/layout/fragment_content_detail.xml.ftl
+++ b/templates/activities/MasterDetailFlow/root/res/layout/fragment_content_detail.xml.ftl
@@ -6,4 +6,4 @@
android:layout_height="match_parent"
android:padding="16dp"
android:textIsSelectable="true"
- tools:context=".${DetailName}Fragment" />
+ tools:context="${packageName}.${DetailName}Fragment" />
diff --git a/templates/activities/MasterDetailFlow/root/src/app_package/ContentDetailActivity.java.ftl b/templates/activities/MasterDetailFlow/root/src/app_package/ContentDetailActivity.java.ftl
index 2cc6054..79f0e90 100644
--- a/templates/activities/MasterDetailFlow/root/src/app_package/ContentDetailActivity.java.ftl
+++ b/templates/activities/MasterDetailFlow/root/src/app_package/ContentDetailActivity.java.ftl
@@ -50,17 +50,17 @@
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- // This ID represents the Home or Up button. In the case of this
- // activity, the Up button is shown. Use NavUtils to allow users
- // to navigate up one level in the application structure. For
- // more details, see the Navigation pattern on Android Design:
- //
- // http://developer.android.com/design/patterns/navigation.html#up-vs-back
- //
- NavUtils.navigateUpTo(this, new Intent(this, ${CollectionName}Activity.class));
- return true;
+ int id = item.getItemId();
+ if (id == android.R.id.home) {
+ // This ID represents the Home or Up button. In the case of this
+ // activity, the Up button is shown. Use NavUtils to allow users
+ // to navigate up one level in the application structure. For
+ // more details, see the Navigation pattern on Android Design:
+ //
+ // http://developer.android.com/design/patterns/navigation.html#up-vs-back
+ //
+ NavUtils.navigateUpTo(this, new Intent(this, ${CollectionName}Activity.class));
+ return true;
}
return super.onOptionsItemSelected(item);
}
diff --git a/templates/activities/MasterDetailFlow/root/src/app_package/ContentListActivity.java.ftl b/templates/activities/MasterDetailFlow/root/src/app_package/ContentListActivity.java.ftl
index ae73f7d3..fe02fe9 100644
--- a/templates/activities/MasterDetailFlow/root/src/app_package/ContentListActivity.java.ftl
+++ b/templates/activities/MasterDetailFlow/root/src/app_package/ContentListActivity.java.ftl
@@ -60,17 +60,17 @@
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- // This ID represents the Home or Up button. In the case of this
- // activity, the Up button is shown. Use NavUtils to allow users
- // to navigate up one level in the application structure. For
- // more details, see the Navigation pattern on Android Design:
- //
- // http://developer.android.com/design/patterns/navigation.html#up-vs-back
- //
- NavUtils.navigateUpFromSameTask(this);
- return true;
+ int id = item.getItemId();
+ if (id == android.R.id.home) {
+ // This ID represents the Home or Up button. In the case of this
+ // activity, the Up button is shown. Use NavUtils to allow users
+ // to navigate up one level in the application structure. For
+ // more details, see the Navigation pattern on Android Design:
+ //
+ // http://developer.android.com/design/patterns/navigation.html#up-vs-back
+ //
+ NavUtils.navigateUpFromSameTask(this);
+ return true;
}
return super.onOptionsItemSelected(item);
}
diff --git a/templates/activities/MasterDetailFlow/template.xml b/templates/activities/MasterDetailFlow/template.xml
index 47de074..c2e2b6e 100644
--- a/templates/activities/MasterDetailFlow/template.xml
+++ b/templates/activities/MasterDetailFlow/template.xml
@@ -1,10 +1,11 @@
<?xml version="1.0"?>
<template
- format="3"
- revision="3"
+ format="4"
+ revision="4"
name="Master/Detail Flow"
minApi="11"
description="Creates a new master/detail flow, allowing users to view a collection of objects as well as details for each object. This flow is presented using two columns on tablet-size screens and one column on handsets and smaller screens. This template creates two activities, a master fragment, and a detail fragment.">
+
<dependency name="android-support-v4" revision="8" />
<thumbs>
diff --git a/templates/activities/SettingsActivity/globals.xml.ftl b/templates/activities/SettingsActivity/globals.xml.ftl
index 6d73e17..d566fee 100644
--- a/templates/activities/SettingsActivity/globals.xml.ftl
+++ b/templates/activities/SettingsActivity/globals.xml.ftl
@@ -1,8 +1,8 @@
<?xml version="1.0"?>
<globals>
<global id="projectOut" value="." />
- <global id="manifestOut" value="." />
- <global id="srcOut" value="src/${slashedPackageName(packageName)}" />
- <global id="resOut" value="res" />
+ <global id="manifestOut" value="${manifestDir}" />
+ <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
+ <global id="resOut" value="${resDir}" />
<global id="simpleName" value="${activityToLayout(activityClass)}" />
</globals>
diff --git a/templates/activities/SettingsActivity/recipe.xml.ftl b/templates/activities/SettingsActivity/recipe.xml.ftl
index b6d46ce..20a6b2e 100644
--- a/templates/activities/SettingsActivity/recipe.xml.ftl
+++ b/templates/activities/SettingsActivity/recipe.xml.ftl
@@ -1,5 +1,7 @@
<?xml version="1.0"?>
<recipe>
+ <dependency mavenUrl="com.android.support:support-v4:18.0.0" />
+
<merge from="AndroidManifest.xml.ftl"
to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
diff --git a/templates/activities/SettingsActivity/root/AndroidManifest.xml.ftl b/templates/activities/SettingsActivity/root/AndroidManifest.xml.ftl
index 9f78fcf..a638dd6 100644
--- a/templates/activities/SettingsActivity/root/AndroidManifest.xml.ftl
+++ b/templates/activities/SettingsActivity/root/AndroidManifest.xml.ftl
@@ -1,7 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
<application>
- <activity android:name=".${activityClass}"
+ <activity android:name="${packageName}.${activityClass}"
<#if isNewProject>
android:label="@string/app_name"
<#else>
diff --git a/templates/activities/SettingsActivity/root/src/app_package/SettingsActivity.java.ftl b/templates/activities/SettingsActivity/root/src/app_package/SettingsActivity.java.ftl
index bf67aca..bf4610f 100644
--- a/templates/activities/SettingsActivity/root/src/app_package/SettingsActivity.java.ftl
+++ b/templates/activities/SettingsActivity/root/src/app_package/SettingsActivity.java.ftl
@@ -63,19 +63,19 @@
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- // This ID represents the Home or Up button. In the case of this
- // activity, the Up button is shown. Use NavUtils to allow users
- // to navigate up one level in the application structure. For
- // more details, see the Navigation pattern on Android Design:
- //
- // http://developer.android.com/design/patterns/navigation.html#up-vs-back
- //
- // TODO: If Settings has multiple levels, Up should navigate up
- // that hierarchy.
- NavUtils.navigateUpFromSameTask(this);
- return true;
+ int id = item.getItemId();
+ if (id == android.R.id.home) {
+ // This ID represents the Home or Up button. In the case of this
+ // activity, the Up button is shown. Use NavUtils to allow users
+ // to navigate up one level in the application structure. For
+ // more details, see the Navigation pattern on Android Design:
+ //
+ // http://developer.android.com/design/patterns/navigation.html#up-vs-back
+ //
+ // TODO: If Settings has multiple levels, Up should navigate up
+ // that hierarchy.
+ NavUtils.navigateUpFromSameTask(this);
+ return true;
}
return super.onOptionsItemSelected(item);
}
diff --git a/templates/activities/SettingsActivity/template.xml b/templates/activities/SettingsActivity/template.xml
index 21956d1..33f9413 100644
--- a/templates/activities/SettingsActivity/template.xml
+++ b/templates/activities/SettingsActivity/template.xml
@@ -1,11 +1,12 @@
<?xml version="1.0"?>
<template
- format="3"
- revision="3"
+ format="4"
+ revision="4"
name="Settings Activity"
description="Creates a new application settings activity that presents alternative layouts on handset and tablet-size screens."
minApi="4"
minBuildApi="11">
+
<dependency name="android-support-v4" revision="8" />
<category value="Activities" />
diff --git a/templates/gradle-projects/NewAndroidApplication/globals.xml.ftl b/templates/gradle-projects/NewAndroidApplication/globals.xml.ftl
deleted file mode 100644
index 9870768..0000000
--- a/templates/gradle-projects/NewAndroidApplication/globals.xml.ftl
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0"?>
-<globals>
- <global id="topOut" value="." />
- <global id="projectOut" value="." />
- <global id="manifestOut" value="." />
- <global id="srcOut" value="src/${slashedPackageName(packageName)}" />
- <global id="resOut" value="res" />
- <global id="mavenUrl" value="mavenCentral" />
- <global id="buildToolsVersion" value="${buildApi}" />
- <global id="gradlePluginVersion" value="1.0.+" />
- <global id="v4SupportLibraryVersion" value="13.0.+" />
-</globals>
diff --git a/templates/gradle-projects/NewAndroidApplication/recipe.xml.ftl b/templates/gradle-projects/NewAndroidApplication/recipe.xml.ftl
deleted file mode 100644
index 429bab3..0000000
--- a/templates/gradle-projects/NewAndroidApplication/recipe.xml.ftl
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <merge from="settings.gradle.ftl"
- to="${escapeXmlAttribute(topOut)}/settings.gradle" />
- <instantiate from="build.gradle.ftl"
- to="${escapeXmlAttribute(projectOut)}/build.gradle" />
- <instantiate from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
-<#if copyIcons>
- <copy from="res/drawable-hdpi"
- to="${escapeXmlAttribute(resOut)}/drawable-hdpi" />
- <copy from="res/drawable-mdpi"
- to="${escapeXmlAttribute(resOut)}/drawable-mdpi" />
- <copy from="res/drawable-xhdpi"
- to="${escapeXmlAttribute(resOut)}/drawable-xhdpi" />
-</#if>
- <instantiate from="res/values/styles.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/styles.xml" />
-<#if buildApi gte 11 && baseTheme != "none">
- <instantiate from="res/values-v11/styles_hc.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values-v11/styles.xml" />
-</#if>
-<#if buildApi gte 14 && baseTheme?contains("darkactionbar")>
- <copy from="res/values-v14/styles_ics.xml"
- to="${escapeXmlAttribute(resOut)}/values-v14/styles.xml" />
-</#if>
-
- <instantiate from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-</recipe>
diff --git a/templates/gradle-projects/NewAndroidApplication/root/AndroidManifest.xml.ftl b/templates/gradle-projects/NewAndroidApplication/root/AndroidManifest.xml.ftl
deleted file mode 100644
index 390a9da..0000000
--- a/templates/gradle-projects/NewAndroidApplication/root/AndroidManifest.xml.ftl
+++ /dev/null
@@ -1,15 +0,0 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="${packageName}"
- android:versionCode="1"
- android:versionName="1.0">
-
- <uses-sdk android:minSdkVersion="${minApi}" <#if buildApi gte 4>android:targetSdkVersion="${targetApi}" </#if>/>
-
- <application <#if minApiLevel gte 4 && buildApi gte 4>android:allowBackup="true"</#if>
- android:label="@string/app_name"
- android:icon="@drawable/ic_launcher"<#if baseTheme != "none">
- android:theme="@style/AppTheme"</#if>>
-
- </application>
-
-</manifest>
diff --git a/templates/gradle-projects/NewAndroidApplication/root/build.gradle.ftl b/templates/gradle-projects/NewAndroidApplication/root/build.gradle.ftl
deleted file mode 100644
index 5196c75..0000000
--- a/templates/gradle-projects/NewAndroidApplication/root/build.gradle.ftl
+++ /dev/null
@@ -1,35 +0,0 @@
-buildscript {
- repositories {
-<#if mavenUrl == "mavenCentral">
- mavenCentral()
-<#else>
- maven { url '${mavenUrl}' }
-</#if>
- }
- dependencies {
- classpath 'com.android.tools.build:gradle:${gradlePluginVersion}'
- }
-}
-apply plugin: 'android'
-
-repositories {
-<#if mavenUrl == "mavenCentral">
- mavenCentral()
-<#else>
- maven { url '${mavenUrl}' }
-</#if>
-}
-
-android {
- compileSdkVersion ${buildApi}
- buildToolsVersion "${buildToolsVersion}"
-
- defaultConfig {
- minSdkVersion ${minApi}
- targetSdkVersion ${targetApi}
- }
-}
-
-dependencies {
- compile 'com.android.support:support-v4:${v4SupportLibraryVersion}'
-}
diff --git a/templates/gradle-projects/NewAndroidApplication/root/res/drawable-hdpi/ic_launcher.png b/templates/gradle-projects/NewAndroidApplication/root/res/drawable-hdpi/ic_launcher.png
deleted file mode 100755
index 96a442e..0000000
--- a/templates/gradle-projects/NewAndroidApplication/root/res/drawable-hdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/templates/gradle-projects/NewAndroidApplication/root/res/drawable-mdpi/ic_launcher.png b/templates/gradle-projects/NewAndroidApplication/root/res/drawable-mdpi/ic_launcher.png
deleted file mode 100755
index 359047d..0000000
--- a/templates/gradle-projects/NewAndroidApplication/root/res/drawable-mdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/templates/gradle-projects/NewAndroidApplication/root/res/drawable-xhdpi/ic_launcher.png b/templates/gradle-projects/NewAndroidApplication/root/res/drawable-xhdpi/ic_launcher.png
deleted file mode 100755
index 71c6d76..0000000
--- a/templates/gradle-projects/NewAndroidApplication/root/res/drawable-xhdpi/ic_launcher.png
+++ /dev/null
Binary files differ
diff --git a/templates/gradle-projects/NewAndroidApplication/root/res/values-v11/styles_hc.xml.ftl b/templates/gradle-projects/NewAndroidApplication/root/res/values-v11/styles_hc.xml.ftl
deleted file mode 100644
index f8993c3..0000000
--- a/templates/gradle-projects/NewAndroidApplication/root/res/values-v11/styles_hc.xml.ftl
+++ /dev/null
@@ -1,11 +0,0 @@
-<resources>
-
- <!--
- Base application theme for API 11+. This theme completely replaces
- AppBaseTheme from res/values/styles.xml on API 11+ devices.
- -->
- <style name="AppBaseTheme" parent="android:Theme.Holo<#if baseTheme?contains("light")>.Light</#if>">
- <!-- API 11 theme customizations can go here. -->
- </style>
-
-</resources>
diff --git a/templates/gradle-projects/NewAndroidApplication/root/res/values-v14/styles_ics.xml b/templates/gradle-projects/NewAndroidApplication/root/res/values-v14/styles_ics.xml
deleted file mode 100644
index a91fd03..0000000
--- a/templates/gradle-projects/NewAndroidApplication/root/res/values-v14/styles_ics.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<resources>
-
- <!--
- Base application theme for API 14+. This theme completely replaces
- AppBaseTheme from BOTH res/values/styles.xml and
- res/values-v11/styles.xml on API 14+ devices.
- -->
- <style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar">
- <!-- API 14 theme customizations can go here. -->
- </style>
-
-</resources>
diff --git a/templates/gradle-projects/NewAndroidApplication/root/res/values/strings.xml.ftl b/templates/gradle-projects/NewAndroidApplication/root/res/values/strings.xml.ftl
deleted file mode 100644
index ee03444..0000000
--- a/templates/gradle-projects/NewAndroidApplication/root/res/values/strings.xml.ftl
+++ /dev/null
@@ -1,3 +0,0 @@
-<resources>
- <string name="app_name">${escapeXmlString(appTitle)}</string>
-</resources>
diff --git a/templates/gradle-projects/NewAndroidApplication/root/res/values/styles.xml.ftl b/templates/gradle-projects/NewAndroidApplication/root/res/values/styles.xml.ftl
deleted file mode 100644
index 30fe5b5..0000000
--- a/templates/gradle-projects/NewAndroidApplication/root/res/values/styles.xml.ftl
+++ /dev/null
@@ -1,20 +0,0 @@
-<resources>
-
- <!--
- Base application theme, dependent on API level. This theme is replaced
- by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
- -->
- <style name="AppBaseTheme" parent="android:Theme<#if baseTheme?contains("light")>.Light</#if>">
- <!--
- Theme customizations available in newer API levels can go in
- res/values-vXX/styles.xml, while customizations related to
- backward-compatibility can go here.
- -->
- </style>
-
- <!-- Application theme. -->
- <style name="AppTheme" parent="AppBaseTheme">
- <!-- All customizations that are NOT specific to a particular API-level can go here. -->
- </style>
-
-</resources>
diff --git a/templates/gradle-projects/NewAndroidApplication/root/settings.gradle.ftl b/templates/gradle-projects/NewAndroidApplication/root/settings.gradle.ftl
deleted file mode 100644
index b12004b..0000000
--- a/templates/gradle-projects/NewAndroidApplication/root/settings.gradle.ftl
+++ /dev/null
@@ -1 +0,0 @@
-include ':${projectName}'
diff --git a/templates/gradle-projects/NewAndroidApplication/template.xml b/templates/gradle-projects/NewAndroidApplication/template.xml
deleted file mode 100644
index 0d9b234..0000000
--- a/templates/gradle-projects/NewAndroidApplication/template.xml
+++ /dev/null
@@ -1,82 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="1"
- revision="2"
- name="Android Application"
- description="Creates a new Android application.">
- <dependency name="android-support-v4" revision="8" />
-
- <thumbs>
- <thumb>template_new_project.png</thumb>
- </thumbs>
-
- <category value="Applications" />
-
- <parameter
- id="packageName"
- name="Package name"
- type="string"
- constraints="package|nonempty"
- default="com.mycompany.myapp" />
-
- <parameter
- id="appTitle"
- name="Application title"
- type="string"
- constraints="nonempty"
- default="My Application" />
-
- <parameter
- id="baseTheme"
- name="Base Theme"
- type="enum"
- default="holo_light_darkactionbar"
- help="The base user interface theme for the application">
- <option id="none">None</option>
- <option id="holo_dark" minBuildApi="11">Holo Dark</option>
- <option id="holo_light" minBuildApi="11">Holo Light</option>
- <option id="holo_light_darkactionbar" minBuildApi="14" default="true">Holo Light with Dark Action Bar</option>
- </parameter>
-
- <parameter
- id="minApi"
- name="Minimum API level"
- type="string"
- constraints="apilevel"
- default="7" />
-
- <!--
- Usually the same as minApi, but when minApi is a code name this will be the corresponding
- API level
- -->
- <parameter
- id="minApiLevel"
- name="Minimum API level"
- type="string"
- constraints="apilevel"
- default="7" />
-
- <parameter
- id="targetApi"
- name="Target API level"
- type="string"
- constraints="apilevel"
- default="16" />
-
- <parameter
- id="buildApi"
- name="Build API level"
- type="string"
- constraints="apilevel"
- default="16" />
-
- <parameter
- id="copyIcons"
- name="Include launcher icons"
- type="boolean"
- default="true" />
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/templates/gradle-projects/NewAndroidApplication/template_new_project.png b/templates/gradle-projects/NewAndroidApplication/template_new_project.png
deleted file mode 100644
index 92e8556..0000000
--- a/templates/gradle-projects/NewAndroidApplication/template_new_project.png
+++ /dev/null
Binary files differ
diff --git a/templates/gradle-projects/NewAndroidLibrary/globals.xml.ftl b/templates/gradle-projects/NewAndroidLibrary/globals.xml.ftl
deleted file mode 100644
index 9870768..0000000
--- a/templates/gradle-projects/NewAndroidLibrary/globals.xml.ftl
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0"?>
-<globals>
- <global id="topOut" value="." />
- <global id="projectOut" value="." />
- <global id="manifestOut" value="." />
- <global id="srcOut" value="src/${slashedPackageName(packageName)}" />
- <global id="resOut" value="res" />
- <global id="mavenUrl" value="mavenCentral" />
- <global id="buildToolsVersion" value="${buildApi}" />
- <global id="gradlePluginVersion" value="1.0.+" />
- <global id="v4SupportLibraryVersion" value="13.0.+" />
-</globals>
diff --git a/templates/gradle-projects/NewAndroidLibrary/recipe.xml.ftl b/templates/gradle-projects/NewAndroidLibrary/recipe.xml.ftl
deleted file mode 100644
index 429bab3..0000000
--- a/templates/gradle-projects/NewAndroidLibrary/recipe.xml.ftl
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0"?>
-<recipe>
- <merge from="settings.gradle.ftl"
- to="${escapeXmlAttribute(topOut)}/settings.gradle" />
- <instantiate from="build.gradle.ftl"
- to="${escapeXmlAttribute(projectOut)}/build.gradle" />
- <instantiate from="AndroidManifest.xml.ftl"
- to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
-
-<#if copyIcons>
- <copy from="res/drawable-hdpi"
- to="${escapeXmlAttribute(resOut)}/drawable-hdpi" />
- <copy from="res/drawable-mdpi"
- to="${escapeXmlAttribute(resOut)}/drawable-mdpi" />
- <copy from="res/drawable-xhdpi"
- to="${escapeXmlAttribute(resOut)}/drawable-xhdpi" />
-</#if>
- <instantiate from="res/values/styles.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/styles.xml" />
-<#if buildApi gte 11 && baseTheme != "none">
- <instantiate from="res/values-v11/styles_hc.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values-v11/styles.xml" />
-</#if>
-<#if buildApi gte 14 && baseTheme?contains("darkactionbar")>
- <copy from="res/values-v14/styles_ics.xml"
- to="${escapeXmlAttribute(resOut)}/values-v14/styles.xml" />
-</#if>
-
- <instantiate from="res/values/strings.xml.ftl"
- to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
-</recipe>
diff --git a/templates/gradle-projects/NewAndroidLibrary/root/AndroidManifest.xml.ftl b/templates/gradle-projects/NewAndroidLibrary/root/AndroidManifest.xml.ftl
deleted file mode 100644
index 390a9da..0000000
--- a/templates/gradle-projects/NewAndroidLibrary/root/AndroidManifest.xml.ftl
+++ /dev/null
@@ -1,15 +0,0 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="${packageName}"
- android:versionCode="1"
- android:versionName="1.0">
-
- <uses-sdk android:minSdkVersion="${minApi}" <#if buildApi gte 4>android:targetSdkVersion="${targetApi}" </#if>/>
-
- <application <#if minApiLevel gte 4 && buildApi gte 4>android:allowBackup="true"</#if>
- android:label="@string/app_name"
- android:icon="@drawable/ic_launcher"<#if baseTheme != "none">
- android:theme="@style/AppTheme"</#if>>
-
- </application>
-
-</manifest>
diff --git a/templates/gradle-projects/NewAndroidLibrary/root/build.gradle.ftl b/templates/gradle-projects/NewAndroidLibrary/root/build.gradle.ftl
deleted file mode 100644
index 5c6994e..0000000
--- a/templates/gradle-projects/NewAndroidLibrary/root/build.gradle.ftl
+++ /dev/null
@@ -1,35 +0,0 @@
-buildscript {
- repositories {
-<#if mavenUrl == "mavenCentral">
- mavenCentral()
-<#else>
- maven { url '${mavenUrl}' }
-</#if>
- }
- dependencies {
- classpath 'com.android.tools.build:gradle:${gradlePluginVersion}'
- }
-}
-apply plugin: 'android-library'
-
-repositories {
-<#if mavenUrl == "mavenCentral">
- mavenCentral()
-<#else>
- maven { url '${mavenUrl}' }
-</#if>
-}
-
-android {
- compileSdkVersion ${buildApi}
- buildToolsVersion "${buildToolsVersion}"
-
- defaultConfig {
- minSdkVersion ${minApi}
- targetSdkVersion ${targetApi}
- }
-}
-
-dependencies {
- compile 'com.android.support:support-v4:${v4SupportLibraryVersion}'
-}
diff --git a/templates/gradle-projects/NewAndroidLibrary/root/res/values-v11/styles_hc.xml.ftl b/templates/gradle-projects/NewAndroidLibrary/root/res/values-v11/styles_hc.xml.ftl
deleted file mode 100644
index f8993c3..0000000
--- a/templates/gradle-projects/NewAndroidLibrary/root/res/values-v11/styles_hc.xml.ftl
+++ /dev/null
@@ -1,11 +0,0 @@
-<resources>
-
- <!--
- Base application theme for API 11+. This theme completely replaces
- AppBaseTheme from res/values/styles.xml on API 11+ devices.
- -->
- <style name="AppBaseTheme" parent="android:Theme.Holo<#if baseTheme?contains("light")>.Light</#if>">
- <!-- API 11 theme customizations can go here. -->
- </style>
-
-</resources>
diff --git a/templates/gradle-projects/NewAndroidLibrary/root/res/values-v14/styles_ics.xml b/templates/gradle-projects/NewAndroidLibrary/root/res/values-v14/styles_ics.xml
deleted file mode 100644
index a91fd03..0000000
--- a/templates/gradle-projects/NewAndroidLibrary/root/res/values-v14/styles_ics.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<resources>
-
- <!--
- Base application theme for API 14+. This theme completely replaces
- AppBaseTheme from BOTH res/values/styles.xml and
- res/values-v11/styles.xml on API 14+ devices.
- -->
- <style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar">
- <!-- API 14 theme customizations can go here. -->
- </style>
-
-</resources>
diff --git a/templates/gradle-projects/NewAndroidLibrary/root/res/values/styles.xml.ftl b/templates/gradle-projects/NewAndroidLibrary/root/res/values/styles.xml.ftl
deleted file mode 100644
index 30fe5b5..0000000
--- a/templates/gradle-projects/NewAndroidLibrary/root/res/values/styles.xml.ftl
+++ /dev/null
@@ -1,20 +0,0 @@
-<resources>
-
- <!--
- Base application theme, dependent on API level. This theme is replaced
- by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
- -->
- <style name="AppBaseTheme" parent="android:Theme<#if baseTheme?contains("light")>.Light</#if>">
- <!--
- Theme customizations available in newer API levels can go in
- res/values-vXX/styles.xml, while customizations related to
- backward-compatibility can go here.
- -->
- </style>
-
- <!-- Application theme. -->
- <style name="AppTheme" parent="AppBaseTheme">
- <!-- All customizations that are NOT specific to a particular API-level can go here. -->
- </style>
-
-</resources>
diff --git a/templates/gradle-projects/NewAndroidLibrary/template.xml b/templates/gradle-projects/NewAndroidLibrary/template.xml
deleted file mode 100644
index 9d13db5..0000000
--- a/templates/gradle-projects/NewAndroidLibrary/template.xml
+++ /dev/null
@@ -1,82 +0,0 @@
-<?xml version="1.0"?>
-<template
- format="1"
- revision="2"
- name="Android Library"
- description="Creates a new Android library.">
- <dependency name="android-support-v4" revision="8" />
-
- <thumbs>
- <thumb>template_new_project.png</thumb>
- </thumbs>
-
- <category value="Applications" />
-
- <parameter
- id="packageName"
- name="Package name"
- type="string"
- constraints="package|nonempty"
- default="com.mycompany.myapp" />
-
- <parameter
- id="appTitle"
- name="Library title"
- type="string"
- constraints="nonempty"
- default="My Library"/>
-
- <parameter
- id="baseTheme"
- name="Base Theme"
- type="enum"
- default="holo_light_darkactionbar"
- help="The base user interface theme for the library">
- <option id="none">None</option>
- <option id="holo_dark" minBuildApi="11">Holo Dark</option>
- <option id="holo_light" minBuildApi="11">Holo Light</option>
- <option id="holo_light_darkactionbar" minBuildApi="14" default="true">Holo Light with Dark Action Bar</option>
- </parameter>
-
- <parameter
- id="minApi"
- name="Minimum API level"
- type="string"
- constraints="apilevel"
- default="7" />
-
- <!--
- Usually the same as minApi, but when minApi is a code name this will be the corresponding
- API level
- -->
- <parameter
- id="minApiLevel"
- name="Minimum API level"
- type="string"
- constraints="apilevel"
- default="7" />
-
- <parameter
- id="targetApi"
- name="Target API level"
- type="string"
- constraints="apilevel"
- default="16" />
-
- <parameter
- id="buildApi"
- name="Build API level"
- type="string"
- constraints="apilevel"
- default="16" />
-
- <parameter
- id="copyIcons"
- name="Include launcher icons"
- type="boolean"
- default="true" />
-
- <globals file="globals.xml.ftl" />
- <execute file="recipe.xml.ftl" />
-
-</template>
diff --git a/templates/gradle-projects/NewAndroidModule/globals.xml.ftl b/templates/gradle-projects/NewAndroidModule/globals.xml.ftl
new file mode 100644
index 0000000..1843c0d
--- /dev/null
+++ b/templates/gradle-projects/NewAndroidModule/globals.xml.ftl
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<globals>
+ <global id="topOut" value="." />
+ <global id="projectOut" value="." />
+ <global id="appCompat" value="${(minApiLevel lt 14)?string('1','')}" />
+ <global id="manifestOut" value="${manifestDir}" />
+ <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
+ <global id="resOut" value="${resDir}" />
+ <global id="mavenUrl" value="mavenCentral" />
+ <global id="buildToolsVersion" value="18.0.1" />
+ <global id="gradlePluginVersion" value="0.6.+" />
+ <global id="v4SupportLibraryVersion" value="13.0.+" />
+</globals>
diff --git a/templates/gradle-projects/NewAndroidModule/recipe.xml.ftl b/templates/gradle-projects/NewAndroidModule/recipe.xml.ftl
new file mode 100644
index 0000000..ade222a
--- /dev/null
+++ b/templates/gradle-projects/NewAndroidModule/recipe.xml.ftl
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+<recipe>
+
+
+ <#if appCompat?has_content><dependency mavenUrl="com.android.support:appcompat-v7:+"/></#if>
+
+<#if !createActivity>
+ <mkdir at="${srcOut}" />
+</#if>
+
+ <merge from="settings.gradle.ftl"
+ to="${escapeXmlAttribute(topOut)}/settings.gradle" />
+ <instantiate from="build.gradle.ftl"
+ to="${escapeXmlAttribute(projectOut)}/build.gradle" />
+ <instantiate from="AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+
+<#if copyIcons>
+ <copy from="res/drawable-hdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-hdpi" />
+ <copy from="res/drawable-mdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-mdpi" />
+ <copy from="res/drawable-xhdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-xhdpi" />
+ <copy from="res/drawable-xxhdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-xxhdpi" />
+</#if>
+<#if makeIgnore>
+ <copy from="module_ignore"
+ to="${escapeXmlAttribute(projectOut)}/.gitignore" />
+</#if>
+<#if enableProGuard>
+ <instantiate from="proguard-rules.txt.ftl"
+ to="${escapeXmlAttribute(projectOut)}/proguard-rules.txt" />
+</#if>
+<#if !(isLibraryProject??) || !isLibraryProject>
+ <instantiate from="res/values/styles.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/styles.xml" />
+</#if>
+
+ <instantiate from="res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+</recipe>
diff --git a/templates/gradle-projects/NewAndroidModule/root/AndroidManifest.xml.ftl b/templates/gradle-projects/NewAndroidModule/root/AndroidManifest.xml.ftl
new file mode 100644
index 0000000..137a9ea
--- /dev/null
+++ b/templates/gradle-projects/NewAndroidModule/root/AndroidManifest.xml.ftl
@@ -0,0 +1,11 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="${packageName}">
+
+ <application <#if minApiLevel gte 4 && buildApi gte 4>android:allowBackup="true"</#if>
+ android:label="@string/app_name"
+ android:icon="@drawable/ic_launcher"<#if baseTheme != "none" && !isLibraryProject>
+ android:theme="@style/AppTheme"</#if>>
+
+ </application>
+
+</manifest>
diff --git a/templates/gradle-projects/NewAndroidModule/root/build.gradle.ftl b/templates/gradle-projects/NewAndroidModule/root/build.gradle.ftl
new file mode 100644
index 0000000..2e35ec4
--- /dev/null
+++ b/templates/gradle-projects/NewAndroidModule/root/build.gradle.ftl
@@ -0,0 +1,73 @@
+buildscript {
+ repositories {
+<#if mavenUrl == "mavenCentral">
+ mavenCentral()
+<#else>
+ maven { url '${mavenUrl}' }
+</#if>
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:${gradlePluginVersion}'
+ }
+}
+<#if isLibraryProject?? && isLibraryProject>
+apply plugin: 'android-library'
+<#else>
+apply plugin: 'android'
+</#if>
+
+repositories {
+<#if mavenUrl == "mavenCentral">
+ mavenCentral()
+<#else>
+ maven { url '${mavenUrl}' }
+</#if>
+}
+
+android {
+ compileSdkVersion ${buildApi}
+ buildToolsVersion "${buildToolsVersion}"
+
+ defaultConfig {
+ minSdkVersion ${minApi}
+ targetSdkVersion ${targetApi}
+ versionCode 1
+ versionName "1.0"
+ }
+<#if javaVersion?? && javaVersion != "1.6">
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_${javaVersion?replace('.','_','i')}
+ targetCompatibility JavaVersion.VERSION_${javaVersion?replace('.','_','i')}
+ }
+</#if>
+<#if enableProGuard>
+ <#if isLibraryProject>
+ release {
+ runProguard false
+ proguardFile 'proguard-rules.txt'
+ proguardFile getDefaultProguardFile('proguard-android.txt')
+ }
+ <#else>
+ buildTypes {
+ release {
+ runProguard false
+ proguardFile getDefaultProguardFile('proguard-android.txt')
+ }
+ }
+ productFlavors {
+ defaultFlavor {
+ proguardFile 'proguard-rules.txt'
+ }
+ }
+ </#if>
+</#if>
+}
+
+dependencies {
+ <#if dependencyList?? >
+ <#list dependencyList as dependency>
+ compile '${dependency}'
+ </#list>
+ </#if>
+}
diff --git a/templates/gradle-projects/NewAndroidModule/root/module_ignore b/templates/gradle-projects/NewAndroidModule/root/module_ignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/templates/gradle-projects/NewAndroidModule/root/module_ignore
@@ -0,0 +1 @@
+/build
diff --git a/templates/gradle-projects/NewAndroidModule/root/proguard-rules.txt.ftl b/templates/gradle-projects/NewAndroidModule/root/proguard-rules.txt.ftl
new file mode 100644
index 0000000..f766622
--- /dev/null
+++ b/templates/gradle-projects/NewAndroidModule/root/proguard-rules.txt.ftl
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdkDir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
\ No newline at end of file
diff --git a/templates/gradle-projects/NewAndroidLibrary/root/res/drawable-hdpi/ic_launcher.png b/templates/gradle-projects/NewAndroidModule/root/res/drawable-hdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
rename from templates/gradle-projects/NewAndroidLibrary/root/res/drawable-hdpi/ic_launcher.png
rename to templates/gradle-projects/NewAndroidModule/root/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/templates/gradle-projects/NewAndroidLibrary/root/res/drawable-mdpi/ic_launcher.png b/templates/gradle-projects/NewAndroidModule/root/res/drawable-mdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
rename from templates/gradle-projects/NewAndroidLibrary/root/res/drawable-mdpi/ic_launcher.png
rename to templates/gradle-projects/NewAndroidModule/root/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/templates/gradle-projects/NewAndroidLibrary/root/res/drawable-xhdpi/ic_launcher.png b/templates/gradle-projects/NewAndroidModule/root/res/drawable-xhdpi/ic_launcher.png
old mode 100755
new mode 100644
similarity index 100%
rename from templates/gradle-projects/NewAndroidLibrary/root/res/drawable-xhdpi/ic_launcher.png
rename to templates/gradle-projects/NewAndroidModule/root/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/templates/gradle-projects/NewAndroidModule/root/res/drawable-xxhdpi/ic_launcher.png b/templates/gradle-projects/NewAndroidModule/root/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4df1894
--- /dev/null
+++ b/templates/gradle-projects/NewAndroidModule/root/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/templates/gradle-projects/NewAndroidLibrary/root/res/values/strings.xml.ftl b/templates/gradle-projects/NewAndroidModule/root/res/values/strings.xml.ftl
similarity index 100%
rename from templates/gradle-projects/NewAndroidLibrary/root/res/values/strings.xml.ftl
rename to templates/gradle-projects/NewAndroidModule/root/res/values/strings.xml.ftl
diff --git a/templates/gradle-projects/NewAndroidModule/root/res/values/styles.xml.ftl b/templates/gradle-projects/NewAndroidModule/root/res/values/styles.xml.ftl
new file mode 100644
index 0000000..7b1c895
--- /dev/null
+++ b/templates/gradle-projects/NewAndroidModule/root/res/values/styles.xml.ftl
@@ -0,0 +1,12 @@
+<resources>
+
+ <!-- Base application theme. -->
+ <style name="AppTheme" parent="<#if
+ appCompat?has_content>Theme.AppCompat<#else
+ >android:Theme.Holo</#if><#if
+ baseTheme?contains("light")>.Light<#if
+ baseTheme?contains("darkactionbar")>.DarkActionBar</#if></#if>">
+ <!-- Customize your theme here. -->
+ </style>
+
+</resources>
diff --git a/templates/gradle-projects/NewAndroidLibrary/root/settings.gradle.ftl b/templates/gradle-projects/NewAndroidModule/root/settings.gradle.ftl
similarity index 100%
rename from templates/gradle-projects/NewAndroidLibrary/root/settings.gradle.ftl
rename to templates/gradle-projects/NewAndroidModule/root/settings.gradle.ftl
diff --git a/templates/gradle-projects/NewAndroidModule/template.xml b/templates/gradle-projects/NewAndroidModule/template.xml
new file mode 100644
index 0000000..dc0ecfa
--- /dev/null
+++ b/templates/gradle-projects/NewAndroidModule/template.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0"?>
+<template
+ format="4"
+ revision="4"
+ name="Android Module"
+ description="Creates a new Android module.">
+
+ <thumbs>
+ <thumb>template_new_project.png</thumb>
+ </thumbs>
+
+ <category value="Applications" />
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="app_package|nonempty"
+ default="com.mycompany.myapp" />
+
+ <parameter
+ id="appTitle"
+ name="Module title"
+ type="string"
+ constraints="nonempty"
+ default="My Module" />
+
+ <parameter
+ id="baseTheme"
+ name="Base Theme"
+ type="enum"
+ default="holo_light_darkactionbar"
+ help="The base user interface theme for the module">
+ <option id="none">None</option>
+ <option id="holo_dark" minBuildApi="11">Holo Dark</option>
+ <option id="holo_light" minBuildApi="11">Holo Light</option>
+ <option id="holo_light_darkactionbar" minBuildApi="14" default="true">Holo Light with Dark Action Bar</option>
+ </parameter>
+
+ <parameter
+ id="minApi"
+ name="Minimum API level"
+ type="string"
+ constraints="apilevel"
+ default="7" />
+
+ <!--
+ Usually the same as minApi, but when minApi is a code name this will be the corresponding
+ API level
+ -->
+ <parameter
+ id="minApiLevel"
+ name="Minimum API level"
+ type="string"
+ constraints="apilevel"
+ default="7" />
+
+ <parameter
+ id="targetApi"
+ name="Target API level"
+ type="string"
+ constraints="apilevel"
+ default="16" />
+
+ <parameter
+ id="buildApi"
+ name="Build API level"
+ type="string"
+ constraints="apilevel"
+ default="18" />
+
+ <parameter
+ id="copyIcons"
+ name="Include launcher icons"
+ type="boolean"
+ default="true" />
+
+ <parameter
+ id="makeIgnore"
+ name="Create .gitignore file"
+ type="boolean"
+ default="true" />
+
+ <parameter
+ id="enableProGuard"
+ name="Enable ProGuard"
+ type="boolean"
+ default="true" />
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/gradle-projects/NewAndroidLibrary/template_new_project.png b/templates/gradle-projects/NewAndroidModule/template_new_project.png
similarity index 100%
rename from templates/gradle-projects/NewAndroidLibrary/template_new_project.png
rename to templates/gradle-projects/NewAndroidModule/template_new_project.png
Binary files differ
diff --git a/templates/gradle-projects/NewAndroidProject/globals.xml.ftl b/templates/gradle-projects/NewAndroidProject/globals.xml.ftl
new file mode 100644
index 0000000..26e600e
--- /dev/null
+++ b/templates/gradle-projects/NewAndroidProject/globals.xml.ftl
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<globals>
+ <global id="topOut" value="." />
+</globals>
diff --git a/templates/gradle-projects/NewAndroidProject/recipe.xml.ftl b/templates/gradle-projects/NewAndroidProject/recipe.xml.ftl
new file mode 100644
index 0000000..5321458
--- /dev/null
+++ b/templates/gradle-projects/NewAndroidProject/recipe.xml.ftl
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+<recipe>
+ <instantiate from="build.gradle.ftl"
+ to="${escapeXmlAttribute(topOut)}/build.gradle" />
+
+<#if makeIgnore>
+ <copy from="project_ignore"
+ to="${escapeXmlAttribute(topOut)}/.gitignore" />
+</#if>
+
+ <instantiate from="settings.gradle.ftl"
+ to="${escapeXmlAttribute(topOut)}/settings.gradle" />
+
+ <instantiate from="gradle.properties.ftl"
+ to="${escapeXmlAttribute(topOut)}/gradle.properties" />
+
+ <copy from="$TEMPLATEDIR/gradle/wrapper"
+ to="${escapeXmlAttribute(topOut)}/" />
+
+<#if sdkDir??>
+ <instantiate from="local.properties.ftl"
+ to="${escapeXmlAttribute(topOut)}/local.properties" />
+</#if>
+</recipe>
diff --git a/templates/gradle-projects/NewAndroidProject/root/build.gradle.ftl b/templates/gradle-projects/NewAndroidProject/root/build.gradle.ftl
new file mode 100644
index 0000000..f7a7ae7
--- /dev/null
+++ b/templates/gradle-projects/NewAndroidProject/root/build.gradle.ftl
@@ -0,0 +1 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
\ No newline at end of file
diff --git a/templates/gradle-projects/NewAndroidProject/root/gradle.properties.ftl b/templates/gradle-projects/NewAndroidProject/root/gradle.properties.ftl
new file mode 100644
index 0000000..5d08ba7
--- /dev/null
+++ b/templates/gradle-projects/NewAndroidProject/root/gradle.properties.ftl
@@ -0,0 +1,18 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Settings specified in this file will override any Gradle settings
+# configured through the IDE.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
\ No newline at end of file
diff --git a/templates/gradle-projects/NewAndroidProject/root/local.properties.ftl b/templates/gradle-projects/NewAndroidProject/root/local.properties.ftl
new file mode 100644
index 0000000..9dcbf9b
--- /dev/null
+++ b/templates/gradle-projects/NewAndroidProject/root/local.properties.ftl
@@ -0,0 +1,10 @@
+## This file is automatically generated by Android Studio.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file should *NOT* be checked into Version Control Systems,
+# as it contains information specific to your local configuration.
+#
+# Location of the SDK. This is only used by Gradle.
+# For customization when using a Version Control System, please read the
+# header note.
+sdk.dir=${escapePropertyValue(sdkDir)}
\ No newline at end of file
diff --git a/templates/gradle-projects/NewAndroidProject/root/project_ignore b/templates/gradle-projects/NewAndroidProject/root/project_ignore
new file mode 100644
index 0000000..d6bfc95
--- /dev/null
+++ b/templates/gradle-projects/NewAndroidProject/root/project_ignore
@@ -0,0 +1,4 @@
+.gradle
+/local.properties
+/.idea/workspace.xml
+.DS_Store
diff --git a/templates/gradle-projects/NewAndroidProject/root/settings.gradle.ftl b/templates/gradle-projects/NewAndroidProject/root/settings.gradle.ftl
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/templates/gradle-projects/NewAndroidProject/root/settings.gradle.ftl
diff --git a/templates/gradle-projects/NewAndroidProject/template.xml b/templates/gradle-projects/NewAndroidProject/template.xml
new file mode 100644
index 0000000..6ae554a
--- /dev/null
+++ b/templates/gradle-projects/NewAndroidProject/template.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<template
+ format="4"
+ revision="1"
+ name="Android Project"
+ description="Creates a new Android project.">
+
+ <thumbs>
+ <thumb>template_new_project.png</thumb>
+ </thumbs>
+
+ <category value="Projects" />
+
+ <parameter
+ id="makeIgnore"
+ name="Create .gitignore file"
+ type="boolean"
+ default="true" />
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/templates/gradle-projects/NewAndroidLibrary/template_new_project.png b/templates/gradle-projects/NewAndroidProject/template_new_project.png
similarity index 100%
copy from templates/gradle-projects/NewAndroidLibrary/template_new_project.png
copy to templates/gradle-projects/NewAndroidProject/template_new_project.png
Binary files differ
diff --git a/templates/gradle-projects/NewJavaLibrary/globals.xml.ftl b/templates/gradle-projects/NewJavaLibrary/globals.xml.ftl
index b731454..8d19d26 100644
--- a/templates/gradle-projects/NewJavaLibrary/globals.xml.ftl
+++ b/templates/gradle-projects/NewJavaLibrary/globals.xml.ftl
@@ -2,5 +2,5 @@
<globals>
<global id="topOut" value="." />
<global id="projectOut" value="." />
- <global id="srcOut" value="src/${slashedPackageName(packageName)}" />
+ <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
</globals>
diff --git a/templates/gradle-projects/NewJavaLibrary/recipe.xml.ftl b/templates/gradle-projects/NewJavaLibrary/recipe.xml.ftl
index e3b20a3..d750f76 100644
--- a/templates/gradle-projects/NewJavaLibrary/recipe.xml.ftl
+++ b/templates/gradle-projects/NewJavaLibrary/recipe.xml.ftl
@@ -6,4 +6,8 @@
to="${escapeXmlAttribute(projectOut)}/build.gradle" />
<instantiate from="/src/library_package/Placeholder.java.ftl"
to="${escapeXmlAttribute(srcOut)}/${className}.java" />
+<#if makeIgnore>
+ <copy from="gitignore"
+ to="${escapeXmlAttribute(projectOut)}/.gitignore" />
+</#if>
</recipe>
diff --git a/templates/gradle-projects/NewJavaLibrary/root/gitignore b/templates/gradle-projects/NewJavaLibrary/root/gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/templates/gradle-projects/NewJavaLibrary/root/gitignore
@@ -0,0 +1 @@
+/build
diff --git a/templates/gradle-projects/NewJavaLibrary/template.xml b/templates/gradle-projects/NewJavaLibrary/template.xml
index cb117a5..a9f35c1 100644
--- a/templates/gradle-projects/NewJavaLibrary/template.xml
+++ b/templates/gradle-projects/NewJavaLibrary/template.xml
@@ -1,7 +1,7 @@
<?xml version="1.0"?>
<template
- format="1"
- revision="2"
+ format="4"
+ revision="4"
name="Java Library"
description="Creates a new Java library.">
@@ -32,6 +32,12 @@
constraints="nonempty"
default="MyClass"/>
+ <parameter
+ id="makeIgnore"
+ name="Create .gitignore file"
+ type="boolean"
+ default="true" />
+
<globals file="globals.xml.ftl" />
<execute file="recipe.xml.ftl" />
diff --git a/templates/gradle/wrapper/gradle/wrapper/gradle-wrapper.properties b/templates/gradle/wrapper/gradle/wrapper/gradle-wrapper.properties
index 5c22dec..d48578c 100644
--- a/templates/gradle/wrapper/gradle/wrapper/gradle-wrapper.properties
+++ b/templates/gradle/wrapper/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=http\://services.gradle.org/distributions/gradle-1.6-bin.zip
+distributionUrl=http\://services.gradle.org/distributions/gradle-1.9-bin.zip
diff --git a/templates/other/AppWidget/globals.xml.ftl b/templates/other/AppWidget/globals.xml.ftl
index ac85374..65e6905 100644
--- a/templates/other/AppWidget/globals.xml.ftl
+++ b/templates/other/AppWidget/globals.xml.ftl
@@ -1,5 +1,7 @@
<?xml version="1.0"?>
<globals>
- <global id="srcOut" value="src/${slashedPackageName(packageName)}" />
+ <global id="manifestOut" value="${manifestDir}" />
+ <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
+ <global id="resOut" value="${resDir}" />
<global id="class_name" value="${camelCaseToUnderscore(className)}" />
</globals>
diff --git a/templates/other/AppWidget/recipe.xml.ftl b/templates/other/AppWidget/recipe.xml.ftl
index 876b7b0..9db22d2 100644
--- a/templates/other/AppWidget/recipe.xml.ftl
+++ b/templates/other/AppWidget/recipe.xml.ftl
@@ -1,31 +1,36 @@
<?xml version="1.0"?>
<recipe>
- <merge from="AndroidManifest.xml.ftl" />
+ <merge from="AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
- <copy from="res/drawable-nodpi/example_appwidget_preview.png" />
+ <copy from="res/drawable-nodpi/example_appwidget_preview.png"
+ to="${escapeXmlAttribute(resOut)}/drawable-nodpi/example_appwidget_preview.png" />
<instantiate from="res/layout/appwidget.xml"
- to="res/layout/${class_name}.xml" />
+ to="${escapeXmlAttribute(resOut)}/layout/${class_name}.xml" />
<#if configurable>
<instantiate from="res/layout/appwidget_configure.xml"
- to="res/layout/${class_name}_configure.xml" />
+ to="${escapeXmlAttribute(resOut)}/layout/${class_name}_configure.xml" />
</#if>
<instantiate from="res/xml/appwidget_info.xml.ftl"
- to="res/xml/${class_name}_info.xml" />
- <merge from="res/values/strings.xml.ftl" />
- <merge from="res/values-v14/dimens.xml" />
- <merge from="res/values/dimens.xml" />
+ to="${escapeXmlAttribute(resOut)}/xml/${class_name}_info.xml" />
+ <merge from="res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
+ <merge from="res/values-v14/dimens.xml"
+ to="${escapeXmlAttribute(resOut)}/values-v14/dimens.xml" />
+ <merge from="res/values/dimens.xml"
+ to="${escapeXmlAttribute(resOut)}/values/dimens.xml" />
<instantiate from="src/app_package/AppWidget.java.ftl"
- to="${srcOut}/${className}.java" />
+ to="${escapeXmlAttribute(srcOut)}/${className}.java" />
<#if configurable>
<instantiate from="src/app_package/AppWidgetConfigureActivity.java.ftl"
- to="${srcOut}/${className}ConfigureActivity.java" />
+ to="${escapeXmlAttribute(srcOut)}/${className}ConfigureActivity.java" />
</#if>
- <open file="${srcOut}/${className}.java" />
+ <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
</recipe>
diff --git a/templates/other/AppWidget/root/AndroidManifest.xml.ftl b/templates/other/AppWidget/root/AndroidManifest.xml.ftl
index 8b96d56..bf8f820 100644
--- a/templates/other/AppWidget/root/AndroidManifest.xml.ftl
+++ b/templates/other/AppWidget/root/AndroidManifest.xml.ftl
@@ -3,7 +3,7 @@
<application>
- <receiver android:name=".${className}" >
+ <receiver android:name="${packageName}.${className}" >
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
@@ -14,7 +14,7 @@
</receiver>
<#if configurable>
- <activity android:name=".${className}ConfigureActivity" >
+ <activity android:name="${packageName}.${className}ConfigureActivity" >
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
diff --git a/templates/other/AppWidget/template.xml b/templates/other/AppWidget/template.xml
index f071363..d79ef0c 100644
--- a/templates/other/AppWidget/template.xml
+++ b/templates/other/AppWidget/template.xml
@@ -1,7 +1,7 @@
<?xml version="1.0"?>
<template
- format="3"
- revision="1"
+ format="4"
+ revision="2"
name="New App Widget"
description="Creates a new App Widget"
minApi="4"
diff --git a/templates/other/BlankFragment/globals.xml.ftl b/templates/other/BlankFragment/globals.xml.ftl
index bfc27eb..6e28120 100644
--- a/templates/other/BlankFragment/globals.xml.ftl
+++ b/templates/other/BlankFragment/globals.xml.ftl
@@ -1,4 +1,5 @@
<?xml version="1.0"?>
<globals>
- <global id="srcOut" value="src/${slashedPackageName(packageName)}" />
+ <global id="resOut" value="${resDir}" />
+ <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
</globals>
diff --git a/templates/other/BlankFragment/recipe.xml.ftl b/templates/other/BlankFragment/recipe.xml.ftl
index add6d27..0008b9b 100644
--- a/templates/other/BlankFragment/recipe.xml.ftl
+++ b/templates/other/BlankFragment/recipe.xml.ftl
@@ -1,18 +1,19 @@
<?xml version="1.0"?>
<recipe>
- <merge from="res/values/strings.xml" />
+ <dependency mavenUrl="com.android.support:support-v4:+"/>
+ <merge from="res/values/strings.xml" to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
<#if includeLayout>
<instantiate from="res/layout/fragment_blank.xml.ftl"
- to="res/layout/fragment_${classToResource(className)}.xml" />
+ to="${escapeXmlAttribute(resOut)}/layout/fragment_${classToResource(className)}.xml" />
- <open file="res/layout/fragment_${classToResource(className)}.xml" />
+ <open file="${escapeXmlAttribute(resOut)}/layout/fragment_${classToResource(className)}.xml" />
</#if>
- <open file="${srcOut}/${className}.java" />
+ <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
<instantiate from="src/app_package/BlankFragment.java.ftl"
- to="${srcOut}/${className}.java" />
+ to="${escapeXmlAttribute(srcOut)}/${className}.java" />
</recipe>
diff --git a/templates/other/BlankFragment/root/res/layout/fragment_blank.xml.ftl b/templates/other/BlankFragment/root/res/layout/fragment_blank.xml.ftl
index 3e04f05..5712aae 100644
--- a/templates/other/BlankFragment/root/res/layout/fragment_blank.xml.ftl
+++ b/templates/other/BlankFragment/root/res/layout/fragment_blank.xml.ftl
@@ -2,7 +2,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
- tools:context=".${className}">
+ tools:context="${packageName}.${className}">
<!-- TODO: Update blank fragment layout -->
<TextView
diff --git a/templates/other/BlankFragment/template.xml b/templates/other/BlankFragment/template.xml
index 9434c18..5ed7c07 100644
--- a/templates/other/BlankFragment/template.xml
+++ b/templates/other/BlankFragment/template.xml
@@ -1,7 +1,7 @@
<?xml version="1.0"?>
<template
- format="3"
- revision="1"
+ format="4"
+ revision="2"
name="New Blank Fragment"
description="Creates a blank fragment that is compatible back to API level 4."
minApi="7"
diff --git a/templates/other/BroadcastReceiver/globals.xml.ftl b/templates/other/BroadcastReceiver/globals.xml.ftl
index bfc27eb..7beccc1 100644
--- a/templates/other/BroadcastReceiver/globals.xml.ftl
+++ b/templates/other/BroadcastReceiver/globals.xml.ftl
@@ -1,4 +1,5 @@
<?xml version="1.0"?>
<globals>
- <global id="srcOut" value="src/${slashedPackageName(packageName)}" />
+ <global id="manifestOut" value="${manifestDir}" />
+ <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
</globals>
diff --git a/templates/other/BroadcastReceiver/recipe.xml.ftl b/templates/other/BroadcastReceiver/recipe.xml.ftl
index a9d2623..d889414 100644
--- a/templates/other/BroadcastReceiver/recipe.xml.ftl
+++ b/templates/other/BroadcastReceiver/recipe.xml.ftl
@@ -1,7 +1,8 @@
<?xml version="1.0"?>
<recipe>
- <merge from="AndroidManifest.xml.ftl" />
+ <merge from="AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
<instantiate from="src/app_package/BroadcastReceiver.java.ftl"
- to="${srcOut}/${className}.java" />
- <open file="${srcOut}/${className}.java" />
+ to="${escapeXmlAttribute(srcOut)}/${className}.java" />
+ <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
</recipe>
diff --git a/templates/other/BroadcastReceiver/root/AndroidManifest.xml.ftl b/templates/other/BroadcastReceiver/root/AndroidManifest.xml.ftl
index 27b60b0..6849a3b 100644
--- a/templates/other/BroadcastReceiver/root/AndroidManifest.xml.ftl
+++ b/templates/other/BroadcastReceiver/root/AndroidManifest.xml.ftl
@@ -1,7 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
<application>
- <receiver android:name=".${className}"
+ <receiver android:name="${packageName}.${className}"
android:exported="${isExported?string}"
android:enabled="${isEnabled?string}" >
</receiver>
diff --git a/templates/other/BroadcastReceiver/template.xml b/templates/other/BroadcastReceiver/template.xml
index b17851e..159ff75 100644
--- a/templates/other/BroadcastReceiver/template.xml
+++ b/templates/other/BroadcastReceiver/template.xml
@@ -1,7 +1,7 @@
<?xml version="1.0"?>
<template
- format="1"
- revision="1"
+ format="4"
+ revision="2"
name="Broadcast Receiver"
description="Creates a new broadcast receiver component and adds it to your Android manifest.">
diff --git a/templates/other/ContentProvider/globals.xml.ftl b/templates/other/ContentProvider/globals.xml.ftl
index bfc27eb..7beccc1 100644
--- a/templates/other/ContentProvider/globals.xml.ftl
+++ b/templates/other/ContentProvider/globals.xml.ftl
@@ -1,4 +1,5 @@
<?xml version="1.0"?>
<globals>
- <global id="srcOut" value="src/${slashedPackageName(packageName)}" />
+ <global id="manifestOut" value="${manifestDir}" />
+ <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
</globals>
diff --git a/templates/other/ContentProvider/recipe.xml.ftl b/templates/other/ContentProvider/recipe.xml.ftl
index 6cdad82..9bc3e61 100644
--- a/templates/other/ContentProvider/recipe.xml.ftl
+++ b/templates/other/ContentProvider/recipe.xml.ftl
@@ -1,7 +1,8 @@
<?xml version="1.0"?>
<recipe>
- <merge from="AndroidManifest.xml.ftl" />
+ <merge from="AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
<instantiate from="src/app_package/ContentProvider.java.ftl"
- to="${srcOut}/${className}.java" />
- <open file="${srcOut}/${className}.java" />
+ to="${escapeXmlAttribute(srcOut)}/${className}.java" />
+ <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
</recipe>
diff --git a/templates/other/ContentProvider/root/AndroidManifest.xml.ftl b/templates/other/ContentProvider/root/AndroidManifest.xml.ftl
index 6fa4afc..819925d 100644
--- a/templates/other/ContentProvider/root/AndroidManifest.xml.ftl
+++ b/templates/other/ContentProvider/root/AndroidManifest.xml.ftl
@@ -1,7 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
<application>
- <provider android:name=".${className}"
+ <provider android:name="${packageName}.${className}"
android:authorities="${authorities}"
android:exported="${isExported?string}"
android:enabled="${isEnabled?string}" >
diff --git a/templates/other/ContentProvider/template.xml b/templates/other/ContentProvider/template.xml
index 21ed1be..dc7e1ea 100644
--- a/templates/other/ContentProvider/template.xml
+++ b/templates/other/ContentProvider/template.xml
@@ -1,7 +1,7 @@
<?xml version="1.0"?>
<template
- format="1"
- revision="1"
+ format="4"
+ revision="2"
name="Content Provider"
description="Creates a new content provider component and adds it to your Android manifest.">
diff --git a/templates/other/CustomView/globals.xml.ftl b/templates/other/CustomView/globals.xml.ftl
index d2eeb40..58fe1d0 100644
--- a/templates/other/CustomView/globals.xml.ftl
+++ b/templates/other/CustomView/globals.xml.ftl
@@ -1,5 +1,6 @@
<?xml version="1.0"?>
<globals>
- <global id="srcOut" value="src/${slashedPackageName(packageName)}" />
+ <global id="resOut" value="${resDir}" />
+ <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
<global id="view_class" value="${camelCaseToUnderscore(viewClass)}" />
</globals>
diff --git a/templates/other/CustomView/recipe.xml.ftl b/templates/other/CustomView/recipe.xml.ftl
index d152df0..ce605b2 100644
--- a/templates/other/CustomView/recipe.xml.ftl
+++ b/templates/other/CustomView/recipe.xml.ftl
@@ -1,13 +1,13 @@
<?xml version="1.0"?>
<recipe>
<merge from="res/values/attrs.xml.ftl"
- to="res/values/attrs_${view_class}.xml" />
+ to="${escapeXmlAttribute(resOut)}/values/attrs_${view_class}.xml" />
<instantiate from="res/layout/sample.xml.ftl"
- to="res/layout/sample_${view_class}.xml" />
+ to="${escapeXmlAttribute(resOut)}/layout/sample_${view_class}.xml" />
<instantiate from="src/app_package/CustomView.java.ftl"
- to="${srcOut}/${viewClass}.java" />
+ to="${escapeXmlAttribute(srcOut)}/${viewClass}.java" />
- <open file="${srcOut}/${viewClass}.java" />
- <open file="res/layout/sample_${view_class}.xml" />
+ <open file="${escapeXmlAttribute(srcOut)}/${viewClass}.java" />
+ <open file="${escapeXmlAttribute(resOut)}/layout/sample_${view_class}.xml" />
</recipe>
diff --git a/templates/other/CustomView/root/res/layout/sample.xml.ftl b/templates/other/CustomView/root/res/layout/sample.xml.ftl
index 03125b2..7dc4232 100755
--- a/templates/other/CustomView/root/res/layout/sample.xml.ftl
+++ b/templates/other/CustomView/root/res/layout/sample.xml.ftl
@@ -1,6 +1,6 @@
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
-<#if !isLibraryProject>
+<#if !isLibraryProject && (!(isGradle??) || !isGradle) >
xmlns:app="http://schemas.android.com/apk/res/${packageName}"
<#else>
xmlns:app="http://schemas.android.com/apk/res-auto"
diff --git a/templates/other/CustomView/template.xml b/templates/other/CustomView/template.xml
index 55f6f3d..5373030 100644
--- a/templates/other/CustomView/template.xml
+++ b/templates/other/CustomView/template.xml
@@ -1,7 +1,7 @@
<?xml version="1.0"?>
<template
- format="1"
- revision="1"
+ format="4"
+ revision="2"
name="Custom View"
description="Creates a new custom view that extends android.view.View and exposes custom attributes.">
diff --git a/templates/other/Daydream/globals.xml.ftl b/templates/other/Daydream/globals.xml.ftl
index 04537f9..4d64b36 100644
--- a/templates/other/Daydream/globals.xml.ftl
+++ b/templates/other/Daydream/globals.xml.ftl
@@ -1,6 +1,8 @@
<?xml version="1.0"?>
<globals>
- <global id="srcOut" value="src/${slashedPackageName(packageName)}" />
+ <global id="manifestOut" value="${manifestDir}" />
+ <global id="resOut" value="${resDir}" />
+ <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
<global id="class_name" value="${classToResource(className)}" />
<global id="info_name" value="${classToResource(className)}_info" />
<global id="settingsClassName" value="${className}SettingsActivity" />
diff --git a/templates/other/Daydream/recipe.xml.ftl b/templates/other/Daydream/recipe.xml.ftl
index 8db9811..5b99917 100644
--- a/templates/other/Daydream/recipe.xml.ftl
+++ b/templates/other/Daydream/recipe.xml.ftl
@@ -1,26 +1,28 @@
<?xml version="1.0"?>
<recipe>
- <merge from="AndroidManifest.xml.ftl" />
- <merge from="res/values/strings.xml.ftl" />
+ <merge from="AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
+ <merge from="res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
<copy from="res/layout-v17/dream.xml"
- to="res/layout-v17/${class_name}.xml" />
+ to="${escapeXmlAttribute(resOut)}/layout-v17/${class_name}.xml" />
<instantiate from="src/app_package/DreamService.java.ftl"
- to="${srcOut}/${className}.java" />
+ to="${escapeXmlAttribute(srcOut)}/${className}.java" />
<#if configurable>
<copy from="res/xml/dream_prefs.xml"
- to="res/xml/${prefs_name}.xml" />
+ to="${escapeXmlAttribute(resOut)}/xml/${prefs_name}.xml" />
<instantiate from="src/app_package/SettingsActivity.java.ftl"
- to="${srcOut}/${settingsClassName}.java" />
+ to="${escapeXmlAttribute(srcOut)}/${settingsClassName}.java" />
<instantiate from="res/xml/xml_dream.xml.ftl"
- to="res/xml/${info_name}.xml" />
+ to="${escapeXmlAttribute(resOut)}/xml/${info_name}.xml" />
</#if>
- <open file="${srcOut}/${className}.java" />
+ <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
</recipe>
diff --git a/templates/other/Daydream/root/AndroidManifest.xml.ftl b/templates/other/Daydream/root/AndroidManifest.xml.ftl
index c23bc6e..01beaec 100644
--- a/templates/other/Daydream/root/AndroidManifest.xml.ftl
+++ b/templates/other/Daydream/root/AndroidManifest.xml.ftl
@@ -4,12 +4,12 @@
<#if configurable>
<activity
- android:name=".${settingsClassName}" />
+ android:name="${packageName}.${settingsClassName}" />
</#if>
<!-- This service is only used on devices with API v17+ -->
<service
- android:name=".${className}"
+ android:name="${packageName}.${className}"
android:exported="true" >
<intent-filter>
<action android:name="android.service.dreams.DreamService" />
diff --git a/templates/other/Daydream/template.xml b/templates/other/Daydream/template.xml
index 2014eee..cd292a4 100644
--- a/templates/other/Daydream/template.xml
+++ b/templates/other/Daydream/template.xml
@@ -1,7 +1,7 @@
<?xml version="1.0"?>
<template
- format="3"
- revision="1"
+ format="4"
+ revision="2"
name="New Daydream"
description="Creates a new Daydream service component, for use on devices running Android 4.2 and later."
minBuildApi="17">
diff --git a/templates/other/IntentService/globals.xml.ftl b/templates/other/IntentService/globals.xml.ftl
index bfc27eb..b44b9f0 100644
--- a/templates/other/IntentService/globals.xml.ftl
+++ b/templates/other/IntentService/globals.xml.ftl
@@ -1,4 +1,4 @@
<?xml version="1.0"?>
<globals>
- <global id="srcOut" value="src/${slashedPackageName(packageName)}" />
+ <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
</globals>
diff --git a/templates/other/IntentService/recipe.xml.ftl b/templates/other/IntentService/recipe.xml.ftl
index 958f6fd..197fc1f 100644
--- a/templates/other/IntentService/recipe.xml.ftl
+++ b/templates/other/IntentService/recipe.xml.ftl
@@ -1,7 +1,8 @@
<?xml version="1.0"?>
<recipe>
- <merge from="AndroidManifest.xml.ftl" />
+ <merge from="AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
<instantiate from="src/app_package/IntentService.java.ftl"
- to="${srcOut}/${className}.java" />
- <open file="${srcOut}/${className}.java" />
+ to="${escapeXmlAttribute(srcOut)}/${className}.java" />
+ <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
</recipe>
diff --git a/templates/other/IntentService/root/AndroidManifest.xml.ftl b/templates/other/IntentService/root/AndroidManifest.xml.ftl
index fcc0b66..61b66bb 100644
--- a/templates/other/IntentService/root/AndroidManifest.xml.ftl
+++ b/templates/other/IntentService/root/AndroidManifest.xml.ftl
@@ -1,7 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
<application>
- <service android:name=".${className}"
+ <service android:name="${packageName}.${className}"
android:exported="false" >
</service>
</application>
diff --git a/templates/other/IntentService/template.xml b/templates/other/IntentService/template.xml
index ffe6cd5..3d86c9c 100644
--- a/templates/other/IntentService/template.xml
+++ b/templates/other/IntentService/template.xml
@@ -1,7 +1,7 @@
<?xml version="1.0"?>
<template
- format="3"
- revision="1"
+ format="4"
+ revision="2"
name="New IntentService"
description="Creates a new intent service class."
minApi="3"
diff --git a/templates/other/ListFragment/globals.xml.ftl b/templates/other/ListFragment/globals.xml.ftl
index 577250d..29dbb98 100644
--- a/templates/other/ListFragment/globals.xml.ftl
+++ b/templates/other/ListFragment/globals.xml.ftl
@@ -1,6 +1,7 @@
<?xml version="1.0"?>
<globals>
- <global id="srcOut" value="src/${slashedPackageName(packageName)}" />
+ <global id="resOut" value="${resDir}" />
+ <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
<global id="collection_name" value="${extractLetters(objectKind?lower_case)}" />
<global id="className" value="${extractLetters(objectKind)}Fragment" />
<global id="fragment_layout" value="fragment_${extractLetters(objectKind?lower_case)}" />
diff --git a/templates/other/ListFragment/recipe.xml.ftl b/templates/other/ListFragment/recipe.xml.ftl
index 5db4fdb..7be60e9 100644
--- a/templates/other/ListFragment/recipe.xml.ftl
+++ b/templates/other/ListFragment/recipe.xml.ftl
@@ -1,26 +1,28 @@
<?xml version="1.0"?>
<recipe>
+ <dependency mavenUrl="com.android.support:support-v4:+"/>
<#if switchGrid == true>
- <merge from="res/values/refs.xml.ftl" />
+ <merge from="res/values/refs.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/refs.xml" />
<merge from="res/values/refs_lrg.xml.ftl"
- to="res/values-large/refs.xml" />
+ to="${escapeXmlAttribute(resOut)}/values-large/refs.xml" />
<merge from="res/values/refs_lrg.xml.ftl"
- to="res/values-sw600dp/refs.xml" />
+ to="${escapeXmlAttribute(resOut)}/values-sw600dp/refs.xml" />
<instantiate from="res/layout/fragment_grid.xml"
- to="res/layout/${fragment_layout}_grid.xml" />
+ to="${escapeXmlAttribute(resOut)}/layout/${fragment_layout}_grid.xml" />
<instantiate from="res/layout/fragment_list.xml"
- to="res/layout/${fragment_layout}_list.xml" />
+ to="${escapeXmlAttribute(resOut)}/layout/${fragment_layout}_list.xml" />
</#if>
<instantiate from="src/app_package/ListFragment.java.ftl"
- to="${srcOut}/${className}.java" />
+ to="${escapeXmlAttribute(srcOut)}/${className}.java" />
<instantiate from="src/app_package/dummy/DummyContent.java.ftl"
- to="${srcOut}/dummy/DummyContent.java" />
+ to="${escapeXmlAttribute(srcOut)}/dummy/DummyContent.java" />
- <open file="${srcOut}/${className}.java" />
+ <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
</recipe>
diff --git a/templates/other/ListFragment/root/res/layout/fragment_grid.xml b/templates/other/ListFragment/root/res/layout/fragment_grid.xml
index 87ae969..55c0044 100644
--- a/templates/other/ListFragment/root/res/layout/fragment_grid.xml
+++ b/templates/other/ListFragment/root/res/layout/fragment_grid.xml
@@ -3,7 +3,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
- tools:context=".${className}">
+ tools:context="${packageName}.${className}">
<GridView
android:id="@android:id/list"
diff --git a/templates/other/ListFragment/root/res/layout/fragment_list.xml b/templates/other/ListFragment/root/res/layout/fragment_list.xml
index 5b0e178..e37a0a6 100644
--- a/templates/other/ListFragment/root/res/layout/fragment_list.xml
+++ b/templates/other/ListFragment/root/res/layout/fragment_list.xml
@@ -3,7 +3,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
- tools:context=".${className}">
+ tools:context="${packageName}.${className}">
<ListView
android:id="@android:id/list"
diff --git a/templates/other/ListFragment/template.xml b/templates/other/ListFragment/template.xml
index e00257c..81eabad 100644
--- a/templates/other/ListFragment/template.xml
+++ b/templates/other/ListFragment/template.xml
@@ -1,7 +1,7 @@
<?xml version="1.0"?>
<template
- format="3"
- revision="1"
+ format="4"
+ revision="2"
name="New List Fragment"
description="Creates a new empty fragment containing a list that can optionally change to a grid when on large screens. Compatible back to API level 4."
minApi="7"
diff --git a/templates/other/Notification/globals.xml.ftl b/templates/other/Notification/globals.xml.ftl
index b302aa9..93d7472 100644
--- a/templates/other/Notification/globals.xml.ftl
+++ b/templates/other/Notification/globals.xml.ftl
@@ -1,9 +1,10 @@
<?xml version="1.0"?>
<globals>
- <global id="srcOut" value="src/${slashedPackageName(packageName)}" />
+ <global id="manifestOut" value="${manifestDir}" />
+ <global id="resOut" value="${resDir}" />
+ <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
<global id="notificationName" value="${className?replace('notification','','i')}" />
<global id="notification_name" value="${camelCaseToUnderscore(className?replace('notification','','i'))}" />
<global id="display_title" value="${camelCaseToUnderscore(className?replace('notification','','i'))?replace('_',' ')?cap_first}" />
-
<global id="icon_resource" value="ic_stat_${camelCaseToUnderscore(className?replace('notification','','i'))}" />
</globals>
diff --git a/templates/other/Notification/recipe.xml.ftl b/templates/other/Notification/recipe.xml.ftl
index bd1c265..1f1afec 100644
--- a/templates/other/Notification/recipe.xml.ftl
+++ b/templates/other/Notification/recipe.xml.ftl
@@ -1,25 +1,31 @@
<?xml version="1.0"?>
<recipe>
- <merge from="AndroidManifest.xml.ftl" />
+ <dependency mavenUrl="com.android.support:support-v4:+"/>
+ <merge from="AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
<#if expandedStyle == "picture">
<copy from="res/drawable-nodpi/example_picture_large.png"
- to="res/drawable-nodpi/example_picture.png" />
+ to="${escapeXmlAttribute(resOut)}/drawable-nodpi/example_picture.png" />
<#else>
<copy from="res/drawable-nodpi/example_picture_small.png"
- to="res/drawable-nodpi/example_picture.png" />
+ to="${escapeXmlAttribute(resOut)}/drawable-nodpi/example_picture.png" />
</#if>
<#if moreActions>
- <copy from="res/drawable-hdpi" />
- <copy from="res/drawable-mdpi" />
- <copy from="res/drawable-xhdpi" />
+ <copy from="res/drawable-hdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-hdpi" />
+ <copy from="res/drawable-mdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-mdpi" />
+ <copy from="res/drawable-xhdpi"
+ to="${escapeXmlAttribute(resOut)}/drawable-xhdpi" />
</#if>
- <merge from="res/values/strings.xml.ftl" />
+ <merge from="res/values/strings.xml.ftl"
+ to="${escapeXmlAttribute(resOut)}/values/strings.xml" />
<instantiate from="src/app_package/NotificationHelper.java.ftl"
- to="${srcOut}/${className}.java" />
- <open file="${srcOut}/${className}.java" />
+ to="${escapeXmlAttribute(srcOut)}/${className}.java" />
+ <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
</recipe>
diff --git a/templates/other/Notification/template.xml b/templates/other/Notification/template.xml
index 61fbc59..b9479b2 100644
--- a/templates/other/Notification/template.xml
+++ b/templates/other/Notification/template.xml
@@ -1,7 +1,7 @@
<?xml version="1.0"?>
<template
- format="3"
- revision="1"
+ format="4"
+ revision="2"
name="New Notification"
description="Creates a new helper class that can show or cancel a status bar notification."
minApi="4">
diff --git a/templates/other/Service/globals.xml.ftl b/templates/other/Service/globals.xml.ftl
index bfc27eb..7beccc1 100644
--- a/templates/other/Service/globals.xml.ftl
+++ b/templates/other/Service/globals.xml.ftl
@@ -1,4 +1,5 @@
<?xml version="1.0"?>
<globals>
- <global id="srcOut" value="src/${slashedPackageName(packageName)}" />
+ <global id="manifestOut" value="${manifestDir}" />
+ <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" />
</globals>
diff --git a/templates/other/Service/recipe.xml.ftl b/templates/other/Service/recipe.xml.ftl
index 6e4bd57..9e66da5 100644
--- a/templates/other/Service/recipe.xml.ftl
+++ b/templates/other/Service/recipe.xml.ftl
@@ -1,7 +1,8 @@
<?xml version="1.0"?>
<recipe>
- <merge from="AndroidManifest.xml.ftl" />
+ <merge from="AndroidManifest.xml.ftl"
+ to="${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml" />
<instantiate from="src/app_package/Service.java.ftl"
- to="${srcOut}/${className}.java" />
- <open file="${srcOut}/${className}.java" />
+ to="${escapeXmlAttribute(srcOut)}/${className}.java" />
+ <open file="${escapeXmlAttribute(srcOut)}/${className}.java" />
</recipe>
diff --git a/templates/other/Service/root/AndroidManifest.xml.ftl b/templates/other/Service/root/AndroidManifest.xml.ftl
index 14b0bce..0f747ea 100644
--- a/templates/other/Service/root/AndroidManifest.xml.ftl
+++ b/templates/other/Service/root/AndroidManifest.xml.ftl
@@ -1,7 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
<application>
- <service android:name=".${className}"
+ <service android:name="${packageName}.${className}"
android:exported="${isExported?string}"
android:enabled="${isEnabled?string}" >
</service>
diff --git a/templates/other/Service/template.xml b/templates/other/Service/template.xml
index 481fe74..de5b36c 100644
--- a/templates/other/Service/template.xml
+++ b/templates/other/Service/template.xml
@@ -1,7 +1,7 @@
<?xml version="1.0"?>
<template
- format="1"
- revision="1"
+ format="4"
+ revision="2"
name="Service"
description="Creates a new service component and adds it to your Android manifest.">
diff --git a/testutils/build.gradle b/testutils/build.gradle
index bcd7e9b..26118af 100644
--- a/testutils/build.gradle
+++ b/testutils/build.gradle
@@ -1,3 +1,6 @@
+apply plugin: 'java'
+apply plugin: 'distrib'
+
group = 'com.android.tools'
archivesBaseName = 'testutils'
@@ -15,3 +18,4 @@
// TODO: be able to ship to prebuilts/devtools/adt
shipping.isShipping = false
+apply from: '../baseVersion.gradle'
\ No newline at end of file
diff --git a/testutils/src/main/java/com/android/testutils/TestUtils.java b/testutils/src/main/java/com/android/testutils/TestUtils.java
index 312e638..172d263 100644
--- a/testutils/src/main/java/com/android/testutils/TestUtils.java
+++ b/testutils/src/main/java/com/android/testutils/TestUtils.java
@@ -49,6 +49,16 @@
root = new File(root, name);
}
+ // Hack: The sdk-common tests are not configured properly; running tests
+ // works correctly from Gradle but not from within the IDE. The following
+ // hack works around this quirk:
+ if (!root.isDirectory() && !root.getPath().contains("sdk-common")) {
+ File r = new File("sdk-common", root.getPath()).getAbsoluteFile();
+ if (r.isDirectory()) {
+ root = r;
+ }
+ }
+
TestCase.assertTrue("Test folder '" + name + "' does not exist! "
+ "(Tip: Check unit test launch config pwd)",
root.isDirectory());