Merge "Update New Project Template Dependencies" am: f908080572
am: f4369128a0

* commit 'f4369128a028f87f9e951f18a955ee0dbc3fcf9d':
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..73fc661 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..c65544b
--- /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..cdb3ebf
--- /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..1fa520b
--- /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..f76b7a9
--- /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&lt;lib. Never automatically change dest minsdk.
+ *                              Codenames are accepted if we can resolve their API level.
+ *      {@code @targetSdkVersion}: warning if dest&lt;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&lt;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 &lt;application android:name=...&gt;.
+     */
+    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&lt;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&lt;lib. Never automatically change dest minsdk.
+     * - {@code @targetSdkVersion}: warning if dest&lt;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 &lt; 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..337d40f
--- /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..df05828
--- /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, &params[6]);
+            r2 = ssFunc(t2, params);
+            r3 = ssFunc(p2, &params[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..9f6d34d
--- /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("&nbsp;");
+
+        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 &amp; and &lt;; 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 b0e28e1..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.7-bin.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 283ede4..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&lt;lib. Never automatically change dest minsdk.
- *                              Codenames are accepted if we can resolve their API level.
- *      {@code @targetSdkVersion}: warning if dest&lt;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&lt;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 &lt;application android:name=...&gt;.
-     */
-    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&lt;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&lt;lib. Never automatically change dest minsdk.
-     * - {@code @targetSdkVersion}: warning if dest&lt;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 &lt; 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 2b62626..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
@@ -24,7 +24,10 @@
 
 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;
@@ -40,12 +43,19 @@
     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;
@@ -53,25 +63,30 @@
         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 = sumThreadTimes(mCallees);
+        mInclusiveThreadTimeInCallees = sumInclusiveTimes(mCallees, ClockType.THREAD);
+        mInclusiveGlobalTimeInCallees = sumInclusiveTimes(mCallees, ClockType.GLOBAL);
     }
 
-    private long sumThreadTimes(@NonNull List<Call> callees) {
+    private long sumInclusiveTimes(@NonNull List<Call> callees, ClockType clockType) {
         long sum = 0;
         for (Call c : callees) {
-            sum += c.getInclusiveThreadTime();
+            sum += c.getInclusiveTime(clockType, TimeUnit.MICROSECONDS);
         }
         return sum;
     }
@@ -89,28 +104,39 @@
         return mDepth;
     }
 
-    public long getEntryThreadTime() {
-        return UnsignedInts.toLong(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 long getExitThreadTime() {
-        return UnsignedInts.toLong(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 getEntryGlobalTime() {
-        return UnsignedInts.toLong(mEntryGlobalTime);
+    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 getExitGlobalTime() {
-        return UnsignedInts.toLong(mExitGlobalTime);
+    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 getInclusiveThreadTime() {
-        return UnsignedInts.toLong(mExitThreadTime - mEntryThreadTime);
-    }
-
-    public long getExclusiveThreadTime() {
-        return getInclusiveThreadTime() - mInclusiveThreadTimeInCallees;
+    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 {
@@ -121,8 +147,6 @@
         private int mExitGlobalTime;
         private int mExitThreadTime;
 
-        private int mDepth = 0;
-
         private List<Builder> mCallees = null;
 
         public Builder(long methodId) {
@@ -150,20 +174,11 @@
             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;
         }
@@ -179,6 +194,11 @@
         public int getMethodExitGlobalTime() {
             return mExitGlobalTime;
         }
+
+        @NonNull
+        public Call build(@NonNull Stack<Long> backStack) {
+            return new Call(this, backStack);
+        }
     }
 
     /**
@@ -195,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());
@@ -226,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 d157475..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;
@@ -105,43 +105,63 @@
 
             if (c.getCallees() != null && !c.getCallees().isEmpty()) {
                 Call.Builder callee = c.getCallees().get(0);
-                entryThreadTime = callee.getMethodEntryThreadTime() - 1;
-                entryGlobalTime = callee.getMethodEntryGlobalTime() - 1;
+                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 (mTopLevelCall != null) {
             return;
         }
 
-        int lastExitThreadTime = 0;
-        int lastExitGlobalTime = 0;
-        if (!mTopLevelCalls.isEmpty()) {
-            Call.Builder last = mTopLevelCalls.get(mTopLevelCalls.size() - 1);
-            lastExitThreadTime = last.getMethodExitThreadTime() + 1;
-            lastExitGlobalTime = last.getMethodExitGlobalTime() + 1;
-        }
-
         // 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 builder = mCallStack.peek();
-            exitMethod(builder.getMethodId(), lastExitThreadTime, lastExitGlobalTime);
+            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, lastExitThreadTime, lastExitGlobalTime);
+        exitMethod(mTopLevelCallId, 0, 0, mTopLevelCalls);
 
         // TODO: use global / thread times to infer context switches
 
         // Build calls from their respective builders
         // 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();
+        mTopLevelCall = mTopLevelCalls.get(0).build(new Stack<Long>());
     }
 
     public Call getTopLevel() {
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 d9ec9ba..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.
@@ -32,32 +44,42 @@
  *  </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 the top level call in that thread */
-    private final SparseArray<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.mTopLevelCalls;
+
+        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 Call getTopLevelCall(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);
 
@@ -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,11 +278,9 @@
             }
 
             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);
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 e3e1b0c..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,17 +262,17 @@
         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:
                     globalTime = buffer.getInt();
                     threadTime = globalTime;
@@ -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 c3adb06..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
@@ -18,7 +18,10 @@
 
 import junit.framework.TestCase;
 
+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() {
@@ -31,7 +34,7 @@
         assertEquals(0x1, topLevel.getCallees().get(0).getMethodId());
     }
 
-    public void testCallStack1() {
+    private Call reconstructSampleCallStack() {
         CallStackReconstructor reconstructor = new CallStackReconstructor(0xff);
 
         reconstructor.addTraceAction(0x1, TraceAction.METHOD_ENTER, 10, 10);
@@ -47,15 +50,33 @@
         reconstructor.addTraceAction(0x6, TraceAction.METHOD_ENTER, 21, 21);
         reconstructor.addTraceAction(0x6, TraceAction.METHOD_EXIT, 22, 22);
 
-        String callStack = reconstructor.getTopLevel().toString();
+        return reconstructor.getTopLevel();
+    }
+
+    public void testCallStack() {
+        Call topLevel = reconstructSampleCallStack();
+        String callStack = topLevel.toString();
         String expectedCallStack =
-                  " -> 255 -> 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(0xff);
 
@@ -122,8 +143,9 @@
         List<Call> callees = reconstructor.getTopLevel().getCallees();
         assertFalse(callees.isEmpty());
         Call call = callees.get(0);
-        assertEquals(15 - 10, call.getInclusiveThreadTime());
-        assertEquals(15 - 10 - (14 - 11), call.getExclusiveThreadTime());
+        assertEquals(15 - 10, call.getInclusiveTime(ClockType.THREAD, TimeUnit.MICROSECONDS));
+        assertEquals(15 - 10 - (14 - 11), call.getExclusiveTime(ClockType.THREAD,
+                TimeUnit.MICROSECONDS));
     }
 
     public void testMissingCallDurations() {
@@ -138,8 +160,9 @@
         List<Call> callees = reconstructor.getTopLevel().getCallees();
         assertFalse(callees.isEmpty());
         Call call = callees.get(0);
-        assertEquals(15 - (11 - 1), call.getInclusiveThreadTime());
-        assertEquals(15 - (11 - 1) - (14 - 11), call.getExclusiveThreadTime());
+        assertEquals(15 - (11 - 1), call.getInclusiveTime(ClockType.THREAD, TimeUnit.MICROSECONDS));
+        assertEquals(15 - (11 - 1) - (14 - 11), call.getExclusiveTime(ClockType.THREAD,
+                TimeUnit.MICROSECONDS));
     }
 
     /**
@@ -157,7 +180,37 @@
         List<Call> callees = reconstructor.getTopLevel().getCallees();
         assertFalse(callees.isEmpty());
         Call call = callees.get(0);
-        assertEquals(8, call.getInclusiveThreadTime());
-        assertEquals(6, call.getExclusiveThreadTime());
+        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 ea7e0e6..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,13 +80,15 @@
         }
     }
 
-    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);
 
-        Call call = traceData.getTopLevelCall(threadId);
+        Call call = thread.getTopLevelCall();
+        assertNotNull(call);
         String actual = call.format(new CallFormatter(traceData.getMethods()));
         assertEquals(expectedCallSequence, actual);
     }
@@ -93,6 +99,9 @@
                         + "                    -> 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 {
@@ -112,17 +121,218 @@
         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;
+    public void testCallDurations() throws IOException {
+        validateCallDurations("/basic.trace", "AsyncTask #1");
+        validateCallDurations("/mismatched.trace", "AsyncTask #1");
+        validateCallDurations("/exception.trace", "AsyncTask #1");
+    }
+
+    private void validateCallDurations(String traceName, String threadName) throws IOException {
+        VmTraceData traceData = getVmTraceData(traceName);
+
+        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 -1;
+        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 &#169; &#xA9; 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\">&#169; 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 &lt;archives&gt; node is mandatory in the repository packages and the
+                collection must have at least one &lt;archive&gt; 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 &lt;extra&gt; package
+         that can be installed in an Android project.
+         If present, the &lt;project-files&gt; 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 &lt;extra&gt; package
+                that can be installed in an Android project.
+                If present, the &lt;project-files&gt; 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 0c18948..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 ae73f7d..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 472dd14..0000000
--- a/templates/gradle-projects/NewAndroidApplication/root/build.gradle.ftl
+++ /dev/null
@@ -1,37 +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 {
-<#if androidSupportLibraryUrl??>
-    compile '${androidSupportLibraryUrl}'
-</#if>
-}
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 a5a8da2..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" />
-
-    <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 752943a..0000000
--- a/templates/gradle-projects/NewAndroidLibrary/root/build.gradle.ftl
+++ /dev/null
@@ -1,37 +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 {
-<#if androidSupportLibraryUrl??>
-    compile '${androidSupportLibraryUrl}'
-</#if>
-}
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 a25cf02..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" />
-
-    <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());